[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\nmax_line_length = 120\ntab_width = 4\n# noinspection EditorConfigKeyCorrectness\ndisabled_rules = no-wildcard-imports, no-unused-imports\n\n[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]\nij_continuation_indent_size = 4\nij_xml_attribute_wrap = on_every_item\n\n[{*.kt,*.kts}]\nij_kotlin_allow_trailing_comma_on_call_site = true\nij_kotlin_allow_trailing_comma = true\nij_kotlin_code_style_defaults = KOTLIN_OFFICIAL\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n    - name: ⚠️ Source issue\n      url: https://github.com/KotatsuApp/kotatsu-parsers/issues/new\n      about: If you have a problem with a specific **manga source** or want to propose a new one, please open an issue in the kotatsu-parsers repository instead\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/report_bug.yml",
    "content": "name: 🐞 Bug report\ndescription: Report a bug in Kotatsu\nlabels: [bug]\nbody:\n\n    - type: textarea\n      id: summary\n      attributes:\n          label: Brief summary\n          description: Please describe, what went wrong\n      validations:\n          required: true\n\n    - type: textarea\n      id: reproduce-steps\n      attributes:\n          label: Steps to reproduce\n          description: Please provide a way to reproduce this issue. Screenshots or videos can be very helpful\n          placeholder: |\n              Example:\n                1. First step\n                2. Second step\n                3. Issue here\n      validations:\n          required: false\n\n\n    - type: input\n      id: kotatsu-version\n      attributes:\n          label: Kotatsu version\n          description: You can find your Kotatsu version in **Settings → About**.\n          placeholder: |\n              Example: \"3.3\"\n      validations:\n          required: true\n\n    - type: input\n      id: android-version\n      attributes:\n          label: Android version\n          description: You can find this somewhere in your Android settings.\n          placeholder: |\n              Example: \"12.0\"\n      validations:\n          required: true\n\n    - type: input\n      id: device\n      attributes:\n          label: Device\n          description: List your device and model.\n          placeholder: |\n              Example: \"LG Nexus 5X\"\n      validations:\n          required: false\n\n    - type: checkboxes\n      id: acknowledgements\n      attributes:\n          label: Acknowledgements\n          options:\n              - label: This is not a duplicate of an existing issue. Please look through the list of [open issues](https://github.com/KotatsuApp/Kotatsu/issues) before creating a new one.\n                required: true\n              - label: This is not an issue with a specific manga source. Otherwise, you have to open an issue in the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers/issues/new/choose).\n                required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/request_feature.yml",
    "content": "name: ⭐ Feature request\ndescription: Suggest a new idea how to improve Kotatsu\nlabels: [feature request]\nbody:\n\n    - type: textarea\n      id: feature-description\n      attributes:\n          label: Describe your suggested feature\n          description: How can Kotatsu be improved?\n          placeholder: |\n              Example:\n                \"It should work like this...\"\n      validations:\n          required: true\n\n    - type: checkboxes\n      id: acknowledgements\n      attributes:\n          label: Acknowledgements\n          description: Read this carefully, we will close and ignore your issue if you skimmed through this.\n          options:\n              - label: This is not a duplicate of an existing issue. Please look through the list of [open issues](https://github.com/KotatsuApp/Kotatsu/issues) before creating a new one.\n                required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "**PLEASE READ THIS**\n\nI acknowledge that:\n\n- I have updated to the latest version of the app (https://github.com/KotatsuApp/Kotatsu/releases/latest)\n- If this is an issue with a parser, that I should be opening an issue in https://github.com/KotatsuApp/kotatsu-parsers\n- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue\n- I will fill out the title and the information in this template\n\nNote that the issue will be automatically closed if you do not fill out the title or requested information.\n\n**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**\n\n---\n\n## Device information\n* Kotatsu version: ?\n* Android version: ?\n* Device: ?\n\n## Steps to reproduce\n1. First step\n2. Second step\n\n## Issue/Request\n?\n\n## Other details\nAdditional details and attachments."
  },
  {
    "path": ".github/workflows/issue_moderator.yml",
    "content": "name: Issue moderator\n\non:\n  issues:\n    types: [opened, edited, reopened]\n  issue_comment:\n    types: [created]\n\njobs:\n  moderate:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Moderate issues\n        uses: tachiyomiorg/issue-moderator-action@v1\n        with:\n           repo-token: ${{ secrets.GITHUB_TOKEN }}\n           auto-close-rules: |\n             [\n               {\n                 \"type\": \"body\",\n                 \"regex\": \".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*\",\n                 \"message\": \"The acknowledgment section was not removed.\"\n               },\n               {\n                 \"type\": \"body\",\n                 \"regex\": \".*\\\\* (Kotatsu version|Android version|Device): \\\\?.*\",\n                 \"message\": \"Requested information in the template was not filled out.\"\n               }\n             ]"
  },
  {
    "path": ".github/workflows/trigger-site-deploy.yml",
    "content": "name: Trigger Site Update\n\non:\n  release:\n    types: [published]\n\njobs:\n  trigger-site:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Send repository_dispatch to site-repo\n        uses: peter-evans/repository-dispatch@v3\n        with:\n          token: ${{ secrets.SITE_REPO_TOKEN }}\n          repository: KotatsuApp/website\n          event-type: app-release\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/dictionaries\n/.idea/modules.xml\n/.idea/misc.xml\n/.idea/markdown.xml\n/.idea/discord.xml\n/.idea/compiler.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/ktlint-plugin.xml\n/.idea/assetWizardSettings.xml\n/.idea/kotlinScripting.xml\n/.idea/kotlinc.xml\n/.idea/deploymentTargetDropDown.xml\n/.idea/androidTestResultsUserPreferences.xml\n/.idea/deploymentTargetSelector.xml\n/.idea/render.experimental.xml\n/.idea/inspectionProfiles/\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\n/.idea/deviceManager.xml\n/.kotlin/\n/.idea/AndroidProjectSystem.xml\n"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n/migrations.xml\n/runConfigurations.xml\n/appInsightsSettings.xml\n/kotlinCodeInsightSettings.xml\n"
  },
  {
    "path": ".idea/appInsightsSettings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AppInsightsSettings\">\n    <option name=\"tabSettings\">\n      <map>\n        <entry key=\"Firebase Crashlytics\">\n          <value>\n            <InsightsFilterSettings>\n              <option name=\"connection\">\n                <ConnectionSetting>\n                  <option name=\"appId\" value=\"PLACEHOLDER\" />\n                  <option name=\"mobileSdkAppId\" value=\"\" />\n                  <option name=\"projectId\" value=\"\" />\n                  <option name=\"projectNumber\" value=\"\" />\n                </ConnectionSetting>\n              </option>\n              <option name=\"signal\" value=\"SIGNAL_UNSPECIFIED\" />\n              <option name=\"timeIntervalDays\" value=\"THIRTY_DAYS\" />\n              <option name=\"visibilityType\" value=\"ALL\" />\n            </InsightsFilterSettings>\n          </value>\n        </entry>\n      </map>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <option name=\"OTHER_INDENT_OPTIONS\">\n      <value />\n    </option>\n    <AndroidXmlCodeStyleSettings>\n      <option name=\"LAYOUT_SETTINGS\">\n        <value>\n          <option name=\"INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION\" value=\"true\" />\n        </value>\n      </option>\n      <option name=\"MANIFEST_SETTINGS\">\n        <value>\n          <option name=\"INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION\" value=\"true\" />\n        </value>\n      </option>\n      <option name=\"OTHER_SETTINGS\">\n        <value>\n          <option name=\"INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION\" value=\"true\" />\n        </value>\n      </option>\n    </AndroidXmlCodeStyleSettings>\n    <JavaCodeStyleSettings>\n      <option name=\"IMPORT_LAYOUT_TABLE\">\n        <value>\n          <package name=\"android\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"androidx\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"com\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"junit\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"net\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"org\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"java\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"javax\" withSubpackages=\"true\" static=\"true\" />\n          <package name=\"\" withSubpackages=\"true\" static=\"true\" />\n          <emptyLine />\n          <package name=\"android\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"androidx\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"com\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"junit\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"net\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"org\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"java\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"javax\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n        </value>\n      </option>\n    </JavaCodeStyleSettings>\n    <JetCodeStyleSettings>\n      <option name=\"ALLOW_TRAILING_COMMA\" value=\"true\" />\n      <option name=\"ALLOW_TRAILING_COMMA_COLLECTION_LITERAL_EXPRESSION\" value=\"true\" />\n      <option name=\"ALLOW_TRAILING_COMMA_VALUE_ARGUMENT_LIST\" value=\"true\" />\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </JetCodeStyleSettings>\n    <codeStyleSettings language=\"Shell Script\">\n      <indentOptions>\n        <option name=\"USE_TAB_CHARACTER\" value=\"true\" />\n      </indentOptions>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"XML\">\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      </indentOptions>\n      <arrangement>\n        <rules>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:android</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:id</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>name</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>style</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>ANDROID_ATTRIBUTE_ORDER</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_ATTRIBUTE />\n                  <XML_NAMESPACE>.*</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n        </rules>\n      </arrangement>\n    </codeStyleSettings>\n    <codeStyleSettings language=\"kotlin\">\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n      <option name=\"LINE_COMMENT_AT_FIRST_COLUMN\" value=\"false\" />\n      <option name=\"BLOCK_COMMENT_AT_FIRST_COLUMN\" value=\"false\" />\n      <option name=\"LINE_COMMENT_ADD_SPACE\" value=\"true\" />\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/gradle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleMigrationSettings\" migrationVersion=\"1\" />\n  <component name=\"GradleSettings\">\n    <option name=\"linkedExternalProjectsSettings\">\n      <GradleProjectSettings>\n        <option name=\"testRunner\" value=\"CHOOSE_PER_TEST\" />\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"gradleJvm\" value=\"#GRADLE_LOCAL_JAVA_HOME\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n            <option value=\"$PROJECT_DIR$/app\" />\n          </set>\n        </option>\n      </GradleProjectSettings>\n    </option>\n  </component>\n</project>\n"
  },
  {
    "path": ".idea/jarRepositories.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RemoteRepositoriesConfiguration\">\n    <remote-repository>\n      <option name=\"id\" value=\"central\" />\n      <option name=\"name\" value=\"Maven Central repository\" />\n      <option name=\"url\" value=\"https://repo1.maven.org/maven2\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"jboss.community\" />\n      <option name=\"name\" value=\"JBoss Community repository\" />\n      <option name=\"url\" value=\"https://repository.jboss.org/nexus/content/repositories/public/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"BintrayJCenter\" />\n      <option name=\"name\" value=\"BintrayJCenter\" />\n      <option name=\"url\" value=\"https://jcenter.bintray.com/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"Google\" />\n      <option name=\"name\" value=\"Google\" />\n      <option name=\"url\" value=\"https://dl.google.com/dl/android/maven2/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"maven\" />\n      <option name=\"name\" value=\"maven\" />\n      <option name=\"url\" value=\"https://jitpack.io\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"maven2\" />\n      <option name=\"name\" value=\"maven2\" />\n      <option name=\"url\" value=\"https://dl.bintray.com/kotlin/kotlin-eap\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"MavenRepo\" />\n      <option name=\"name\" value=\"MavenRepo\" />\n      <option name=\"url\" value=\"https://repo.maven.apache.org/maven2/\" />\n    </remote-repository>\n    <remote-repository>\n      <option name=\"id\" value=\"maven2\" />\n      <option name=\"name\" value=\"maven2\" />\n      <option name=\"url\" value=\"https://maven.pkg.github.com/nv95/kotatsu-parsers\" />\n    </remote-repository>\n  </component>\n</project>"
  },
  {
    "path": ".idea/kotlinCodeInsightSettings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"KotlinCodeInsightWorkspaceSettings\">\n    <option name=\"addUnambiguousImportsOnTheFly\" value=\"true\" />\n    <option name=\"optimizeImportsOnTheFly\" value=\"true\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/ktlint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"KtlintProjectConfiguration\">\n    <enableKtlint>false</enableKtlint>\n    <androidMode>true</androidMode>\n    <treatAsErrors>false</treatAsErrors>\n    <disabledRules>\n      <list>\n        <option value=\"no-empty-first-line-in-method-block\" />\n      </list>\n    </disabledRules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GitSharedSettings\">\n    <option name=\"FORCE_PUSH_PROHIBITED_PATTERNS\">\n      <list>\n        <option value=\"master\" />\n        <option value=\"devel\" />\n        <option value=\"legacy\" />\n      </list>\n    </option>\n  </component>\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".weblate",
    "content": "[weblate]\nurl = https://hosted.weblate.org/api/\ntranslation = kotatsu/strings\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Kotatsu contribution guidelines\n\n+ If you want to **fix bugs** or **implement new features** that **already have an [issue card](https://github.com/KotatsuApp/Kotatsu/issues):** please assign this issue to you and/or comment about it.\n+ If you want to **implement a new feature:** open an issue or discussion regarding it to ensure it will be accepted.\n+ **Translations** have to be managed using the [Weblate](https://hosted.weblate.org/engage/kotatsu/) platform.\n+ In case you want to **add a new manga source,** refer to the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers).\n\n**Refactoring** or some **dev-faces improvements** might also be accepted. However, please stick to the following principles:\n\n+ **Performance matters.** In the case of choosing between source code beauty and performance, performance should be a priority. \n+ Please, **do not modify readme and other information files** (except for typos).\n+ **Avoid adding new dependencies** unless required. APK size is important.\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.md",
    "content": "> [!IMPORTANT]\n> In light of recent challenges — including threating actions from Kakao Entertainment Corp and upcoming Google’s\n> [new sideloading policy](https://f-droid.org/ru/2025/10/28/sideloading.html) — we’ve made the difficult decision to shut down Kotatsu and end its support. We’re deeply grateful\n> to everyone who contributed and to the amazing community that grew around this project.\n\n---\n\n<div align=\"center\">\n\n**[Kotatsu](https://github.com/KotatsuApp/Kotatsu) is a free and open-source manga reader for Android with built-in\nonline content sources.**\n\n![Android 6.0](https://img.shields.io/badge/android-6.0+-brightgreen) [![Sources count](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2FKotatsuApp%2Fkotatsu-parsers%2Frefs%2Fheads%2Fmaster%2F.github%2Fsummary.yaml&query=total&label=manga%20sources&color=%23E9321C)](https://github.com/KotatsuApp/kotatsu-parsers) [![weblate](https://hosted.weblate.org/widgets/kotatsu/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/kotatsu/) [![Discord](https://img.shields.io/discord/898363402467045416?color=5865f2&label=discord)](https://discord.gg/NNJ5RgVBC5) [![Telegram](https://img.shields.io/badge/chat-telegram-60ACFF?)](https://t.me/kotatsuapp) [![License](https://img.shields.io/github/license/KotatsuApp/Kotatsu)](https://github.com/KotatsuApp/Kotatsu/blob/devel/LICENSE)\n\n### Main Features\n\n<div align=\"left\">\n\n* Online [manga catalogues](https://github.com/KotatsuApp/kotatsu-parsers) (with 1200+ manga sources)\n* Search manga by name, genres and more filters\n* Favorites organized by user-defined categories\n* Reading history, bookmarks and incognito mode support\n* Download manga and read it offline. Third-party CBZ archives are also supported\n* Clean and convenient Material You UI, optimized for phones, tablets and desktop\n* Standard and Webtoon-optimized customizable reader, gesture support on reading interface\n* Notifications about new chapters with updates feed, manga recommendations (with filters)\n* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu\n* Password / fingerprint-protected access to the app\n* Automatically sync app data with other devices on the same account\n* Support for older devices running Android 6.0+\n\n</div>\n\n### In-App Screenshots\n\n<div align=\"center\">\n    <img src=\"./metadata/en-US/images/phoneScreenshots/1.png\" alt=\"Mobile view\" width=\"250\"/>\n    <img src=\"./metadata/en-US/images/phoneScreenshots/2.png\" alt=\"Mobile view\" width=\"250\"/>\n    <img src=\"./metadata/en-US/images/phoneScreenshots/3.png\" alt=\"Mobile view\" width=\"250\"/>\n    <img src=\"./metadata/en-US/images/phoneScreenshots/4.png\" alt=\"Mobile view\" width=\"250\"/>\n    <img src=\"./metadata/en-US/images/phoneScreenshots/5.png\" alt=\"Mobile view\" width=\"250\"/>\n    <img src=\"./metadata/en-US/images/phoneScreenshots/6.png\" alt=\"Mobile view\" width=\"250\"/>\n</div>\n\n<br>\n\n<div align=\"center\">\n    <img src=\"./metadata/en-US/images/tenInchScreenshots/1.png\" alt=\"Tablet view\" width=\"400\"/>\n    <img src=\"./metadata/en-US/images/tenInchScreenshots/2.png\" alt=\"Tablet view\" width=\"400\"/>\n</div>\n\n### Localization\n\n<a href=\"https://hosted.weblate.org/engage/kotatsu/\">\n<img src=\"https://hosted.weblate.org/widget/kotatsu/horizontal-auto.png\" alt=\"Translation status\" />\n</a>\n\n**[Kotatsu](https://github.com/KotatsuApp/Kotatsu) is localized in a number of different languages.**<br>\n**📌 If you would like to help improve these or add new languages,\nplease head over to the [Weblate project page](https://hosted.weblate.org/engage/kotatsu/)**\n\n### Contributing\n\n<br>\n\n<a href=\"https://github.com/KotatsuApp/Kotatsu\">\n  <picture>\n    <source srcset=\"https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu&bg_color=0d1117&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true\" media=\"(prefers-color-scheme: dark)\">\n    <img src=\"https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true\" alt=\"Kotatsu GitHub Repository\">\n  </picture>\n</a>\n<a href=\"https://github.com/KotatsuApp/Kotatsu-parsers\">\n  <picture>\n    <source srcset=\"https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu-parsers&bg_color=0d1117&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true\" media=\"(prefers-color-scheme: dark)\">\n    <img src=\"https://github-readme-stats.vercel.app/api/pin/?username=KotatsuApp&repo=Kotatsu-parsers&text_color=1976d2&title_color=1976d2&icon_color=0877d2&border_radius=10&description_lines_count=2&show_owner=true\" alt=\"Kotatsu-parsers GitHub Repository\">\n  </picture>\n</a><br></br>\n\n</br>\n\n**📌 Pull requests are welcome, if you want:\nSee [CONTRIBUTING.md](https://github.com/KotatsuApp/Kotatsu/blob/devel/CONTRIBUTING.md) for the guidelines**\n\n### Certificate fingerprints\n\n```plaintext\n2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE\n```\n\n```plaintext\n67:E1:51:00:BB:80:93:01:78:3E:DC:B6:34:8F:A3:BB:F8:30:34:D9:1E:62:86:8A:91:05:3D:BD:70:DB:3F:18\n```\n\n### License\n\n[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)\n\n<div align=\"left\">\n\nYou may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications\nto or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build &\ninstall instructions.\n\n</div>\n\n### DMCA disclaimer\n\n<div align=\"left\">\n\nThe developers of this application do not have any affiliation with the content available in the app and does not store\nor distribute any content. This application should be considered a web browser, all content that can be found using this\napplication is freely available on the Internet. All DMCA takedown requests should be sent to the owners of the website\nwhere the content is hosted.\n\n</div>\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n/schemas/\n"
  },
  {
    "path": "app/build.gradle",
    "content": "import java.time.LocalDateTime\n\nplugins {\n\tid 'com.android.application'\n\tid 'kotlin-android'\n\tid 'com.google.devtools.ksp'\n\tid 'kotlin-parcelize'\n\tid 'dagger.hilt.android.plugin'\n\tid 'androidx.room'\n\tid 'org.jetbrains.kotlin.plugin.serialization'\n\t// enable if needed\n\t// id 'dev.reformator.stacktracedecoroutinator'\n}\n\nandroid {\n\tcompileSdk = 36\n\tbuildToolsVersion = '35.0.0'\n\tnamespace = 'org.koitharu.kotatsu'\n\n\tdefaultConfig {\n\t\tapplicationId 'org.koitharu.kotatsu'\n\t\tminSdk = 23\n\t\ttargetSdk = 36\n\t\tversionCode = 1033\n\t\tversionName = '9.4.1'\n\t\tgeneratedDensities = []\n\t\ttestInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'\n\t\tksp {\n\t\t\targ('room.generateKotlin', 'true')\n\t\t}\n\t\tandroidResources {\n\t\t\t// https://issuetracker.google.com/issues/408030127\n\t\t\tgenerateLocaleConfig false\n\t\t}\n\t\tdef localProperties = new Properties()\n\t\tdef localPropertiesFile = rootProject.file('local.properties')\n\t\tif (localPropertiesFile.exists()) {\n\t\t\tlocalProperties.load(new FileInputStream(localPropertiesFile))\n\t\t}\n\t\tresValue 'string', 'tg_backup_bot_token', localProperties.getProperty('tg_backup_bot_token', '')\n\t}\n\tbuildTypes {\n\t\tdebug {\n\t\t\tapplicationIdSuffix = '.debug'\n\t\t}\n\t\trelease {\n\t\t\tminifyEnabled true\n\t\t\tshrinkResources true\n\t\t\tproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n\t\t}\n\t\tnightly {\n\t\t\tinitWith release\n\t\t\tapplicationIdSuffix = '.nightly'\n\t\t}\n\t}\n\tbuildFeatures {\n\t\tviewBinding true\n\t\tbuildConfig true\n\t}\n\tpackagingOptions {\n\t\tresources {\n\t\t\texcludes += [\n\t\t\t\t'META-INF/README.md',\n\t\t\t\t'META-INF/NOTICE.md'\n\t\t\t]\n\t\t}\n\t}\n\tsourceSets {\n\t\tandroidTest.assets.srcDirs += files(\"$projectDir/schemas\".toString())\n\t\tmain.java.srcDirs += 'src/main/kotlin/'\n\t}\n\tcompileOptions {\n\t\tcoreLibraryDesugaringEnabled true\n\t\tsourceCompatibility JavaVersion.VERSION_11\n\t\ttargetCompatibility JavaVersion.VERSION_11\n\t}\n\tkotlinOptions {\n\t\tjvmTarget = JavaVersion.VERSION_11.toString()\n\t\tfreeCompilerArgs += [\n\t\t\t'-opt-in=kotlin.ExperimentalStdlibApi',\n\t\t\t'-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',\n\t\t\t'-opt-in=kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi',\n\t\t\t'-opt-in=kotlinx.coroutines.InternalForInheritanceCoroutinesApi',\n\t\t\t'-opt-in=kotlinx.coroutines.FlowPreview',\n\t\t\t'-opt-in=kotlin.contracts.ExperimentalContracts',\n\t\t\t'-opt-in=coil3.annotation.ExperimentalCoilApi',\n\t\t\t'-opt-in=coil3.annotation.InternalCoilApi',\n\t\t\t'-opt-in=kotlinx.serialization.ExperimentalSerializationApi',\n\t\t\t'-Xjspecify-annotations=strict',\n\t\t\t'-Xannotation-default-target=first-only',\n\t\t\t'-Xtype-enhancement-improvements-strict-mode'\n\t\t]\n\t}\n\troom {\n\t\tschemaDirectory \"$projectDir/schemas\"\n\t}\n\tlint {\n\t\tabortOnError true\n\t\tdisable 'MissingTranslation', 'PrivateResource', 'SetJavaScriptEnabled', 'SimpleDateFormat'\n\t}\n\ttestOptions {\n\t\tunitTests.includeAndroidResources true\n\t\tunitTests.returnDefaultValues false\n\t\tkotlinOptions {\n\t\t\tfreeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi']\n\t\t}\n\t}\n\tapplicationVariants.configureEach { variant ->\n\t\tif (variant.name == 'nightly') {\n\t\t\tvariant.outputs.each { output ->\n\t\t\t\tdef now = LocalDateTime.now()\n\t\t\t\toutput.versionCodeOverride = now.format(\"yyMMdd\").toInteger()\n\t\t\t\toutput.versionNameOverride = 'N' + now.format(\"yyyyMMdd\")\n\t\t\t}\n\t\t}\n\t}\n}\ndependencies {\n\tdef parsersVersion = libs.versions.parsers.get()\n\tif (System.properties.containsKey('parsersVersionOverride')) {\n\t\t// usage:\n\t\t// -DparsersVersionOverride=$(curl -s https://api.github.com/repos/kotatsuapp/kotatsu-parsers/commits/master -H \"Accept: application/vnd.github.sha\" | cut -c -10)\n\t\tparsersVersion = System.getProperty('parsersVersionOverride')\n\t}\n\t//noinspection UseTomlInstead\n\timplementation(\"com.github.KotatsuApp:kotatsu-parsers:$parsersVersion\") {\n\t\texclude group: 'org.json', module: 'json'\n\t}\n\n\tcoreLibraryDesugaring libs.desugar.jdk.libs\n\timplementation libs.kotlin.stdlib\n\timplementation libs.kotlinx.coroutines.android\n\timplementation libs.kotlinx.coroutines.guava\n\n\timplementation libs.androidx.appcompat\n\timplementation libs.androidx.core\n\timplementation libs.androidx.activity\n\timplementation libs.androidx.fragment\n\timplementation libs.androidx.transition\n\timplementation libs.androidx.collection\n\timplementation libs.lifecycle.viewmodel\n\timplementation libs.lifecycle.service\n\timplementation libs.lifecycle.process\n\timplementation libs.androidx.constraintlayout\n\timplementation libs.androidx.documentfile\n\timplementation libs.androidx.swiperefreshlayout\n\timplementation libs.androidx.recyclerview\n\timplementation libs.androidx.viewpager2\n\timplementation libs.androidx.preference\n\timplementation libs.androidx.biometric\n\timplementation libs.material\n\timplementation libs.androidx.lifecycle.common.java8\n\timplementation libs.androidx.webkit\n\n\timplementation libs.androidx.work.runtime\n\timplementation libs.guava\n\n    // Foldable/Window layout\n    implementation libs.androidx.window\n\n\timplementation libs.androidx.room.runtime\n\timplementation libs.androidx.room.ktx\n\tksp libs.androidx.room.compiler\n\n\timplementation libs.okhttp\n\timplementation libs.okhttp.tls\n\timplementation libs.okhttp.dnsoverhttps\n\timplementation libs.okio\n\timplementation libs.kotlinx.serialization.json\n\n\timplementation libs.adapterdelegates\n\timplementation libs.adapterdelegates.viewbinding\n\n\timplementation libs.hilt.android\n\tksp libs.hilt.compiler\n\timplementation libs.androidx.hilt.work\n\tksp libs.androidx.hilt.compiler\n\n\timplementation libs.coil.core\n\timplementation libs.coil.network\n\timplementation libs.coil.gif\n\timplementation libs.coil.svg\n\timplementation libs.avif.decoder\n\timplementation libs.ssiv\n\timplementation libs.disk.lru.cache\n\timplementation libs.markwon\n\timplementation libs.kizzyrpc\n\n\timplementation libs.acra.http\n\timplementation libs.acra.dialog\n\n\timplementation libs.conscrypt.android\n\n\tdebugImplementation libs.leakcanary.android\n\tnightlyImplementation libs.leakcanary.android\n\tdebugImplementation libs.workinspector\n\n\ttestImplementation libs.junit\n\ttestImplementation libs.json\n\ttestImplementation libs.kotlinx.coroutines.test\n\n\tandroidTestImplementation libs.androidx.runner\n\tandroidTestImplementation libs.androidx.rules\n\tandroidTestImplementation libs.androidx.test.core\n\tandroidTestImplementation libs.androidx.junit\n\n\tandroidTestImplementation libs.kotlinx.coroutines.test\n\n\tandroidTestImplementation libs.androidx.room.testing\n\tandroidTestImplementation libs.moshi.kotlin\n\n\tandroidTestImplementation libs.hilt.android.testing\n\tkspAndroidTest libs.hilt.android.compiler\n}\n"
  },
  {
    "path": "app/libs/.gitkeep",
    "content": " \n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "-optimizationpasses 8\n-dontobfuscate\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n\tpublic static void checkExpressionValueIsNotNull(...);\n\tpublic static void checkNotNullExpressionValue(...);\n\tpublic static void checkReturnedValueIsNotNull(...);\n\tpublic static void checkFieldIsNotNull(...);\n\tpublic static void checkParameterIsNotNull(...);\n\tpublic static void checkNotNullParameter(...);\n}\n\n-dontwarn okhttp3.internal.platform.**\n-dontwarn org.conscrypt.**\n-dontwarn org.bouncycastle.**\n-dontwarn org.openjsse.**\n-dontwarn com.google.j2objc.annotations.**\n-dontwarn coil3.PlatformContext\n\n-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment\n-keep class org.koitharu.kotatsu.settings.about.changelog.ChangelogFragment\n\n-keep class org.koitharu.kotatsu.core.exceptions.* { *; }\n-keep class org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy { *; }\n-keep class org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment { *; }\n-keep class org.jsoup.parser.Tag\n-keep class org.jsoup.internal.StringUtil\n\n-keep class org.acra.security.NoKeyStoreFactory { *; }\n-keep class org.acra.config.DefaultRetryPolicy { *; }\n-keep class org.acra.attachment.DefaultAttachmentProvider { *; }\n-keep class org.acra.sender.JobSenderService\n"
  },
  {
    "path": "app/src/androidTest/assets/categories/simple.json",
    "content": "{\n\t\"id\": 4,\n\t\"title\": \"Read later\",\n\t\"sortKey\": 1,\n\t\"order\": \"NEWEST\",\n\t\"createdAt\": 1335906000000,\n\t\"isTrackingEnabled\": true,\n\t\"isVisibleInLibrary\": true\n}\n"
  },
  {
    "path": "app/src/androidTest/assets/manga/bad_ids.json",
    "content": "{\n\t\"id\": -2096681732556647985,\n\t\"title\": \"Странствия Эманон\",\n\t\"altTitles\": [],\n\t\"url\": \"/stranstviia_emanon\",\n\t\"publicUrl\": \"https://readmanga.io/stranstviia_emanon\",\n\t\"rating\": 0.9400894,\n\t\"isNsfw\": true,\n\t\"coverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg\",\n\t\"tags\": [\n\t\t{\n\t\t\t\"title\": \"Сверхъестественное\",\n\t\t\t\"key\": \"supernatural\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Сэйнэн\",\n\t\t\t\"key\": \"seinen\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Повседневность\",\n\t\t\t\"key\": \"slice_of_life\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Приключения\",\n\t\t\t\"key\": \"adventure\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"state\": \"FINISHED\",\n\t\"authors\": [],\n\t\"largeCoverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg\",\n\t\"description\": \"Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \\n<br>Начало истории читайте в \\\"Воспоминаниях Эманон\\\". \\n<div class=\\\"clearfix\\\"></div>\",\n\t\"chapters\": [\n\t\t{\n\t\t\t\"id\": 1552943969433540704,\n\t\t\t\"title\": \"1 - 1\",\n\t\t\t\"number\": 1,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/1\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433540705,\n\t\t\t\"title\": \"1 - 2\",\n\t\t\t\"number\": 2,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/2\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433540706,\n\t\t\t\"title\": \"1 - 3\",\n\t\t\t\"number\": 3,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/3\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433540707,\n\t\t\t\"title\": \"1 - 4\",\n\t\t\t\"number\": 4,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/4\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433540708,\n\t\t\t\"title\": \"1 - 5\",\n\t\t\t\"number\": 5,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/5\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433541665,\n\t\t\t\"title\": \"2 - 1\",\n\t\t\t\"number\": 6,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/1\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1415570400000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433541666,\n\t\t\t\"title\": \"2 - 2\",\n\t\t\t\"number\": 7,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/2\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1419976800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433541667,\n\t\t\t\"title\": \"2 - 3\",\n\t\t\t\"number\": 8,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/3\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1427922000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433541668,\n\t\t\t\"title\": \"2 - 4\",\n\t\t\t\"number\": 9,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/4\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1436907600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433541669,\n\t\t\t\"title\": \"2 - 5\",\n\t\t\t\"number\": 10,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/5\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1446674400000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433541670,\n\t\t\t\"title\": \"2 - 6\",\n\t\t\t\"number\": 11,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/6\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1451512800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433542626,\n\t\t\t\"title\": \"3 - 1\",\n\t\t\t\"number\": 12,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/1\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1461618000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433542627,\n\t\t\t\"title\": \"3 - 2\",\n\t\t\t\"number\": 13,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/2\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1461618000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 1552943969433542628,\n\t\t\t\"title\": \"3 - 3\",\n\t\t\t\"number\": 14,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/3\",\n\t\t\t\"scanlator\": \"\",\n\t\t\t\"uploadDate\": 1465851600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"source\": \"READMANGA_RU\"\n}\n"
  },
  {
    "path": "app/src/androidTest/assets/manga/empty.json",
    "content": "{\n\t\"id\": -2096681732556647985,\n\t\"title\": \"Странствия Эманон\",\n\t\"altTitles\": [],\n\t\"url\": \"/stranstviia_emanon\",\n\t\"publicUrl\": \"https://readmanga.io/stranstviia_emanon\",\n\t\"rating\": 0.9400894,\n\t\"isNsfw\": true,\n\t\"coverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg\",\n\t\"tags\": [\n\t\t{\n\t\t\t\"title\": \"Сверхъестественное\",\n\t\t\t\"key\": \"supernatural\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Сэйнэн\",\n\t\t\t\"key\": \"seinen\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Повседневность\",\n\t\t\t\"key\": \"slice_of_life\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Приключения\",\n\t\t\t\"key\": \"adventure\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"state\": \"FINISHED\",\n\t\"authors\": [],\n\t\"largeCoverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg\",\n\t\"description\": \"Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \\n<br>Начало истории читайте в \\\"Воспоминаниях Эманон\\\". \\n<div class=\\\"clearfix\\\"></div>\",\n\t\"chapters\": [],\n\t\"source\": \"READMANGA_RU\"\n}\n"
  },
  {
    "path": "app/src/androidTest/assets/manga/first_chapters.json",
    "content": "{\n\t\"id\": -2096681732556647985,\n\t\"title\": \"Странствия Эманон\",\n\t\"altTitles\": [],\n\t\"url\": \"/stranstviia_emanon\",\n\t\"publicUrl\": \"https://readmanga.io/stranstviia_emanon\",\n\t\"rating\": 0.9400894,\n\t\"isNsfw\": true,\n\t\"coverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg\",\n\t\"tags\": [\n\t\t{\n\t\t\t\"title\": \"Сверхъестественное\",\n\t\t\t\"key\": \"supernatural\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Сэйнэн\",\n\t\t\t\"key\": \"seinen\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Повседневность\",\n\t\t\t\"key\": \"slice_of_life\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Приключения\",\n\t\t\t\"key\": \"adventure\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"state\": \"FINISHED\",\n\t\"authors\": [],\n\t\"largeCoverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg\",\n\t\"description\": \"Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \\n<br>Начало истории читайте в \\\"Воспоминаниях Эманон\\\". \\n<div class=\\\"clearfix\\\"></div>\",\n\t\"chapters\": [\n\t\t{\n\t\t\t\"id\": 3552943969433540704,\n\t\t\t\"title\": \"1 - 1\",\n\t\t\t\"number\": 1,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/1\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540705,\n\t\t\t\"title\": \"1 - 2\",\n\t\t\t\"number\": 2,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/2\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540706,\n\t\t\t\"title\": \"1 - 3\",\n\t\t\t\"number\": 3,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/3\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540707,\n\t\t\t\"title\": \"1 - 4\",\n\t\t\t\"number\": 4,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/4\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540708,\n\t\t\t\"title\": \"1 - 5\",\n\t\t\t\"number\": 5,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/5\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541665,\n\t\t\t\"title\": \"2 - 1\",\n\t\t\t\"number\": 6,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/1\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1415570400000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541666,\n\t\t\t\"title\": \"2 - 2\",\n\t\t\t\"number\": 7,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/2\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1419976800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541667,\n\t\t\t\"title\": \"2 - 3\",\n\t\t\t\"number\": 8,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/3\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1427922000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541668,\n\t\t\t\"title\": \"2 - 4\",\n\t\t\t\"number\": 9,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/4\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1436907600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541669,\n\t\t\t\"title\": \"2 - 5\",\n\t\t\t\"number\": 10,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/5\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1446674400000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541670,\n\t\t\t\"title\": \"2 - 6\",\n\t\t\t\"number\": 11,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/6\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1451512800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"source\": \"READMANGA_RU\"\n}\n"
  },
  {
    "path": "app/src/androidTest/assets/manga/full.json",
    "content": "{\n\t\"id\": -2096681732556647985,\n\t\"title\": \"Странствия Эманон\",\n\t\"altTitles\": [],\n\t\"url\": \"/stranstviia_emanon\",\n\t\"publicUrl\": \"https://readmanga.io/stranstviia_emanon\",\n\t\"rating\": 0.9400894,\n\t\"isNsfw\": true,\n\t\"coverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg\",\n\t\"tags\": [\n\t\t{\n\t\t\t\"title\": \"Сверхъестественное\",\n\t\t\t\"key\": \"supernatural\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Сэйнэн\",\n\t\t\t\"key\": \"seinen\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Повседневность\",\n\t\t\t\"key\": \"slice_of_life\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Приключения\",\n\t\t\t\"key\": \"adventure\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"state\": \"FINISHED\",\n\t\"authors\": [],\n\t\"largeCoverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg\",\n\t\"description\": \"Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \\n<br>Начало истории читайте в \\\"Воспоминаниях Эманон\\\". \\n<div class=\\\"clearfix\\\"></div>\",\n\t\"chapters\": [\n\t\t{\n\t\t\t\"id\": 3552943969433540704,\n\t\t\t\"title\": \"1 - 1\",\n\t\t\t\"number\": 1,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/1\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540705,\n\t\t\t\"title\": \"1 - 2\",\n\t\t\t\"number\": 2,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/2\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540706,\n\t\t\t\"title\": \"1 - 3\",\n\t\t\t\"number\": 3,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/3\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540707,\n\t\t\t\"title\": \"1 - 4\",\n\t\t\t\"number\": 4,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/4\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540708,\n\t\t\t\"title\": \"1 - 5\",\n\t\t\t\"number\": 5,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/5\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541665,\n\t\t\t\"title\": \"2 - 1\",\n\t\t\t\"number\": 6,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/1\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1415570400000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541666,\n\t\t\t\"title\": \"2 - 2\",\n\t\t\t\"number\": 7,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/2\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1419976800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541667,\n\t\t\t\"title\": \"2 - 3\",\n\t\t\t\"number\": 8,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/3\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1427922000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541668,\n\t\t\t\"title\": \"2 - 4\",\n\t\t\t\"number\": 9,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/4\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1436907600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541669,\n\t\t\t\"title\": \"2 - 5\",\n\t\t\t\"number\": 10,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/5\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1446674400000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541670,\n\t\t\t\"title\": \"2 - 6\",\n\t\t\t\"number\": 11,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/6\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1451512800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433542626,\n\t\t\t\"title\": \"3 - 1\",\n\t\t\t\"number\": 12,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/1\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1461618000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433542627,\n\t\t\t\"title\": \"3 - 2\",\n\t\t\t\"number\": 13,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/2\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1461618000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433542628,\n\t\t\t\"title\": \"3 - 3\",\n\t\t\t\"number\": 14,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/3\",\n\t\t\t\"scanlator\": \"\",\n\t\t\t\"uploadDate\": 1465851600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"source\": \"READMANGA_RU\"\n}\n"
  },
  {
    "path": "app/src/androidTest/assets/manga/header.json",
    "content": "{\n\t\"id\": -2096681732556647985,\n\t\"title\": \"Странствия Эманон\",\n\t\"altTitles\": [],\n\t\"url\": \"/stranstviia_emanon\",\n\t\"publicUrl\": \"https://readmanga.io/stranstviia_emanon\",\n\t\"rating\": 0.9400894,\n\t\"isNsfw\": true,\n\t\"coverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg\",\n\t\"tags\": [\n\t\t{\n\t\t\t\"title\": \"Сверхъестественное\",\n\t\t\t\"key\": \"supernatural\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Сэйнэн\",\n\t\t\t\"key\": \"seinen\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Повседневность\",\n\t\t\t\"key\": \"slice_of_life\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Приключения\",\n\t\t\t\"key\": \"adventure\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"state\": \"FINISHED\",\n\t\"authors\": [],\n\t\"largeCoverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg\",\n\t\"description\": null,\n\t\"source\": \"READMANGA_RU\"\n}\n"
  },
  {
    "path": "app/src/androidTest/assets/manga/without_middle_chapter.json",
    "content": "{\n\t\"id\": -2096681732556647985,\n\t\"title\": \"Странствия Эманон\",\n\t\"altTitles\": [],\n\t\"url\": \"/stranstviia_emanon\",\n\t\"publicUrl\": \"https://readmanga.io/stranstviia_emanon\",\n\t\"rating\": 0.9400894,\n\t\"isNsfw\": true,\n\t\"coverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg\",\n\t\"tags\": [\n\t\t{\n\t\t\t\"title\": \"Сверхъестественное\",\n\t\t\t\"key\": \"supernatural\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Сэйнэн\",\n\t\t\t\"key\": \"seinen\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Повседневность\",\n\t\t\t\"key\": \"slice_of_life\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Приключения\",\n\t\t\t\"key\": \"adventure\",\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"state\": \"FINISHED\",\n\t\"authors\": [],\n\t\"largeCoverUrl\": \"https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg\",\n\t\"description\": \"Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \\n<br>Начало истории читайте в \\\"Воспоминаниях Эманон\\\". \\n<div class=\\\"clearfix\\\"></div>\",\n\t\"chapters\": [\n\t\t{\n\t\t\t\"id\": 3552943969433540704,\n\t\t\t\"title\": \"1 - 1\",\n\t\t\t\"number\": 1,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/1\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540705,\n\t\t\t\"title\": \"1 - 2\",\n\t\t\t\"number\": 2,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/2\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540706,\n\t\t\t\"title\": \"1 - 3\",\n\t\t\t\"number\": 3,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/3\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540707,\n\t\t\t\"title\": \"1 - 4\",\n\t\t\t\"number\": 4,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/4\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433540708,\n\t\t\t\"title\": \"1 - 5\",\n\t\t\t\"number\": 5,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol1/5\",\n\t\t\t\"scanlator\": \"Sad-Robot\",\n\t\t\t\"uploadDate\": 1342731600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541666,\n\t\t\t\"title\": \"2 - 2\",\n\t\t\t\"number\": 7,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/2\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1419976800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541667,\n\t\t\t\"title\": \"2 - 3\",\n\t\t\t\"number\": 8,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/3\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1427922000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541668,\n\t\t\t\"title\": \"2 - 4\",\n\t\t\t\"number\": 9,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/4\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1436907600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541669,\n\t\t\t\"title\": \"2 - 5\",\n\t\t\t\"number\": 10,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/5\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1446674400000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433541670,\n\t\t\t\"title\": \"2 - 6\",\n\t\t\t\"number\": 11,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol2/6\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1451512800000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433542626,\n\t\t\t\"title\": \"3 - 1\",\n\t\t\t\"number\": 12,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/1\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1461618000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433542627,\n\t\t\t\"title\": \"3 - 2\",\n\t\t\t\"number\": 13,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/2\",\n\t\t\t\"scanlator\": \"Sup!\",\n\t\t\t\"uploadDate\": 1461618000000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t},\n\t\t{\n\t\t\t\"id\": 3552943969433542628,\n\t\t\t\"title\": \"3 - 3\",\n\t\t\t\"number\": 14,\n\t\t\t\"volume\": 0,\n\t\t\t\"url\": \"/stranstviia_emanon/vol3/3\",\n\t\t\t\"scanlator\": \"\",\n\t\t\t\"uploadDate\": 1465851600000,\n\t\t\t\"source\": \"READMANGA_RU\"\n\t\t}\n\t],\n\t\"source\": \"READMANGA_RU\"\n}\n"
  },
  {
    "path": "app/src/androidTest/kotlin/org/koitharu/kotatsu/HiltTestRunner.kt",
    "content": "package org.koitharu.kotatsu\n\nimport android.app.Application\nimport android.content.Context\nimport androidx.test.runner.AndroidJUnitRunner\nimport dagger.hilt.android.testing.HiltTestApplication\n\nclass HiltTestRunner : AndroidJUnitRunner() {\n\n    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {\n        return super.newApplication(cl, HiltTestApplication::class.java.name, context)\n    }\n}\n"
  },
  {
    "path": "app/src/androidTest/kotlin/org/koitharu/kotatsu/Instrumentation.kt",
    "content": "package org.koitharu.kotatsu\n\nimport android.app.Instrumentation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\nsuspend fun Instrumentation.awaitForIdle() = suspendCoroutine<Unit> { cont ->\n\twaitForIdle { cont.resume(Unit) }\n}\n"
  },
  {
    "path": "app/src/androidTest/kotlin/org/koitharu/kotatsu/SampleData.kt",
    "content": "package org.koitharu.kotatsu\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport com.squareup.moshi.FromJson\nimport com.squareup.moshi.JsonAdapter\nimport com.squareup.moshi.JsonReader\nimport com.squareup.moshi.JsonWriter\nimport com.squareup.moshi.Moshi\nimport com.squareup.moshi.ToJson\nimport com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory\nimport okio.buffer\nimport okio.source\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport java.time.Instant\nimport java.util.Date\nimport kotlin.reflect.KClass\n\nobject SampleData {\n\n\tprivate val moshi = Moshi.Builder()\n\t\t.add(DateAdapter())\n\t\t.add(InstantAdapter())\n\t\t.add(MangaSourceAdapter())\n\t\t.add(KotlinJsonAdapterFactory())\n\t\t.build()\n\n\tval manga: Manga = loadAsset(\"manga/header.json\", Manga::class)\n\n\tval mangaDetails: Manga = loadAsset(\"manga/full.json\", Manga::class)\n\n\tval tag = mangaDetails.tags.elementAt(2)\n\n\tval chapter = checkNotNull(mangaDetails.chapters)[2]\n\n\tval favouriteCategory: FavouriteCategory = loadAsset(\"categories/simple.json\", FavouriteCategory::class)\n\n\tfun <T : Any> loadAsset(name: String, cls: KClass<T>): T {\n\t\tval assets = InstrumentationRegistry.getInstrumentation().context.assets\n\t\treturn assets.open(name).use {\n\t\t\tmoshi.adapter(cls.java).fromJson(it.source().buffer())\n\t\t} ?: throw RuntimeException(\"Cannot read asset from json \\\"$name\\\"\")\n\t}\n\n\tprivate class DateAdapter : JsonAdapter<Date>() {\n\n\t\t@FromJson\n\t\toverride fun fromJson(reader: JsonReader): Date? {\n\t\t\tval ms = reader.nextLong()\n\t\t\treturn if (ms == 0L) {\n\t\t\t\tnull\n\t\t\t} else {\n\t\t\t\tDate(ms)\n\t\t\t}\n\t\t}\n\n\t\t@ToJson\n\t\toverride fun toJson(writer: JsonWriter, value: Date?) {\n\t\t\twriter.value(value?.time ?: 0L)\n\t\t}\n\t}\n\n\tprivate class MangaSourceAdapter : JsonAdapter<MangaSource>() {\n\n\t\t@FromJson\n\t\toverride fun fromJson(reader: JsonReader): MangaSource? {\n\t\t\tval name = reader.nextString() ?: return null\n\t\t\treturn MangaSource(name)\n\t\t}\n\n\t\t@ToJson\n\t\toverride fun toJson(writer: JsonWriter, value: MangaSource?) {\n\t\t\twriter.value(value?.name)\n\t\t}\n\t}\n\n\tprivate class InstantAdapter : JsonAdapter<Instant>() {\n\n\t\t@FromJson\n\t\toverride fun fromJson(reader: JsonReader): Instant? {\n\t\t\tval ms = reader.nextLong()\n\t\t\treturn if (ms == 0L) {\n\t\t\t\tnull\n\t\t\t} else {\n\t\t\t\tInstant.ofEpochMilli(ms)\n\t\t\t}\n\t\t}\n\n\t\t@ToJson\n\t\toverride fun toJson(writer: JsonWriter, value: Instant?) {\n\t\t\twriter.value(value?.toEpochMilli() ?: 0L)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/androidTest/kotlin/org/koitharu/kotatsu/core/db/MangaDatabaseTest.kt",
    "content": "package org.koitharu.kotatsu.core.db\n\nimport androidx.room.testing.MigrationTestHelper\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Assert.assertEquals\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n@RunWith(AndroidJUnit4::class)\nclass MangaDatabaseTest {\n\n\t@get:Rule\n\tval helper: MigrationTestHelper = MigrationTestHelper(\n\t\tInstrumentationRegistry.getInstrumentation(),\n\t\tMangaDatabase::class.java,\n\t)\n\n\tprivate val migrations = getDatabaseMigrations(InstrumentationRegistry.getInstrumentation().targetContext)\n\n\t@Test\n\tfun versions() {\n\t\tassertEquals(1, migrations.first().startVersion)\n\t\trepeat(migrations.size) { i ->\n\t\t\tassertEquals(i + 1, migrations[i].startVersion)\n\t\t\tassertEquals(i + 2, migrations[i].endVersion)\n\t\t}\n\t\tassertEquals(DATABASE_VERSION, migrations.last().endVersion)\n\t}\n\n\t@Test\n\tfun migrateAll() {\n\t\thelper.createDatabase(TEST_DB, 1).close()\n\t\tfor (migration in migrations) {\n\t\t\thelper.runMigrationsAndValidate(\n\t\t\t\tTEST_DB,\n\t\t\t\tmigration.endVersion,\n\t\t\t\ttrue,\n\t\t\t\tmigration,\n\t\t\t).close()\n\t\t}\n\t}\n\n\t@Test\n\tfun prePopulate() {\n\t\tval resources = InstrumentationRegistry.getInstrumentation().targetContext.resources\n\t\thelper.createDatabase(TEST_DB, DATABASE_VERSION).use {\n\t\t\tDatabasePrePopulateCallback(resources).onCreate(it)\n\t\t}\n\t}\n\n\tprivate companion object {\n\n\t\tconst val TEST_DB = \"test-db\"\n\t}\n}\n"
  },
  {
    "path": "app/src/androidTest/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManagerTest.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport android.content.pm.ShortcutInfo\nimport android.content.pm.ShortcutManager\nimport android.os.Build\nimport androidx.core.content.getSystemService\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport dagger.hilt.android.testing.HiltAndroidRule\nimport dagger.hilt.android.testing.HiltAndroidTest\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertTrue\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.koitharu.kotatsu.SampleData\nimport org.koitharu.kotatsu.awaitForIdle\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport javax.inject.Inject\n\n@HiltAndroidTest\n@RunWith(AndroidJUnit4::class)\nclass AppShortcutManagerTest {\n\n\t@get:Rule\n\tvar hiltRule = HiltAndroidRule(this)\n\n\t@Inject\n\tlateinit var historyRepository: HistoryRepository\n\n\t@Inject\n\tlateinit var appShortcutManager: AppShortcutManager\n\n\t@Inject\n\tlateinit var database: MangaDatabase\n\n\t@Before\n\tfun setUp() {\n\t\thiltRule.inject()\n\t\tdatabase.clearAllTables()\n\t}\n\n\t@Test\n\tfun testUpdateShortcuts() = runTest {\n\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {\n\t\t\treturn@runTest\n\t\t}\n\t\tdatabase.invalidationTracker.addObserver(appShortcutManager)\n\t\tawaitUpdate()\n\t\tassertTrue(getShortcuts().isEmpty())\n\t\thistoryRepository.addOrUpdate(\n\t\t\tmanga = SampleData.manga,\n\t\t\tchapterId = SampleData.chapter.id,\n\t\t\tpage = 4,\n\t\t\tscroll = 2,\n\t\t\tpercent = 0.3f,\n\t\t\tforce = false,\n\t\t)\n\t\tawaitUpdate()\n\n\t\tval shortcuts = getShortcuts()\n\t\tassertEquals(1, shortcuts.size)\n\t}\n\n\tprivate fun getShortcuts(): List<ShortcutInfo> {\n\t\tval context = InstrumentationRegistry.getInstrumentation().targetContext\n\t\tval manager = checkNotNull(context.getSystemService<ShortcutManager>())\n\t\treturn manager.dynamicShortcuts.filterNot { it.id == \"com.squareup.leakcanary.dynamic_shortcut\" }\n\t}\n\n\tprivate suspend fun awaitUpdate() {\n\t\tval instrumentation = InstrumentationRegistry.getInstrumentation()\n\t\tinstrumentation.awaitForIdle()\n\t\tappShortcutManager.await()\n\t}\n}\n"
  },
  {
    "path": "app/src/androidTest/kotlin/org/koitharu/kotatsu/settings/backup/AppBackupAgentTest.kt",
    "content": "package org.koitharu.kotatsu.settings.backup\n\nimport android.content.res.AssetManager\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport dagger.hilt.android.testing.HiltAndroidRule\nimport dagger.hilt.android.testing.HiltAndroidTest\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertNull\nimport org.junit.Assert.assertTrue\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.koitharu.kotatsu.SampleData\nimport org.koitharu.kotatsu.backups.data.BackupRepository\nimport org.koitharu.kotatsu.backups.domain.AppBackupAgent\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport java.io.File\nimport javax.inject.Inject\n\n@HiltAndroidTest\n@RunWith(AndroidJUnit4::class)\nclass AppBackupAgentTest {\n\n\t@get:Rule\n\tvar hiltRule = HiltAndroidRule(this)\n\n\t@Inject\n\tlateinit var historyRepository: HistoryRepository\n\n\t@Inject\n\tlateinit var favouritesRepository: FavouritesRepository\n\n\t@Inject\n\tlateinit var backupRepository: BackupRepository\n\n\t@Inject\n\tlateinit var database: MangaDatabase\n\n\t@Before\n\tfun setUp() {\n\t\thiltRule.inject()\n\t\tdatabase.clearAllTables()\n\t}\n\n\t@Test\n\tfun backupAndRestore() = runTest {\n\t\tval category = favouritesRepository.createCategory(\n\t\t\ttitle = SampleData.favouriteCategory.title,\n\t\t\tsortOrder = SampleData.favouriteCategory.order,\n\t\t\tisTrackerEnabled = SampleData.favouriteCategory.isTrackingEnabled,\n\t\t\tisVisibleOnShelf = SampleData.favouriteCategory.isVisibleInLibrary,\n\t\t)\n\t\tfavouritesRepository.addToCategory(categoryId = category.id, mangas = listOf(SampleData.manga))\n\t\thistoryRepository.addOrUpdate(\n\t\t\tmanga = SampleData.mangaDetails,\n\t\t\tchapterId = SampleData.mangaDetails.chapters!![2].id,\n\t\t\tpage = 3,\n\t\t\tscroll = 40,\n\t\t\tpercent = 0.2f,\n\t\t\tforce = false,\n\t\t)\n\t\tval history = checkNotNull(historyRepository.getOne(SampleData.manga))\n\n\t\tval agent = AppBackupAgent()\n\t\tval backup = agent.createBackupFile(\n\t\t\tcontext = InstrumentationRegistry.getInstrumentation().targetContext,\n\t\t\trepository = backupRepository,\n\t\t)\n\n\t\tdatabase.clearAllTables()\n\t\tassertTrue(favouritesRepository.getAllManga().isEmpty())\n\t\tassertNull(historyRepository.getLastOrNull())\n\n\t\tbackup.inputStream().use {\n\t\t\tagent.restoreBackupFile(it.fd, backup.length(), backupRepository)\n\t\t}\n\n\t\tassertEquals(category, favouritesRepository.getCategory(category.id))\n\t\tassertEquals(history, historyRepository.getOne(SampleData.manga))\n\t\tassertEquals(listOf(SampleData.manga), favouritesRepository.getManga(category.id))\n\n\t\tval allTags = database.getTagsDao().findTags(SampleData.tag.source.name).toMangaTags()\n\t\tassertTrue(SampleData.tag in allTags)\n\t}\n\n\t@Test\n\tfun restoreOldBackup() {\n\t\tval agent = AppBackupAgent()\n\t\tval backup = File.createTempFile(\"backup_\", \".tmp\")\n\t\tInstrumentationRegistry.getInstrumentation().context.assets\n\t\t\t.open(\"kotatsu_test.bak\", AssetManager.ACCESS_STREAMING)\n\t\t\t.use { input ->\n\t\t\t\tbackup.outputStream().use { output ->\n\t\t\t\t\tinput.copyTo(output)\n\t\t\t\t}\n\t\t\t}\n\t\tbackup.inputStream().use {\n\t\t\tagent.restoreBackupFile(it.fd, backup.length(), backupRepository)\n\t\t}\n\t\trunTest {\n\t\t\tassertEquals(6, historyRepository.observeAll().first().size)\n\t\t\tassertEquals(2, favouritesRepository.observeCategories().first().size)\n\t\t\tassertEquals(15, favouritesRepository.getAllManga().size)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/debug/kotlin/org/koitharu/kotatsu/KotatsuApp.kt",
    "content": "package org.koitharu.kotatsu\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.os.Build\nimport android.os.StrictMode\nimport androidx.core.content.edit\nimport androidx.fragment.app.strictmode.FragmentStrictMode\nimport leakcanary.LeakCanary\nimport org.koitharu.kotatsu.core.BaseApp\n\nclass KotatsuApp : BaseApp() {\n\n\tvar isLeakCanaryEnabled: Boolean\n\t\tget() = getDebugPreferences(this).getBoolean(KEY_LEAK_CANARY, true)\n\t\tset(value) {\n\t\t\tgetDebugPreferences(this).edit { putBoolean(KEY_LEAK_CANARY, value) }\n\t\t\tconfigureLeakCanary()\n\t\t}\n\n\toverride fun attachBaseContext(base: Context) {\n\t\tsuper.attachBaseContext(base)\n\t\tenableStrictMode()\n\t\tconfigureLeakCanary()\n\t}\n\n\tprivate fun configureLeakCanary() {\n\t\tLeakCanary.config = LeakCanary.config.copy(\n\t\t\tdumpHeap = isLeakCanaryEnabled,\n\t\t)\n\t}\n\n\tprivate fun enableStrictMode() {\n\t\tval notifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n\t\t\tStrictModeNotifier(this)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t\tStrictMode.setThreadPolicy(\n\t\t\tStrictMode.ThreadPolicy.Builder().apply {\n\t\t\t\tdetectNetwork()\n\t\t\t\tdetectDiskWrites()\n\t\t\t\tdetectCustomSlowCalls()\n\t\t\t\tdetectResourceMismatches()\n\t\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) detectUnbufferedIo()\n\t\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) detectExplicitGc()\n\t\t\t\tpenaltyLog()\n\t\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {\n\t\t\t\t\tpenaltyListener(notifier.executor, notifier)\n\t\t\t\t}\n\t\t\t}.build(),\n\t\t)\n\t\tStrictMode.setVmPolicy(\n\t\t\tStrictMode.VmPolicy.Builder().apply {\n\t\t\t\tdetectActivityLeaks()\n\t\t\t\tdetectLeakedSqlLiteObjects()\n\t\t\t\tdetectLeakedClosableObjects()\n\t\t\t\tdetectLeakedRegistrationObjects()\n\t\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\t\t\tdetectContentUriWithoutPermission()\n\t\t\t\t}\n\t\t\t\tdetectFileUriExposure()\n\t\t\t\tpenaltyLog()\n\t\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {\n\t\t\t\t\tpenaltyListener(notifier.executor, notifier)\n\t\t\t\t}\n\t\t\t}.build(),\n\t\t)\n\t\tFragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder().apply {\n\t\t\tdetectWrongFragmentContainer()\n\t\t\tdetectFragmentTagUsage()\n\t\t\tdetectRetainInstanceUsage()\n\t\t\tdetectSetUserVisibleHint()\n\t\t\tdetectWrongNestedHierarchy()\n\t\t\tdetectFragmentReuse()\n\t\t\tpenaltyLog()\n\t\t\tif (notifier != null) {\n\t\t\t\tpenaltyListener(notifier)\n\t\t\t}\n\t\t}.build()\n\t}\n\n\tprivate companion object {\n\n\t\tconst val PREFS_DEBUG = \"_debug\"\n\t\tconst val KEY_LEAK_CANARY = \"leak_canary\"\n\n\t\tfun getDebugPreferences(context: Context): SharedPreferences =\n\t\t\tcontext.getSharedPreferences(PREFS_DEBUG, MODE_PRIVATE)\n\t}\n}\n"
  },
  {
    "path": "app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt",
    "content": "package org.koitharu.kotatsu\n\nimport android.app.Notification\nimport android.app.Notification.BigTextStyle\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.content.Context\nimport android.os.Build\nimport android.os.StrictMode\nimport android.os.strictmode.Violation\nimport androidx.annotation.RequiresApi\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.content.getSystemService\nimport androidx.fragment.app.strictmode.FragmentStrictMode\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.asExecutor\nimport org.koitharu.kotatsu.core.util.ShareHelper\nimport kotlin.math.absoluteValue\nimport androidx.fragment.app.strictmode.Violation as FragmentViolation\n\n@RequiresApi(Build.VERSION_CODES.P)\nclass StrictModeNotifier(\n\tprivate val context: Context,\n) : StrictMode.OnVmViolationListener, StrictMode.OnThreadViolationListener, FragmentStrictMode.OnViolationListener {\n\n\tval executor = Dispatchers.Default.asExecutor()\n\n\tprivate val notificationManager by lazy {\n\t\tval nm = checkNotNull(context.getSystemService<NotificationManager>())\n\t\tval channel = NotificationChannel(\n\t\t\tCHANNEL_ID,\n\t\t\tcontext.getString(R.string.strict_mode),\n\t\t\tNotificationManager.IMPORTANCE_LOW,\n\t\t)\n\t\tnm.createNotificationChannel(channel)\n\t\tnm\n\t}\n\n\toverride fun onVmViolation(v: Violation) = showNotification(v)\n\n\toverride fun onThreadViolation(v: Violation) = showNotification(v)\n\n\toverride fun onViolation(violation: FragmentViolation) = showNotification(violation)\n\n\tprivate fun showNotification(violation: Throwable) = Notification.Builder(context, CHANNEL_ID)\n\t\t.setSmallIcon(R.drawable.ic_bug)\n\t\t.setContentTitle(context.getString(R.string.strict_mode))\n\t\t.setContentText(violation.message)\n\t\t.setStyle(\n\t\t\tBigTextStyle()\n\t\t\t\t.setBigContentTitle(context.getString(R.string.strict_mode))\n\t\t\t\t.setSummaryText(violation.message)\n\t\t\t\t.bigText(violation.stackTraceToString()),\n\t\t).setShowWhen(true)\n\t\t.setContentIntent(\n\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\tcontext,\n\t\t\t\tviolation.hashCode(),\n\t\t\t\tShareHelper(context).getShareTextIntent(violation.stackTraceToString()),\n\t\t\t\t0,\n\t\t\t\tfalse,\n\t\t\t),\n\t\t)\n\t\t.setAutoCancel(true)\n\t\t.setGroup(CHANNEL_ID)\n\t\t.build()\n\t\t.let { notificationManager.notify(CHANNEL_ID, violation.hashCode().absoluteValue, it) }\n\n\tprivate companion object {\n\n\t\tconst val CHANNEL_ID = \"strict_mode\"\n\t}\n}\n"
  },
  {
    "path": "app/src/debug/kotlin/org/koitharu/kotatsu/core/network/CurlLoggingInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport android.util.Log\nimport okhttp3.Interceptor\nimport okhttp3.Request\nimport okhttp3.Response\nimport okio.Buffer\nimport org.koitharu.kotatsu.core.network.CommonHeaders.ACCEPT_ENCODING\n\nclass CurlLoggingInterceptor(\n\tprivate val curlOptions: String? = null\n) : Interceptor {\n\n\tprivate val escapeRegex = Regex(\"([\\\\[\\\\]\\\"])\")\n\n\toverride fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request()).also {\n\t\tlogRequest(it.networkResponse?.request ?: it.request)\n\t}\n\n\tprivate fun logRequest(request: Request) {\n\t\tvar isCompressed = false\n\n\t\tval curlCmd = StringBuilder()\n\t\tcurlCmd.append(\"curl\")\n\t\tif (curlOptions != null) {\n\t\t\tcurlCmd.append(' ').append(curlOptions)\n\t\t}\n\t\tcurlCmd.append(\" -X \").append(request.method)\n\n\t\tfor ((name, value) in request.headers) {\n\t\t\tif (name.equals(ACCEPT_ENCODING, ignoreCase = true) && value.equals(\"gzip\", ignoreCase = true)) {\n\t\t\t\tisCompressed = true\n\t\t\t}\n\t\t\tcurlCmd.append(\" -H \\\"\").append(name).append(\": \").append(value.escape()).append('\\\"')\n\t\t}\n\n\t\tval body = request.body\n\t\tif (body != null) {\n\t\t\tval buffer = Buffer()\n\t\t\tbody.writeTo(buffer)\n\t\t\tval charset = body.contentType()?.charset() ?: Charsets.UTF_8\n\t\t\tcurlCmd.append(\" --data-raw '\")\n\t\t\t\t.append(buffer.readString(charset).replace(\"\\n\", \"\\\\n\"))\n\t\t\t\t.append(\"'\")\n\t\t}\n\t\tif (isCompressed) {\n\t\t\tcurlCmd.append(\" --compressed\")\n\t\t}\n\t\tcurlCmd.append(\" \\\"\").append(request.url.toString().escape()).append('\"')\n\n\t\tlog(\"---cURL (\" + request.url + \")\")\n\t\tlog(curlCmd.toString())\n\t}\n\n\tprivate fun String.escape() = replace(escapeRegex) { match ->\n\t\t\"\\\\\" + match.value\n\t}\n\n\tprivate fun log(msg: String) {\n\t\tLog.d(\"CURL\", msg)\n\t}\n}\n"
  },
  {
    "path": "app/src/debug/kotlin/org/koitharu/kotatsu/core/parser/TestMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.model.TestMangaSource\nimport org.koitharu.kotatsu.parsers.MangaLoaderContext\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterOptions\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport java.util.EnumSet\n\n/*\n This class is for parser development and testing purposes\n You can open it in the app via Settings -> Debug\n */\nclass TestMangaRepository(\n\t@Suppress(\"unused\") private val loaderContext: MangaLoaderContext,\n\tcache: MemoryContentCache\n) : CachingMangaRepository(cache) {\n\n\toverride val source = TestMangaSource\n\n\toverride val sortOrders: Set<SortOrder> = EnumSet.allOf(SortOrder::class.java)\n\n\toverride var defaultSortOrder: SortOrder\n\t\tget() = sortOrders.first()\n\t\tset(value) = Unit\n\n\toverride val filterCapabilities = MangaListFilterCapabilities()\n\n\toverride suspend fun getFilterOptions() = MangaListFilterOptions()\n\n\toverride suspend fun getList(\n\t\toffset: Int,\n\t\torder: SortOrder?,\n\t\tfilter: MangaListFilter?\n\t): List<Manga> = TODO(\"Get manga list by filter\")\n\n\toverride suspend fun getDetailsImpl(\n\t\tmanga: Manga\n\t): Manga = TODO(\"Fetch manga details\")\n\n\toverride suspend fun getPagesImpl(\n\t\tchapter: MangaChapter\n\t): List<MangaPage> = TODO(\"Get pages for specific chapter\")\n\n\toverride suspend fun getPageUrl(\n\t\tpage: MangaPage\n\t): String = TODO(\"Return direct url of page image or page.url if it is already a direct url\")\n\n\toverride suspend fun getRelatedMangaImpl(\n\t\tseed: Manga\n\t): List<Manga> = TODO(\"Get list of related manga. This method is optional and parser library has a default implementation\")\n}\n"
  },
  {
    "path": "app/src/debug/kotlin/org/koitharu/kotatsu/core/ui/BaseService.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.content.Context\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.LifecycleService\nimport leakcanary.AppWatcher\n\nabstract class BaseService : LifecycleService() {\n\n\toverride fun attachBaseContext(newBase: Context) {\n\t\tsuper.attachBaseContext(ContextCompat.getContextForLanguage(newBase))\n\t}\n\n\toverride fun onDestroy() {\n\t\tsuper.onDestroy()\n\t\tAppWatcher.objectWatcher.watch(\n\t\t\twatchedObject = this,\n\t\t\tdescription = \"${javaClass.simpleName} service received Service#onDestroy() callback\",\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/debug/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.os.Looper\n\nfun Throwable.printStackTraceDebug() = printStackTrace()\n\nfun assertNotInMainThread() = check(Looper.myLooper() != Looper.getMainLooper()) {\n\t\"Calling this from the main thread is prohibited\"\n}\n"
  },
  {
    "path": "app/src/debug/kotlin/org/koitharu/kotatsu/settings/DebugSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.os.Bundle\nimport androidx.preference.Preference\nimport leakcanary.LeakCanary\nimport org.koitharu.kotatsu.KotatsuApp\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.TestMangaSource\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.settings.utils.SplitSwitchPreference\nimport org.koitharu.workinspector.WorkInspector\n\nclass DebugSettingsFragment : BasePreferenceFragment(R.string.debug), Preference.OnPreferenceChangeListener,\n\tPreference.OnPreferenceClickListener {\n\n\tprivate val application\n\t\tget() = requireContext().applicationContext as KotatsuApp\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_debug)\n\t\tfindPreference<SplitSwitchPreference>(KEY_LEAK_CANARY)?.let { pref ->\n\t\t\tpref.isChecked = application.isLeakCanaryEnabled\n\t\t\tpref.onPreferenceChangeListener = this\n\t\t\tpref.onContainerClickListener = this\n\t\t}\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tfindPreference<SplitSwitchPreference>(KEY_LEAK_CANARY)?.isChecked = application.isLeakCanaryEnabled\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {\n\t\tKEY_WORK_INSPECTOR -> {\n\t\t\tstartActivity(WorkInspector.getIntent(preference.context))\n\t\t\ttrue\n\t\t}\n\n\t\tKEY_TEST_PARSER -> {\n\t\t\trouter.openList(TestMangaSource, null, null)\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onPreferenceTreeClick(preference)\n\t}\n\n\toverride fun onPreferenceClick(preference: Preference): Boolean = when (preference.key) {\n\t\tKEY_LEAK_CANARY -> {\n\t\t\tstartActivity(LeakCanary.newLeakDisplayActivityIntent())\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onPreferenceTreeClick(preference)\n\t}\n\n\toverride fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean = when (preference.key) {\n\t\tKEY_LEAK_CANARY -> {\n\t\t\tapplication.isLeakCanaryEnabled = newValue as Boolean\n\t\t\ttrue\n\t\t}\n\n\t\telse -> false\n\t}\n\n\tprivate companion object {\n\n\t\tconst val KEY_LEAK_CANARY = \"leak_canary\"\n\t\tconst val KEY_WORK_INSPECTOR = \"work_inspector\"\n\t\tconst val KEY_TEST_PARSER = \"test_parser\"\n\t}\n}\n"
  },
  {
    "path": "app/src/debug/res/drawable/ic_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M20,8H17.19C16.74,7.2 16.12,6.5 15.37,6L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.05,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6C7.87,6.5 7.26,7.21 6.81,8H4V10H6.09C6.03,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.03,15.67 6.09,16H4V18H6.81C8.47,20.87 12.14,21.84 15,20.18C15.91,19.66 16.67,18.9 17.19,18H20V16H17.91C17.97,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.97,10.33 17.91,10H20V8M16,15A4,4 0 0,1 12,19A4,4 0 0,1 8,15V11A4,4 0 0,1 12,7A4,4 0 0,1 16,11V15M14,10V12H10V10H14M10,14H14V16H10V14Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/debug/res/drawable-anydpi-v24/ic_bug.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"#FFFFFF\">\n  <group android:scaleX=\"0.98150784\"\n      android:scaleY=\"0.98150784\"\n      android:translateX=\"0.22190611\"\n      android:translateY=\"-0.2688478\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z\"/>\n  </group>\n</vector>\n"
  },
  {
    "path": "app/src/debug/res/values/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\t<bool name=\"leak_canary_add_launcher_icon\" tools:node=\"replace\">false</bool>\n\t<bool name=\"wi_launcher_icon_enabled\" tools:node=\"replace\">false</bool>\n</resources>\n"
  },
  {
    "path": "app/src/debug/res/values/constants.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"account_type_sync\" translatable=\"false\">org.kotatsu.debug.sync</string>\n\t<string name=\"sync_authority_history\" translatable=\"false\">org.koitharu.kotatsu.debug.history</string>\n\t<string name=\"sync_authority_favourites\" translatable=\"false\">org.koitharu.kotatsu.debug.favourites</string>\n</resources>\n"
  },
  {
    "path": "app/src/debug/res/values/strings.xml",
    "content": "<resources>\n\t<string name=\"app_name\" translatable=\"false\">Kotatsu Dev</string>\n\t<string name=\"strict_mode\">Strict mode</string>\n</resources>\n"
  },
  {
    "path": "app/src/debug/res/xml/pref_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<org.koitharu.kotatsu.settings.utils.SplitSwitchPreference\n\t\tandroid:key=\"leak_canary\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"LeakCanary\" />\n\n\t<Preference\n\t\tandroid:key=\"work_inspector\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/wi_lib_name\" />\n\n\t<Preference\n\t\tandroid:key=\"test_parser\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/test_parser\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/debug/res/xml/pref_root_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<PreferenceScreen\n\t\tandroid:fragment=\"org.koitharu.kotatsu.settings.DebugSettingsFragment\"\n\t\tandroid:icon=\"@drawable/ic_debug\"\n\t\tandroid:key=\"debug\"\n\t\tandroid:title=\"@string/debug\" />\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\">\n\n\t<uses-permission android:name=\"android.permission.INTERNET\" />\n\t<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n\t<uses-permission\n\t\tandroid:name=\"android.permission.FOREGROUND_SERVICE\"\n\t\ttools:ignore=\"ForegroundServicesPolicy\" />\n\t<uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n\t<uses-permission android:name=\"android.permission.VIBRATE\" />\n\t<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n\t<uses-permission android:name=\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\" />\n\t<uses-permission android:name=\"android.permission.GET_ACCOUNTS\" />\n\t<uses-permission android:name=\"android.permission.MANAGE_ACCOUNTS\" />\n\t<uses-permission android:name=\"android.permission.AUTHENTICATE_ACCOUNTS\" />\n\t<uses-permission android:name=\"android.permission.USE_CREDENTIALS\" />\n\t<uses-permission android:name=\"android.permission.READ_SYNC_STATS\" />\n\t<uses-permission android:name=\"android.permission.READ_SYNC_SETTINGS\" />\n\t<uses-permission android:name=\"android.permission.WRITE_SYNC_SETTINGS\" />\n\t<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n\t<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_DATA_SYNC\" />\n\t<uses-permission\n\t\tandroid:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"\n\t\ttools:ignore=\"RequestInstallPackagesPolicy\" />\n\t<uses-permission android:name=\"android.permission.REQUEST_DELETE_PACKAGES\" />\n\t<uses-permission\n\t\tandroid:name=\"android.permission.QUERY_ALL_PACKAGES\"\n\t\ttools:ignore=\"PackageVisibilityPolicy,QueryAllPackagesPermission\" />\n\t<uses-permission\n\t\tandroid:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"\n\t\tandroid:maxSdkVersion=\"29\" />\n\t<uses-permission\n\t\tandroid:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"\n\t\ttools:ignore=\"AllFilesAccessPolicy,ScopedStorage\" />\n\n\t<queries>\n\t\t<intent>\n\t\t\t<action android:name=\"android.intent.action.PROCESS_TEXT\" />\n\t\t\t<data android:mimeType=\"text/plain\" />\n\t\t</intent>\n\t\t<intent>\n\t\t\t<action android:name=\"android.speech.action.RECOGNIZE_SPEECH\" />\n\t\t</intent>\n\t</queries>\n\n\t<application\n\t\tandroid:name=\"org.koitharu.kotatsu.KotatsuApp\"\n\t\tandroid:allowBackup=\"true\"\n\t\tandroid:backupAgent=\"org.koitharu.kotatsu.backups.domain.AppBackupAgent\"\n\t\tandroid:dataExtractionRules=\"@xml/backup_rules\"\n\t\tandroid:enableOnBackInvokedCallback=\"@bool/is_predictive_back_enabled\"\n\t\tandroid:extractNativeLibs=\"true\"\n\t\tandroid:fullBackupContent=\"@xml/backup_content\"\n\t\tandroid:fullBackupOnly=\"true\"\n\t\tandroid:hasFragileUserData=\"true\"\n\t\tandroid:restoreAnyVersion=\"true\"\n\t\tandroid:icon=\"@mipmap/ic_launcher\"\n\t\tandroid:label=\"@string/app_name\"\n\t\tandroid:largeHeap=\"true\"\n\t\tandroid:localeConfig=\"@xml/locales_config\"\n\t\tandroid:networkSecurityConfig=\"@xml/network_security_config\"\n\t\tandroid:requestLegacyExternalStorage=\"true\"\n\t\tandroid:roundIcon=\"@mipmap/ic_launcher_round\"\n\t\tandroid:supportsRtl=\"true\"\n\t\tandroid:theme=\"@style/Theme.Kotatsu\"\n\t\ttools:ignore=\"UnusedAttribute\">\n\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.main.ui.MainActivity\"\n\t\t\tandroid:exported=\"true\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />\n\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />\n\t\t\t</intent-filter>\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"android.app.default_searchable\"\n\t\t\t\tandroid:value=\"org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity\" />\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.details.ui.DetailsActivity\"\n\t\t\tandroid:exported=\"true\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"${applicationId}.action.VIEW_MANGA\" />\n\t\t\t</intent-filter>\n\t\t\t<intent-filter android:autoVerify=\"true\">\n\t\t\t\t<action android:name=\"android.intent.action.VIEW\" />\n\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t\t<category android:name=\"android.intent.category.BROWSABLE\" />\n\n\t\t\t\t<data android:scheme=\"http\" />\n\t\t\t\t<data android:scheme=\"https\" />\n\t\t\t\t<data android:host=\"kotatsu.app\" />\n\t\t\t\t<data android:path=\"/manga\" />\n\t\t\t</intent-filter>\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.VIEW\" />\n\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t\t<category android:name=\"android.intent.category.BROWSABLE\" />\n\n\t\t\t\t<data android:scheme=\"kotatsu\" />\n\t\t\t\t<data android:host=\"manga\" />\n\t\t\t\t<data android:host=\"kotatsu.app\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.reader.ui.ReaderActivity\"\n\t\t\tandroid:exported=\"true\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"${applicationId}.action.READ_MANGA\" />\n\t\t\t</intent-filter>\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"com.samsung.android.support.REMOTE_ACTION\" />\n\t\t\t</intent-filter>\n\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"com.samsung.android.support.REMOTE_ACTION\"\n\t\t\t\tandroid:resource=\"@xml/remote_action\" />\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.search.ui.MangaListActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/manga_list\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"${applicationId}.action.EXPLORE_MANGA\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.history.ui.HistoryActivity\"\n\t\t\tandroid:label=\"@string/history\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity\"\n\t\t\tandroid:label=\"@string/updates\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.favourites.ui.FavouritesActivity\"\n\t\t\tandroid:label=\"@string/favourites\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity\"\n\t\t\tandroid:label=\"@string/bookmarks\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity\"\n\t\t\tandroid:label=\"@string/suggestions\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity\"\n\t\t\tandroid:label=\"@string/related_manga\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.SettingsActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/settings\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.VIEW\" />\n\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t\t<category android:name=\"android.intent.category.BROWSABLE\" />\n\n\t\t\t\t<data android:scheme=\"kotatsu\" />\n\t\t\t\t<data android:host=\"about\" />\n\t\t\t\t<data android:host=\"sync-settings\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity\"\n\t\t\tandroid:label=\"@string/reader_actions\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity\"\n\t\t\tandroid:label=\"@string/local_manga_directories\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.browser.BrowserActivity\"\n\t\t\tandroid:configChanges=\"orientation|screenSize|screenLayout|keyboardHidden\"\n\t\t\tandroid:windowSoftInputMode=\"adjustResize\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity\"\n\t\t\tandroid:autoRemoveFromRecents=\"true\"\n\t\t\tandroid:configChanges=\"orientation|screenSize|screenLayout|keyboardHidden\"\n\t\t\tandroid:windowSoftInputMode=\"adjustResize\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity\"\n\t\t\tandroid:configChanges=\"orientation|screenSize|screenLayout|keyboardHidden\"\n\t\t\tandroid:windowSoftInputMode=\"adjustResize\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity\"\n\t\t\tandroid:label=\"@string/manage_categories\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.widget.shelf.ShelfWidgetConfigActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/manga_shelf\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.appwidget.action.APPWIDGET_CONFIGURE\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.widget.recent.RecentWidgetConfigActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/recent_manga\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.appwidget.action.APPWIDGET_CONFIGURE\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.search.ui.multi.SearchActivity\"\n\t\t\tandroid:label=\"@string/search\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.main.ui.protect.ProtectActivity\"\n\t\t\tandroid:noHistory=\"true\"\n\t\t\tandroid:windowSoftInputMode=\"adjustResize\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.protect.ProtectSetupActivity\"\n\t\t\tandroid:windowSoftInputMode=\"adjustResize\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.download.ui.list.DownloadsActivity\"\n\t\t\tandroid:label=\"@string/downloads\"\n\t\t\tandroid:launchMode=\"singleTop\" />\n\t\t<activity android:name=\"org.koitharu.kotatsu.image.ui.ImageActivity\" />\n\t\t<activity android:name=\"org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.override.OverrideConfigActivity\"\n\t\t\tandroid:label=\"@string/edit\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.sync.ui.SyncAuthActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/sync\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity\"\n\t\t\tandroid:label=\"@string/color_correction\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.scrobbling.common.ui.config.ScrobblerConfigActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/settings\"\n\t\t\tandroid:launchMode=\"singleTop\">\n\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.VIEW\" />\n\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t\t<category android:name=\"android.intent.category.BROWSABLE\" />\n\n\t\t\t\t<data android:scheme=\"kotatsu\" />\n\t\t\t\t<data android:host=\"shikimori-auth\" />\n\t\t\t\t<data android:host=\"anilist-auth\" />\n\t\t\t\t<data android:host=\"mal-auth\" />\n\t\t\t\t<data android:host=\"kitsu-auth\" />\n\t\t\t</intent-filter>\n\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity\"\n\t\t\tandroid:label=\"@string/sources_catalog\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.scrobbling.kitsu.ui.KitsuAuthActivity\"\n\t\t\tandroid:exported=\"false\"\n\t\t\tandroid:label=\"@string/kitsu\"\n\t\t\ttools:ignore=\"AppLinkUrlError\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.VIEW\" />\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t\t<data android:scheme=\"kotatsu+kitsu\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.stats.ui.StatsActivity\"\n\t\t\tandroid:label=\"@string/reading_stats\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.alternatives.ui.AlternativesActivity\"\n\t\t\tandroid:label=\"@string/alternatives\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.settings.about.AppUpdateActivity\"\n\t\t\tandroid:label=\"@string/app_update_available\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity\"\n\t\t\tandroid:label=\"@string/tracker_debug_info\" />\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.picker.ui.PageImagePickActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/pick_manga_page\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.GET_CONTENT\" />\n\n\t\t\t\t<category android:name=\"android.intent.category.OPENABLE\" />\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\n\t\t\t\t<data android:mimeType=\"image/*\" />\n\t\t\t</intent-filter>\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.PICK\" />\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t\t<data android:mimeType=\"image/*\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\"org.koitharu.kotatsu.scrobbling.discord.ui.DiscordAuthActivity\"\n\t\t\tandroid:label=\"@string/discord\" />\n\n\t\t<service\n\t\t\tandroid:name=\"androidx.work.impl.foreground.SystemForegroundService\"\n\t\t\tandroid:foregroundServiceType=\"dataSync\"\n\t\t\ttools:node=\"merge\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService\"\n\t\t\tandroid:foregroundServiceType=\"dataSync\"\n\t\t\tandroid:label=\"@string/local_manga_processing\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupService\"\n\t\t\tandroid:foregroundServiceType=\"dataSync\"\n\t\t\tandroid:label=\"@string/periodic_backups\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.alternatives.ui.AutoFixService\"\n\t\t\tandroid:foregroundServiceType=\"dataSync\"\n\t\t\tandroid:label=\"@string/fixing_manga\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.local.ui.LocalIndexUpdateService\"\n\t\t\tandroid:label=\"@string/local_manga_processing\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.backups.ui.backup.BackupService\"\n\t\t\tandroid:foregroundServiceType=\"dataSync\"\n\t\t\tandroid:label=\"@string/creating_backup\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.backups.ui.restore.RestoreService\"\n\t\t\tandroid:foregroundServiceType=\"dataSync\"\n\t\t\tandroid:label=\"@string/restoring_backup\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.local.ui.ImportService\"\n\t\t\tandroid:foregroundServiceType=\"dataSync\"\n\t\t\tandroid:label=\"@string/importing_manga\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.widget.shelf.ShelfWidgetService\"\n\t\t\tandroid:label=\"@string/manga_shelf\"\n\t\t\tandroid:permission=\"android.permission.BIND_REMOTEVIEWS\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.widget.recent.RecentWidgetService\"\n\t\t\tandroid:label=\"@string/recent_manga\"\n\t\t\tandroid:permission=\"android.permission.BIND_REMOTEVIEWS\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.sync.ui.SyncAuthenticatorService\"\n\t\t\tandroid:exported=\"true\"\n\t\t\ttools:ignore=\"ExportedService\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.accounts.AccountAuthenticator\" />\n\t\t\t</intent-filter>\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"android.accounts.AccountAuthenticator\"\n\t\t\t\tandroid:resource=\"@xml/authenticator_sync\" />\n\t\t</service>\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.sync.ui.favourites.FavouritesSyncService\"\n\t\t\tandroid:exported=\"false\"\n\t\t\tandroid:label=\"@string/favourites\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.content.SyncAdapter\" />\n\t\t\t</intent-filter>\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"android.content.SyncAdapter\"\n\t\t\t\tandroid:resource=\"@xml/sync_favourites\" />\n\t\t</service>\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.sync.ui.history.HistorySyncService\"\n\t\t\tandroid:exported=\"false\"\n\t\t\tandroid:label=\"@string/history\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.content.SyncAdapter\" />\n\t\t\t</intent-filter>\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"android.content.SyncAdapter\"\n\t\t\t\tandroid:resource=\"@xml/sync_history\" />\n\t\t</service>\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.details.service.MangaPrefetchService\"\n\t\t\tandroid:exported=\"false\"\n\t\t\tandroid:label=\"@string/prefetch_content\" />\n\t\t<service\n\t\t\tandroid:name=\"org.koitharu.kotatsu.browser.AdListUpdateService\"\n\t\t\tandroid:exported=\"false\" />\n\n\t\t<provider\n\t\t\tandroid:name=\"org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider\"\n\t\t\tandroid:authorities=\"${applicationId}.MangaSuggestionsProvider\"\n\t\t\tandroid:exported=\"false\" />\n\t\t<provider\n\t\t\tandroid:name=\"androidx.core.content.FileProvider\"\n\t\t\tandroid:authorities=\"${applicationId}.files\"\n\t\t\tandroid:exported=\"false\"\n\t\t\tandroid:grantUriPermissions=\"true\">\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"android.support.FILE_PROVIDER_PATHS\"\n\t\t\t\tandroid:resource=\"@xml/filepaths\" />\n\t\t</provider>\n\t\t<provider\n\t\t\tandroid:name=\"org.koitharu.kotatsu.sync.ui.favourites.FavouritesSyncProvider\"\n\t\t\tandroid:authorities=\"@string/sync_authority_favourites\"\n\t\t\tandroid:exported=\"false\"\n\t\t\tandroid:label=\"@string/favourites\"\n\t\t\tandroid:syncable=\"true\" />\n\t\t<provider\n\t\t\tandroid:name=\"org.koitharu.kotatsu.sync.ui.history.HistorySyncProvider\"\n\t\t\tandroid:authorities=\"@string/sync_authority_history\"\n\t\t\tandroid:exported=\"false\"\n\t\t\tandroid:label=\"@string/history\"\n\t\t\tandroid:syncable=\"true\" />\n\t\t<provider\n\t\t\tandroid:name=\"androidx.startup.InitializationProvider\"\n\t\t\tandroid:authorities=\"${applicationId}.androidx-startup\"\n\t\t\tandroid:exported=\"false\"\n\t\t\ttools:node=\"remove\">\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"androidx.work.WorkManagerInitializer\"\n\t\t\t\tandroid:value=\"androidx.startup\"\n\t\t\t\ttools:node=\"remove\" />\n\t\t</provider>\n\n\t\t<receiver\n\t\t\tandroid:name=\"org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler$DiscardReceiver\"\n\t\t\tandroid:exported=\"false\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"org.koitharu.kotatsu.CAPTCHA_DISCARD\" />\n\t\t\t</intent-filter>\n\t\t</receiver>\n\t\t<receiver\n\t\t\tandroid:name=\"org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/manga_shelf\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n\t\t\t</intent-filter>\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"android.appwidget.provider\"\n\t\t\t\tandroid:resource=\"@xml/widget_shelf\" />\n\t\t</receiver>\n\t\t<receiver\n\t\t\tandroid:name=\"org.koitharu.kotatsu.widget.recent.RecentWidgetProvider\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:label=\"@string/recent_manga\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n\t\t\t</intent-filter>\n\t\t\t<meta-data\n\t\t\t\tandroid:name=\"android.appwidget.provider\"\n\t\t\t\tandroid:resource=\"@xml/widget_recent\" />\n\t\t</receiver>\n\t\t<receiver\n\t\t\tandroid:name=\"org.koitharu.kotatsu.core.ErrorReporterReceiver\"\n\t\t\tandroid:exported=\"false\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"${applicationId}.action.REPORT_ERROR\" />\n\t\t\t</intent-filter>\n\t\t</receiver>\n\n\t\t<meta-data\n\t\t\tandroid:name=\"android.webkit.WebView.EnableSafeBrowsing\"\n\t\t\tandroid:value=\"false\" />\n\t\t<meta-data\n\t\t\tandroid:name=\"android.webkit.WebView.MetricsOptOut\"\n\t\t\tandroid:value=\"true\" />\n\t\t<meta-data\n\t\t\tandroid:name=\"com.samsung.android.icon_container.has_icon_container\"\n\t\t\tandroid:value=\"@bool/com_samsung_android_icon_container_has_icon_container\" />\n\n\t\t<activity-alias\n\t\t\tandroid:name=\"org.koitharu.kotatsu.details.ui.DetailsByLinkActivity\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:targetActivity=\"org.koitharu.kotatsu.details.ui.DetailsActivity\">\n\n\t\t\t<intent-filter android:autoVerify=\"false\">\n\t\t\t\t<action android:name=\"android.intent.action.VIEW\" />\n\n\t\t\t\t<category android:name=\"android.intent.category.DEFAULT\" />\n\t\t\t\t<category android:name=\"android.intent.category.BROWSABLE\" />\n\n\t\t\t\t<data android:scheme=\"http\" />\n\t\t\t\t<data android:scheme=\"https\" />\n\n\t\t\t\t<data android:host=\"bato.to\" />\n\t\t\t\t<data android:host=\"batocomic.com\" />\n\t\t\t\t<data android:host=\"batocomic.net\" />\n\t\t\t\t<data android:host=\"batocomic.org\" />\n\t\t\t\t<data android:host=\"batotoo.com\" />\n\t\t\t\t<data android:host=\"batotwo.com\" />\n\t\t\t\t<data android:host=\"battwo.com\" />\n\t\t\t\t<data android:host=\"comiko.net\" />\n\t\t\t\t<data android:host=\"comiko.org\" />\n\t\t\t\t<data android:host=\"mangatoto.com\" />\n\t\t\t\t<data android:host=\"mangatoto.net\" />\n\t\t\t\t<data android:host=\"mangatoto.org\" />\n\t\t\t\t<data android:host=\"readtoto.com\" />\n\t\t\t\t<data android:host=\"readtoto.net\" />\n\t\t\t\t<data android:host=\"readtoto.org\" />\n\t\t\t\t<data android:host=\"dto.to\" />\n\t\t\t\t<data android:host=\"hto.to\" />\n\t\t\t\t<data android:host=\"mto.to\" />\n\t\t\t\t<data android:host=\"wto.to\" />\n\t\t\t\t<data android:host=\"xbato.com\" />\n\t\t\t\t<data android:host=\"xbato.net\" />\n\t\t\t\t<data android:host=\"xbato.org\" />\n\t\t\t\t<data android:host=\"zbato.com\" />\n\t\t\t\t<data android:host=\"zbato.net\" />\n\t\t\t\t<data android:host=\"zbato.org\" />\n\t\t\t\t<data android:host=\"comick.io\" />\n\t\t\t\t<data android:host=\"comick.cc\" />\n\t\t\t\t<data android:host=\"e-hentai.org\" />\n\t\t\t\t<data android:host=\"exhentai.org\" />\n\t\t\t\t<data android:host=\"hitomi.la\" />\n\t\t\t\t<data android:host=\"imhentai.xxx\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"mangadex.org\" />\n\t\t\t\t<data android:host=\"mangafire.to\" />\n\t\t\t\t<data android:host=\"mangafire.to\" />\n\t\t\t\t<data android:host=\"mangafire.to\" />\n\t\t\t\t<data android:host=\"mangafire.to\" />\n\t\t\t\t<data android:host=\"mangafire.to\" />\n\t\t\t\t<data android:host=\"mangafire.to\" />\n\t\t\t\t<data android:host=\"mangafire.to\" />\n\t\t\t\t<data android:host=\"mangapark.net\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangaplus.shueisha.co.jp\" />\n\t\t\t\t<data android:host=\"mangareader.to\" />\n\t\t\t\t<data android:host=\"www.ninemanga.com\" />\n\t\t\t\t<data android:host=\"es.ninemanga.com\" />\n\t\t\t\t<data android:host=\"ru.ninemanga.com\" />\n\t\t\t\t<data android:host=\"de.ninemanga.com\" />\n\t\t\t\t<data android:host=\"br.ninemanga.com\" />\n\t\t\t\t<data android:host=\"it.ninemanga.com\" />\n\t\t\t\t<data android:host=\"fr.ninemanga.com\" />\n\t\t\t\t<data android:host=\"animeh.to\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"webtoons.com\" />\n\t\t\t\t<data android:host=\"papscan.com\" />\n\t\t\t\t<data android:host=\"komikzoid.id\" />\n\t\t\t\t<data android:host=\"neumanga.xyz\" />\n\t\t\t\t<data android:host=\"sektekomik.xyz\" />\n\t\t\t\t<data android:host=\"flixscans.net\" />\n\t\t\t\t<data android:host=\"mangastorm.org\" />\n\t\t\t\t<data android:host=\"teamoney.site\" />\n\t\t\t\t<data android:host=\"anibel.net\" />\n\t\t\t\t<data android:host=\"www.mangahaus.com\" />\n\t\t\t\t<data android:host=\"www.enlignemanga.com\" />\n\t\t\t\t<data android:host=\"www.frmanga.com\" />\n\t\t\t\t<data android:host=\"www.seinemanga.com\" />\n\t\t\t\t<data android:host=\"www.mangakoinu.com\" />\n\t\t\t\t<data android:host=\"oioivn.com\" />\n\t\t\t\t<data android:host=\"asuracomic.net\" />\n\t\t\t\t<data android:host=\"manhwafull.net\" />\n\t\t\t\t<data android:host=\"manga.clone-army.org\" />\n\t\t\t\t<data android:host=\"comixextra.com\" />\n\t\t\t\t<data android:host=\"dynasty-scans.com\" />\n\t\t\t\t<data android:host=\"flixscans.org\" />\n\t\t\t\t<data android:host=\"www.mgeko.cc\" />\n\t\t\t\t<data android:host=\"www.mgeko.com\" />\n\t\t\t\t<data android:host=\"www.mangageko.com\" />\n\t\t\t\t<data android:host=\"www.mangakawaii.io\" />\n\t\t\t\t<data android:host=\"www.mangatown.com\" />\n\t\t\t\t<data android:host=\"mangaowl.to\" />\n\t\t\t\t<data android:host=\"manhwa18.net\" />\n\t\t\t\t<data android:host=\"manhwas.men\" />\n\t\t\t\t<data android:host=\"po2scans.com\" />\n\t\t\t\t<data android:host=\"pururin.to\" />\n\t\t\t\t<data android:host=\"vymanga.net\" />\n\t\t\t\t<data android:host=\"templescanesp.net\" />\n\t\t\t\t<data android:host=\"visortmo.com\" />\n\t\t\t\t<data android:host=\"manhwa18.com\" />\n\t\t\t\t<data android:host=\"leerolimpo.com\" />\n\t\t\t\t<data android:host=\"klz9.com\" />\n\t\t\t\t<data android:host=\"welovemanga.one\" />\n\t\t\t\t<data android:host=\"weloma.art\" />\n\t\t\t\t<data android:host=\"reader.deathtollscans.net\" />\n\t\t\t\t<data android:host=\"reader.mangatellers.gr\" />\n\t\t\t\t<data android:host=\"reader.seinagi.org.es\" />\n\t\t\t\t<data android:host=\"www.menudo-fansub.com\" />\n\t\t\t\t<data android:host=\"lector.pzykosis666hfansub.com\" />\n\t\t\t\t<data android:host=\"adulto.seinagi.org.es\" />\n\t\t\t\t<data android:host=\"reader.powermanga.org\" />\n\t\t\t\t<data android:host=\"www.ramareader.it\" />\n\t\t\t\t<data android:host=\"read-nifteam.info\" />\n\t\t\t\t<data android:host=\"bentomanga.com\" />\n\t\t\t\t<data android:host=\"www.bentomanga.com\" />\n\t\t\t\t<data android:host=\"furyosociety.com\" />\n\t\t\t\t<data android:host=\"legacy-scans.com\" />\n\t\t\t\t<data android:host=\"lire-scan.me\" />\n\t\t\t\t<data android:host=\"lugnica-scans.com\" />\n\t\t\t\t<data android:host=\"www.mangakawaii.io\" />\n\t\t\t\t<data android:host=\"www.manga-mana.com\" />\n\t\t\t\t<data android:host=\"scansmangas.me\" />\n\t\t\t\t<data android:host=\"scantrad-union.com\" />\n\t\t\t\t<data android:host=\"hentaislayer.net\" />\n\t\t\t\t<data android:host=\"scyllacomics.xyz\" />\n\t\t\t\t<data android:host=\"lelscanfr.com\" />\n\t\t\t\t<data android:host=\"asmhentai.com\" />\n\t\t\t\t<data android:host=\"doujindesu.uk\" />\n\t\t\t\t<data android:host=\"3hentai.net\" />\n\t\t\t\t<data android:host=\"hentaienvy.com\" />\n\t\t\t\t<data android:host=\"hentaiera.com\" />\n\t\t\t\t<data android:host=\"hentaiforce.net\" />\n\t\t\t\t<data android:host=\"hentaifox.com\" />\n\t\t\t\t<data android:host=\"hentairox.com\" />\n\t\t\t\t<data android:host=\"nhentai.net\" />\n\t\t\t\t<data android:host=\"hentaiseason.com\" />\n\t\t\t\t<data android:host=\"hentaitokyo.net\" />\n\t\t\t\t<data android:host=\"mundohentaioficial.com\" />\n\t\t\t\t<data android:host=\"universohentai.com\" />\n\t\t\t\t<data android:host=\"mahoushoujobu.com\" />\n\t\t\t\t<data android:host=\"danke.moe\" />\n\t\t\t\t<data android:host=\"guya.cubari.moe\" />\n\t\t\t\t<data android:host=\"hachirumi.com\" />\n\t\t\t\t<data android:host=\"omegascans.org\" />\n\t\t\t\t<data android:host=\"reaperscans.com\" />\n\t\t\t\t<data android:host=\"templescan.net\" />\n\t\t\t\t<data android:host=\"lectorikigai.erigu.com\" />\n\t\t\t\t<data android:host=\"perf-scan.fr\" />\n\t\t\t\t<data android:host=\"site.modescanlator.net\" />\n\t\t\t\t<data android:host=\"brakeout.xyz\" />\n\t\t\t\t<data android:host=\"cerberuseries.xyz\" />\n\t\t\t\t<data android:host=\"mangaesp.net\" />\n\t\t\t\t<data android:host=\"toomics.com/de\" />\n\t\t\t\t<data android:host=\"toomics.top/de\" />\n\t\t\t\t<data android:host=\"daycomics.me/en\" />\n\t\t\t\t<data android:host=\"hotcomics.me/en\" />\n\t\t\t\t<data android:host=\"toomics.com/en\" />\n\t\t\t\t<data android:host=\"toomics.com/es\" />\n\t\t\t\t<data android:host=\"toomics.com/mx\" />\n\t\t\t\t<data android:host=\"toomics.com/fr\" />\n\t\t\t\t<data android:host=\"toomics.com/it\" />\n\t\t\t\t<data android:host=\"toomics.com/ja\" />\n\t\t\t\t<data android:host=\"toomics.com/por\" />\n\t\t\t\t<data android:host=\"toomics.com/sc\" />\n\t\t\t\t<data android:host=\"toomics.com/tc\" />\n\t\t\t\t<data android:host=\"doujindesu.tv\" />\n\t\t\t\t<data android:host=\"hentaicrot.com\" />\n\t\t\t\t<data android:host=\"pixhentai.com\" />\n\t\t\t\t<data android:host=\"mangagalaxy.org\" />\n\t\t\t\t<data android:host=\"vortextoon.com\" />\n\t\t\t\t<data android:host=\"vortextoon.com\" />\n\t\t\t\t<data android:host=\"nicovideo.jp\" />\n\t\t\t\t<data android:host=\"4uscans.com\" />\n\t\t\t\t<data android:host=\"ezmanga.org\" />\n\t\t\t\t<data android:host=\"kewnscans.org\" />\n\t\t\t\t<data android:host=\"laidbackscans.org\" />\n\t\t\t\t<data android:host=\"luacomic.net\" />\n\t\t\t\t<data android:host=\"magustoon.com\" />\n\t\t\t\t<data android:host=\"necroscans.com\" />\n\t\t\t\t<data android:host=\"anteikuscan.fr\" />\n\t\t\t\t<data android:host=\"astrames.fr\" />\n\t\t\t\t<data android:host=\"edscanlation.fr\" />\n\t\t\t\t<data android:host=\"starboundscans.com\" />\n\t\t\t\t<data android:host=\"likemanga.io\" />\n\t\t\t\t<data android:host=\"ero18x.com\" />\n\t\t\t\t<data android:host=\"eromanhwa.org\" />\n\t\t\t\t<data android:host=\"kdtscans.com\" />\n\t\t\t\t<data android:host=\"manga18fx.com\" />\n\t\t\t\t<data android:host=\"mangacrazy.net\" />\n\t\t\t\t<data android:host=\"mangatop.site\" />\n\t\t\t\t<data android:host=\"manhwa18.cc\" />\n\t\t\t\t<data android:host=\"manhwa-raw.com\" />\n\t\t\t\t<data android:host=\"manytoon.club\" />\n\t\t\t\t<data android:host=\"arabtoons.net\" />\n\t\t\t\t<data android:host=\"3asq.org\" />\n\t\t\t\t<data android:host=\"azoramoon.com\" />\n\t\t\t\t<data android:host=\"comicarab.com\" />\n\t\t\t\t<data android:host=\"www.hadess.xyz\" />\n\t\t\t\t<data android:host=\"gatemanga.com\" />\n\t\t\t\t<data android:host=\"gmanga.site\" />\n\t\t\t\t<data android:host=\"lekmanga.net\" />\n\t\t\t\t<data android:host=\"lekmanga.com\" />\n\t\t\t\t<data android:host=\"lekmanga.org\" />\n\t\t\t\t<data android:host=\"like-manga.net\" />\n\t\t\t\t<data android:host=\"manga-leko.org\" />\n\t\t\t\t<data android:host=\"link-manga.com\" />\n\t\t\t\t<data android:host=\"manga-lionz.com\" />\n\t\t\t\t<data android:host=\"mangapeak.org\" />\n\t\t\t\t<data android:host=\"mangarose.net\" />\n\t\t\t\t<data android:host=\"manga-starz.com\" />\n\t\t\t\t<data android:host=\"mangatime.us\" />\n\t\t\t\t<data android:host=\"mangarabic.com\" />\n\t\t\t\t<data android:host=\"manga-spark.com\" />\n\t\t\t\t<data android:host=\"manhatic.com\" />\n\t\t\t\t<data android:host=\"niji-translations.com\" />\n\t\t\t\t<data android:host=\"novelstown.com\" />\n\t\t\t\t<data android:host=\"olaoe.cyou\" />\n\t\t\t\t<data android:host=\"rocksmanga.com\" />\n\t\t\t\t<data android:host=\"www.shadowxmanga.com\" />\n\t\t\t\t<data android:host=\"webtoonempire-ron.com\" />\n\t\t\t\t<data android:host=\"yonabar.xyz\" />\n\t\t\t\t<data android:host=\"mangalesen.net\" />\n\t\t\t\t<data android:host=\"adultwebtoon.com\" />\n\t\t\t\t<data android:host=\"allporncomic.com\" />\n\t\t\t\t<data android:host=\"anisascans.in\" />\n\t\t\t\t<data android:host=\"anshscans.org\" />\n\t\t\t\t<data android:host=\"aquareader.net\" />\n\t\t\t\t<data android:host=\"arcanescans.com\" />\n\t\t\t\t<data android:host=\"aryascans.com\" />\n\t\t\t\t<data android:host=\"asurascansgg.com\" />\n\t\t\t\t<data android:host=\"asurascans.us\" />\n\t\t\t\t<data android:host=\"babelwuxia.com\" />\n\t\t\t\t<data android:host=\"bananamanga.net\" />\n\t\t\t\t<data android:host=\"bestmanhua.com\" />\n\t\t\t\t<data android:host=\"bibimanga.com\" />\n\t\t\t\t<data android:host=\"boyslove.me\" />\n\t\t\t\t<data android:host=\"cocomic.co\" />\n\t\t\t\t<data android:host=\"coffeemanga.io\" />\n\t\t\t\t<data android:host=\"coloredmanga.net\" />\n\t\t\t\t<data android:host=\"comicsvalley.com\" />\n\t\t\t\t<data android:host=\"v2.comiz.net\" />\n\t\t\t\t<data android:host=\"creepyscans.com\" />\n\t\t\t\t<data android:host=\"dark-scan.com\" />\n\t\t\t\t<data android:host=\"darkscans.net\" />\n\t\t\t\t<data android:host=\"reader.decadencescans.com\" />\n\t\t\t\t<data android:host=\"dragontea.ink\" />\n\t\t\t\t<data android:host=\"duckmanga.com\" />\n\t\t\t\t<data android:host=\"www.redmanga.org\" />\n\t\t\t\t<data android:host=\"factmanga.com\" />\n\t\t\t\t<data android:host=\"firescans.xyz\" />\n\t\t\t\t<data android:host=\"freecomiconline.me\" />\n\t\t\t\t<data android:host=\"freemanga.me\" />\n\t\t\t\t<data android:host=\"freemangatop.com\" />\n\t\t\t\t<data android:host=\"freewebtooncoins.com\" />\n\t\t\t\t<data android:host=\"gdscans.com\" />\n\t\t\t\t<data android:host=\"gedecomix.com\" />\n\t\t\t\t<data android:host=\"goodgirls.moe\" />\n\t\t\t\t<data android:host=\"gourmetsupremacy.com\" />\n\t\t\t\t<data android:host=\"grabber.zone\" />\n\t\t\t\t<data android:host=\"harimanga.com\" />\n\t\t\t\t<data android:host=\"manga18h.xyz\" />\n\t\t\t\t<data android:host=\"hentai4free.net\" />\n\t\t\t\t<data android:host=\"hentaimanga.me\" />\n\t\t\t\t<data android:host=\"hentaiwebtoon.com\" />\n\t\t\t\t<data android:host=\"hentaixcomic.com\" />\n\t\t\t\t<data android:host=\"hentaixyuri.com\" />\n\t\t\t\t<data android:host=\"hentaixdickgirl.com\" />\n\t\t\t\t<data android:host=\"hiperdex.com\" />\n\t\t\t\t<data android:host=\"hunlight.com\" />\n\t\t\t\t<data android:host=\"en.huntersscan.xyz\" />\n\t\t\t\t<data android:host=\"immortalupdates.com\" />\n\t\t\t\t<data android:host=\"infamous-scans.com\" />\n\t\t\t\t<data android:host=\"www.xmanhwa.me\" />\n\t\t\t\t<data android:host=\"www.isekaiscan.top\" />\n\t\t\t\t<data android:host=\"paragonscans.com\" />\n\t\t\t\t<data android:host=\"itsyourightmanhua.com\" />\n\t\t\t\t<data android:host=\"s2manga.io\" />\n\t\t\t\t<data android:host=\"18.kiara.cool\" />\n\t\t\t\t<data android:host=\"kissmanga.in\" />\n\t\t\t\t<data android:host=\"ksgroupscans.com\" />\n\t\t\t\t<data android:host=\"retsu.org\" />\n\t\t\t\t<data android:host=\"kunmanga.com\" />\n\t\t\t\t<data android:host=\"lscomic.com\" />\n\t\t\t\t<data android:host=\"lhtranslation.net\" />\n\t\t\t\t<data android:host=\"lilymanga.net\" />\n\t\t\t\t<data android:host=\"lolicon.mobi\" />\n\t\t\t\t<data android:host=\"luffymanga.com\" />\n\t\t\t\t<data android:host=\"luxmanga.net\" />\n\t\t\t\t<data android:host=\"madaradex.org\" />\n\t\t\t\t<data android:host=\"manga-1001.com\" />\n\t\t\t\t<data android:host=\"manga18.xyz\" />\n\t\t\t\t<data android:host=\"manga18h.xyz\" />\n\t\t\t\t<data android:host=\"manga18x.net\" />\n\t\t\t\t<data android:host=\"manga1k.com\" />\n\t\t\t\t<data android:host=\"manga1st.online\" />\n\t\t\t\t<data android:host=\"manga68.com\" />\n\t\t\t\t<data android:host=\"mangaaction.com\" />\n\t\t\t\t<data android:host=\"mangazin.org\" />\n\t\t\t\t<data android:host=\"mangabob.com\" />\n\t\t\t\t<data android:host=\"mangacultivator.com\" />\n\t\t\t\t<data android:host=\"mangadass.com\" />\n\t\t\t\t<data android:host=\"mangadistrict.com\" />\n\t\t\t\t<data android:host=\"mangadna.com\" />\n\t\t\t\t<data android:host=\"mangaeclipse.com\" />\n\t\t\t\t<data android:host=\"mangaeffect.com\" />\n\t\t\t\t<data android:host=\"manhuafast.net\" />\n\t\t\t\t<data android:host=\"mangaforfree.com\" />\n\t\t\t\t<data android:host=\"mangafoxfull.com\" />\n\t\t\t\t<data android:host=\"mangafreak.online\" />\n\t\t\t\t<data android:host=\"mangageek.org\" />\n\t\t\t\t<data android:host=\"mangahentai.me\" />\n\t\t\t\t<data android:host=\"mangakiss.org\" />\n\t\t\t\t<data android:host=\"mangakomi.io\" />\n\t\t\t\t<data android:host=\"mangaleveling.com\" />\n\t\t\t\t<data android:host=\"mangaonlineteam.com\" />\n\t\t\t\t<data android:host=\"mangamaniacs.org\" />\n\t\t\t\t<data android:host=\"mangaonlineteam.com\" />\n\t\t\t\t<data android:host=\"mangaowlnet.com\" />\n\t\t\t\t<data android:host=\"mangaowl.io\" />\n\t\t\t\t<data android:host=\"mangaowlyaoi.com\" />\n\t\t\t\t<data android:host=\"mangapure.net\" />\n\t\t\t\t<data android:host=\"mangaqueen.com\" />\n\t\t\t\t<data android:host=\"www.mangaread.org\" />\n\t\t\t\t<data android:host=\"mangaread.co\" />\n\t\t\t\t<data android:host=\"mangarockteam.com\" />\n\t\t\t\t<data android:host=\"mangarockteam.com\" />\n\t\t\t\t<data android:host=\"mangarolls.net\" />\n\t\t\t\t<data android:host=\"toon69.com\" />\n\t\t\t\t<data android:host=\"mangaruby.com\" />\n\t\t\t\t<data android:host=\"mangaryu.com\" />\n\t\t\t\t<data android:host=\"mangasushi.org\" />\n\t\t\t\t<data android:host=\"mangatx.gg\" />\n\t\t\t\t<data android:host=\"mangatx.to\" />\n\t\t\t\t<data android:host=\"mangaempress.com\" />\n\t\t\t\t<data android:host=\"mangatyrant.com\" />\n\t\t\t\t<data android:host=\"mangaweebs.in\" />\n\t\t\t\t<data android:host=\"mangazin.org\" />\n\t\t\t\t<data android:host=\"toonclash.com\" />\n\t\t\t\t<data android:host=\"mangagg.com\" />\n\t\t\t\t<data android:host=\"mangaowl.one\" />\n\t\t\t\t<data android:host=\"www.mangasy.com\" />\n\t\t\t\t<data android:host=\"mangaus.xyz\" />\n\t\t\t\t<data android:host=\"manhuamanhwa.com\" />\n\t\t\t\t<data android:host=\"manhuazone.org\" />\n\t\t\t\t<data android:host=\"www.manhuazonghe.com\" />\n\t\t\t\t<data android:host=\"manhuaes.com\" />\n\t\t\t\t<data android:host=\"manhuafast.com\" />\n\t\t\t\t<data android:host=\"manhuaga.com\" />\n\t\t\t\t<data android:host=\"manhuahot.com\" />\n\t\t\t\t<data android:host=\"manhuaplus.com\" />\n\t\t\t\t<data android:host=\"www.manhuasy.com\" />\n\t\t\t\t<data android:host=\"manhuaus.com\" />\n\t\t\t\t<data android:host=\"manhuauss.com\" />\n\t\t\t\t<data android:host=\"manhwa18.app\" />\n\t\t\t\t<data android:host=\"manhwa18.org\" />\n\t\t\t\t<data android:host=\"manhwa68.com\" />\n\t\t\t\t<data android:host=\"manhwaclan.com\" />\n\t\t\t\t<data android:host=\"manhwafull.com\" />\n\t\t\t\t<data android:host=\"manhwahentai.me\" />\n\t\t\t\t<data android:host=\"manhwahentai.to\" />\n\t\t\t\t<data android:host=\"manhwamanhua.com\" />\n\t\t\t\t<data android:host=\"manhwanew.com\" />\n\t\t\t\t<data android:host=\"manhwaraw.com\" />\n\t\t\t\t<data android:host=\"manhwatop.com\" />\n\t\t\t\t<data android:host=\"www.manhwaden.com\" />\n\t\t\t\t<data android:host=\"manhwasco.net\" />\n\t\t\t\t<data android:host=\"manhwaz.com\" />\n\t\t\t\t<data android:host=\"manycomic.com\" />\n\t\t\t\t<data android:host=\"manytoon.com\" />\n\t\t\t\t<data android:host=\"manytoon.me\" />\n\t\t\t\t<data android:host=\"milftoon.xxx\" />\n\t\t\t\t<data android:host=\"mm-scans.org\" />\n\t\t\t\t<data android:host=\"mortalsgroove.com\" />\n\t\t\t\t<data android:host=\"inkreads.com\" />\n\t\t\t\t<data android:host=\"neatmangas.com\" />\n\t\t\t\t<data android:host=\"newmanhua.com\" />\n\t\t\t\t<data android:host=\"www.nightcomic.com\" />\n\t\t\t\t<data android:host=\"nitroscans.net\" />\n\t\t\t\t<data android:host=\"novelcrow.com\" />\n\t\t\t\t<data android:host=\"novelmic.com\" />\n\t\t\t\t<data android:host=\"1manhwa.com\" />\n\t\t\t\t<data android:host=\"onlymanhwa.org\" />\n\t\t\t\t<data android:host=\"www.paritehaber.com\" />\n\t\t\t\t<data android:host=\"pawmanga.com\" />\n\t\t\t\t<data android:host=\"www.petrotechsociety.org\" />\n\t\t\t\t<data android:host=\"pianmanga.me\" />\n\t\t\t\t<data android:host=\"platinumscans.com\" />\n\t\t\t\t<data android:host=\"ponymanga.com\" />\n\t\t\t\t<data android:host=\"porncomix.online\" />\n\t\t\t\t<data android:host=\"rackusreads.com\" />\n\t\t\t\t<data android:host=\"readfreecomics.com\" />\n\t\t\t\t<data android:host=\"readmanhua.net\" />\n\t\t\t\t<data android:host=\"evilflowers.com\" />\n\t\t\t\t<data android:host=\"reset-scans.co\" />\n\t\t\t\t<data android:host=\"zinchanmanga.mobi\" />\n\t\t\t\t<data android:host=\"s2manga.com\" />\n\t\t\t\t<data android:host=\"aquascans.com\" />\n\t\t\t\t<data android:host=\"sectscans.com\" />\n\t\t\t\t<data android:host=\"setsuscans.com\" />\n\t\t\t\t<data android:host=\"shibamanga.com\" />\n\t\t\t\t<data android:host=\"shootingstarscans.com\" />\n\t\t\t\t<data android:host=\"sleepytranslations.com\" />\n\t\t\t\t<data android:host=\"1st-kissmanga.net\" />\n\t\t\t\t<data android:host=\"1stkiss-manga.com\" />\n\t\t\t\t<data android:host=\"stonescape.xyz\" />\n\t\t\t\t<data android:host=\"summanga.com\" />\n\t\t\t\t<data android:host=\"tcbscans-manga.com\" />\n\t\t\t\t<data android:host=\"teenmanhua.com\" />\n\t\t\t\t<data android:host=\"theblank.net\" />\n\t\t\t\t<data android:host=\"theguildscans.com\" />\n\t\t\t\t<data android:host=\"toonchill.com\" />\n\t\t\t\t<data android:host=\"www.toongod.org\" />\n\t\t\t\t<data android:host=\"toonily.com\" />\n\t\t\t\t<data android:host=\"toonizy.com\" />\n\t\t\t\t<data android:host=\"manhuatop.org\" />\n\t\t\t\t<data android:host=\"topreadmanhwa.com\" />\n\t\t\t\t<data android:host=\"treemanga.com\" />\n\t\t\t\t<data android:host=\"tritinia.org\" />\n\t\t\t\t<data android:host=\"utoon.net\" />\n\t\t\t\t<data android:host=\"webdexscans.com\" />\n\t\t\t\t<data android:host=\"webtoon.uk\" />\n\t\t\t\t<data android:host=\"webtoonscan.com\" />\n\t\t\t\t<data android:host=\"www.webtoon.xyz\" />\n\t\t\t\t<data android:host=\"whalemanga.com\" />\n\t\t\t\t<data android:host=\"woopread.com\" />\n\t\t\t\t<data android:host=\"yaoihub.com\" />\n\t\t\t\t<data android:host=\"yaoi.mobi\" />\n\t\t\t\t<data android:host=\"yaoiscan.com\" />\n\t\t\t\t<data android:host=\"zandynofansub.aishiteru.org\" />\n\t\t\t\t<data android:host=\"zinchanmanga.com\" />\n\t\t\t\t<data android:host=\"zinmanga.cc\" />\n\t\t\t\t<data android:host=\"zin-manga.com\" />\n\t\t\t\t<data android:host=\"zinmanga.ms\" />\n\t\t\t\t<data android:host=\"zinchanmanga.net\" />\n\t\t\t\t<data android:host=\"zinmanga.net\" />\n\t\t\t\t<data android:host=\"apollcomics.es\" />\n\t\t\t\t<data android:host=\"artessupremas.com\" />\n\t\t\t\t<data android:host=\"scansatlanticos.com\" />\n\t\t\t\t<data android:host=\"barmanga.com\" />\n\t\t\t\t<data android:host=\"begatranslation.com\" />\n\t\t\t\t<data android:host=\"bokugents.com\" />\n\t\t\t\t<data android:host=\"cocorip.net\" />\n\t\t\t\t<data android:host=\"copypastescan.xyz\" />\n\t\t\t\t<data android:host=\"daprob.com\" />\n\t\t\t\t<data android:host=\"doujinhentai.net\" />\n\t\t\t\t<data android:host=\"www.doujinshell.com\" />\n\t\t\t\t<data android:host=\"dragontranslation.net\" />\n\t\t\t\t<data android:host=\"emperormanga.net\" />\n\t\t\t\t<data android:host=\"hadesnofansub.com\" />\n\t\t\t\t<data android:host=\"haremscann.es\" />\n\t\t\t\t<data android:host=\"herenscan.com\" />\n\t\t\t\t<data android:host=\"housemangas.com\" />\n\t\t\t\t<data android:host=\"infrafandub.com\" />\n\t\t\t\t<data android:host=\"inmoralnofansub.xyz\" />\n\t\t\t\t<data android:host=\"marcialhub.xyz\" />\n\t\t\t\t<data android:host=\"jobsibe.com\" />\n\t\t\t\t<data android:host=\"kenhuav2scan.com\" />\n\t\t\t\t<data android:host=\"lectorkns.com\" />\n\t\t\t\t<data android:host=\"koinoboriscan.com\" />\n\t\t\t\t<data android:host=\"www.lectormanga.lat\" />\n\t\t\t\t<data android:host=\"lectorunitoon.com\" />\n\t\t\t\t<data android:host=\"lectorunm.life\" />\n\t\t\t\t<data android:host=\"lkscanlation.com\" />\n\t\t\t\t<data android:host=\"wikicrab.xyz\" />\n\t\t\t\t<data android:host=\"mangaland.net\" />\n\t\t\t\t<data android:host=\"manga.mundodrama.site\" />\n\t\t\t\t<data android:host=\"mangasnosekai.com\" />\n\t\t\t\t<data android:host=\"mangaxico.com\" />\n\t\t\t\t<data android:host=\"manhwa-es.com\" />\n\t\t\t\t<data android:host=\"manhwa-latino.com\" />\n\t\t\t\t<data android:host=\"panconcola.com\" />\n\t\t\t\t<data android:host=\"marmota.me\" />\n\t\t\t\t<data android:host=\"mh.cookni.net\" />\n\t\t\t\t<data android:host=\"es.mi2manga.com\" />\n\t\t\t\t<data android:host=\"visormonarca.com\" />\n\t\t\t\t<data android:host=\"mundomanhwa.com\" />\n\t\t\t\t<data android:host=\"www.swordalada.org\" />\n\t\t\t\t<data android:host=\"panconcola.com\" />\n\t\t\t\t<data android:host=\"ragnarokscan.com\" />\n\t\t\t\t<data android:host=\"ragnarokscanlation.net\" />\n\t\t\t\t<data android:host=\"richtoscan.com\" />\n\t\t\t\t<data android:host=\"rsdleft.com\" />\n\t\t\t\t<data android:host=\"latan.visorsmr.com\" />\n\t\t\t\t<data android:host=\"sapphirescan.com\" />\n\t\t\t\t<data android:host=\"scambertraslator.com\" />\n\t\t\t\t<data android:host=\"www.stickhorse.cl\" />\n\t\t\t\t<data android:host=\"taurusmanga.com\" />\n\t\t\t\t<data android:host=\"territorioleal.com\" />\n\t\t\t\t<data android:host=\"tmomanga.com\" />\n\t\t\t\t<data android:host=\"topcomicporno.com\" />\n\t\t\t\t<data android:host=\"vermanhwa.com\" />\n\t\t\t\t<data android:host=\"astral-manga.fr\" />\n\t\t\t\t<data android:host=\"www1.bluesolo.org\" />\n\t\t\t\t<data android:host=\"epsilonsoft.to\" />\n\t\t\t\t<data android:host=\"epsilonscan.to\" />\n\t\t\t\t<data android:host=\"fr-scan.com\" />\n\t\t\t\t<data android:host=\"harmony-scan.fr\" />\n\t\t\t\t<data android:host=\"hentai-origines.fr\" />\n\t\t\t\t<data android:host=\"hentai.scantrad-vf.cc\" />\n\t\t\t\t<data android:host=\"hentaizone.xyz\" />\n\t\t\t\t<data android:host=\"hhentai.fr\" />\n\t\t\t\t<data android:host=\"mangahub.fr\" />\n\t\t\t\t<data android:host=\"manga-scantrad.io\" />\n\t\t\t\t<data android:host=\"mangas-origines.fr\" />\n\t\t\t\t<data android:host=\"crunchyscan.fr\" />\n\t\t\t\t<data android:host=\"pantheon-scan.com\" />\n\t\t\t\t<data android:host=\"raijinscans.fr\" />\n\t\t\t\t<data android:host=\"fr.readergen.fr\" />\n\t\t\t\t<data android:host=\"reaperscans.fr\" />\n\t\t\t\t<data android:host=\"scan-hentai.fr\" />\n\t\t\t\t<data android:host=\"x-manga.net\" />\n\t\t\t\t<data android:host=\"scantrad-vf.me\" />\n\t\t\t\t<data android:host=\"toonfr.com\" />\n\t\t\t\t<data android:host=\"birdtoon.net\" />\n\t\t\t\t<data android:host=\"hwago.org\" />\n\t\t\t\t<data android:host=\"indo18h.com\" />\n\t\t\t\t<data android:host=\"klikmanga.id\" />\n\t\t\t\t<data android:host=\"lumoskomik.com\" />\n\t\t\t\t<data android:host=\"manhwahub.net\" />\n\t\t\t\t<data android:host=\"mgkomik.id\" />\n\t\t\t\t<data android:host=\"pojokmanga.info\" />\n\t\t\t\t<data android:host=\"shinigami03.com\" />\n\t\t\t\t<data android:host=\"worldmanhwas.zone\" />\n\t\t\t\t<data android:host=\"www.xmanhwa.me\" />\n\t\t\t\t<data android:host=\"yubikiri.my.id\" />\n\t\t\t\t<data android:host=\"yuramanga.my.id\" />\n\t\t\t\t<data android:host=\"www.beyondtheataraxia.com\" />\n\t\t\t\t<data android:host=\"mangafenxi.net\" />\n\t\t\t\t<data android:host=\"rawxz.si\" />\n\t\t\t\t<data android:host=\"rawdex.net\" />\n\t\t\t\t<data android:host=\"mangahona.pl\" />\n\t\t\t\t<data android:host=\"alonescanlator.com.br\" />\n\t\t\t\t<data android:host=\"ancientcomics.com.br\" />\n\t\t\t\t<data android:host=\"apenasmaisumyaoi.com\" />\n\t\t\t\t<data android:host=\"argoscomics.com.br\" />\n\t\t\t\t<data android:host=\"arthurscan.xyz\" />\n\t\t\t\t<data android:host=\"atemporal.cloud\" />\n\t\t\t\t<data android:host=\"leitor.borutoexplorer.com.br\" />\n\t\t\t\t<data android:host=\"burningscans.com\" />\n\t\t\t\t<data android:host=\"cabaredowatame.site\" />\n\t\t\t\t<data android:host=\"cafecomyaoi.com.br\" />\n\t\t\t\t<data android:host=\"cerisetoon.com\" />\n\t\t\t\t<data android:host=\"crystalcomics.com\" />\n\t\t\t\t<data android:host=\"cvnscan.com\" />\n\t\t\t\t<data android:host=\"dianxiatrads.com\" />\n\t\t\t\t<data android:host=\"dreamscan.com.br\" />\n\t\t\t\t<data android:host=\"euphoriascan.com\" />\n\t\t\t\t<data android:host=\"fayscans.net\" />\n\t\t\t\t<data android:host=\"fbsquadx.com\" />\n\t\t\t\t<data android:host=\"fenixproject.xyz\" />\n\t\t\t\t<data android:host=\"flowermangas.com\" />\n\t\t\t\t<data android:host=\"foxwhite.com.br\" />\n\t\t\t\t<data android:host=\"galinhasamurai.com\" />\n\t\t\t\t<data android:host=\"gekkou.site\" />\n\t\t\t\t<data android:host=\"ghostscan.com.br\" />\n\t\t\t\t<data android:host=\"gooffansub.com\" />\n\t\t\t\t<data android:host=\"hentaiteca.net\" />\n\t\t\t\t<data android:host=\"hiper.cool\" />\n\t\t\t\t<data android:host=\"hunterscomics.com\" />\n\t\t\t\t<data android:host=\"illusionscan.com\" />\n\t\t\t\t<data android:host=\"imperioscans.com.br\" />\n\t\t\t\t<data android:host=\"imperiodabritannia.com\" />\n\t\t\t\t<data android:host=\"kakuseiproject.com\" />\n\t\t\t\t<data android:host=\"kalango.org\" />\n\t\t\t\t<data android:host=\"ladyestelarscan.com.br\" />\n\t\t\t\t<data android:host=\"leitordemanga.com\" />\n\t\t\t\t<data android:host=\"leitor.kamisama.com.br\" />\n\t\t\t\t<data android:host=\"lerhentai.com\" />\n\t\t\t\t<data android:host=\"leryaoi.com\" />\n\t\t\t\t<data android:host=\"lermangas.me\" />\n\t\t\t\t<data android:host=\"lichmangas.com\" />\n\t\t\t\t<data android:host=\"limboscan.com.br\" />\n\t\t\t\t<data android:host=\"limitedtimeproject.com\" />\n\t\t\t\t<data android:host=\"www.linkstartscan.xyz\" />\n\t\t\t\t<data android:host=\"lunarrscan.com\" />\n\t\t\t\t<data android:host=\"maidscans.com\" />\n\t\t\t\t<data android:host=\"maidsecret.com\" />\n\t\t\t\t<data android:host=\"mangananquim.site\" />\n\t\t\t\t<data android:host=\"manganinja.com\" />\n\t\t\t\t<data android:host=\"manhastro.com\" />\n\t\t\t\t<data android:host=\"momonohanascan.com\" />\n\t\t\t\t<data android:host=\"moonloversscan.com.br\" />\n\t\t\t\t<data android:host=\"moonwitchinlovescan.com\" />\n\t\t\t\t<data android:host=\"mrbenne.com\" />\n\t\t\t\t<data android:host=\"mugiwarasoficial.com\" />\n\t\t\t\t<data android:host=\"mangalivre.net\" />\n\t\t\t\t<data android:host=\"neroxus.com.br\" />\n\t\t\t\t<data android:host=\"ninjacomics.xyz\" />\n\t\t\t\t<data android:host=\"nocfsb.com\" />\n\t\t\t\t<data android:host=\"noindexscan.com\" />\n\t\t\t\t<data android:host=\"norterose.com.br\" />\n\t\t\t\t<data android:host=\"passamaoscan.com\" />\n\t\t\t\t<data android:host=\"pirulitorosa.site\" />\n\t\t\t\t<data android:host=\"portalyaoi.com\" />\n\t\t\t\t<data android:host=\"prismahentai.com\" />\n\t\t\t\t<data android:host=\"projetoscanlator.com\" />\n\t\t\t\t<data android:host=\"psunicorn.com\" />\n\t\t\t\t<data android:host=\"pussy.sussytoons.com\" />\n\t\t\t\t<data android:host=\"rainbowfairyscan.com\" />\n\t\t\t\t<data android:host=\"remangas.net\" />\n\t\t\t\t<data android:host=\"rogmangas.com\" />\n\t\t\t\t<data android:host=\"sinensistoon.com\" />\n\t\t\t\t<data android:host=\"oldi.sussytoons.com\" />\n\t\t\t\t<data android:host=\"sweetscan.net\" />\n\t\t\t\t<data android:host=\"tankouhentai.com\" />\n\t\t\t\t<data android:host=\"tatakaescan.com\" />\n\t\t\t\t<data android:host=\"valkyriescan.com\" />\n\t\t\t\t<data android:host=\"villainessscan.xyz\" />\n\t\t\t\t<data android:host=\"wicked-witch-scan.com\" />\n\t\t\t\t<data android:host=\"winterscan.com\" />\n\t\t\t\t<data android:host=\"wonderlandscan.com\" />\n\t\t\t\t<data android:host=\"trisalyanp.com\" />\n\t\t\t\t<data android:host=\"3xyaoi.com\" />\n\t\t\t\t<data android:host=\"ycscan.com\" />\n\t\t\t\t<data android:host=\"yuri.live\" />\n\t\t\t\t<data android:host=\"bestmanga.club\" />\n\t\t\t\t<data android:host=\"mangamammy.ru\" />\n\t\t\t\t<data android:host=\"mangaonelove.site\" />\n\t\t\t\t<data android:host=\"mangazavr.ru\" />\n\t\t\t\t<data android:host=\"bakaman.net\" />\n\t\t\t\t<data android:host=\"cat300.net\" />\n\t\t\t\t<data android:host=\"doujinza.com\" />\n\t\t\t\t<data android:host=\"www.kings-manga.co\" />\n\t\t\t\t<data android:host=\"manga-lc.net\" />\n\t\t\t\t<data android:host=\"mangadeemak.com\" />\n\t\t\t\t<data android:host=\"www.manhuabug.com\" />\n\t\t\t\t<data android:host=\"www.manhuakey.com\" />\n\t\t\t\t<data android:host=\"www.nekopost.co\" />\n\t\t\t\t<data android:host=\"www.rh2plusmanga.com\" />\n\t\t\t\t<data android:host=\"alliedfansub.net\" />\n\t\t\t\t<data android:host=\"anikiga.com\" />\n\t\t\t\t<data android:host=\"anisamanga.com\" />\n\t\t\t\t<data android:host=\"araznovel.com\" />\n\t\t\t\t<data android:host=\"www.mangaoku.org\" />\n\t\t\t\t<data android:host=\"clover-manga.com\" />\n\t\t\t\t<data android:host=\"deccalscans.net\" />\n\t\t\t\t<data android:host=\"diamondfansub.com\" />\n\t\t\t\t<data android:host=\"domalfansub.com.tr\" />\n\t\t\t\t<data android:host=\"esomanga.com\" />\n\t\t\t\t<data android:host=\"garciamanga.com\" />\n\t\t\t\t<data android:host=\"ghostfansub.co\" />\n\t\t\t\t<data android:host=\"glorymanga.com\" />\n\t\t\t\t<data android:host=\"grimelek.pro\" />\n\t\t\t\t<data android:host=\"guncelmanga.net\" />\n\t\t\t\t<data android:host=\"hayalistic.com.tr\" />\n\t\t\t\t<data android:host=\"hoifansub.com\" />\n\t\t\t\t<data android:host=\"www.imparatormanga.com\" />\n\t\t\t\t<data android:host=\"jellyring.co\" />\n\t\t\t\t<data android:host=\"jiangzaitoon.pro\" />\n\t\t\t\t<data android:host=\"kabusmanga.com\" />\n\t\t\t\t<data android:host=\"kedi.to\" />\n\t\t\t\t<data android:host=\"koreliscans.com\" />\n\t\t\t\t<data android:host=\"www.kuroimanga.com\" />\n\t\t\t\t<data android:host=\"laviniafansub.com\" />\n\t\t\t\t<data android:host=\"lilyumfansub.com.tr\" />\n\t\t\t\t<data android:host=\"lunascans.org\" />\n\t\t\t\t<data android:host=\"www.mangatilkisi.com\" />\n\t\t\t\t<data android:host=\"mangaokusana.com\" />\n\t\t\t\t<data android:host=\"manga-sehri.net\" />\n\t\t\t\t<data android:host=\"mangatr.net\" />\n\t\t\t\t<data android:host=\"mangawow.org\" />\n\t\t\t\t<data android:host=\"mangawt.net\" />\n\t\t\t\t<data android:host=\"mangaoku.info\" />\n\t\t\t\t<data android:host=\"manga-sehri.com\" />\n\t\t\t\t<data android:host=\"mangawt.com\" />\n\t\t\t\t<data android:host=\"manwe.pro\" />\n\t\t\t\t<data android:host=\"merlinscans.com\" />\n\t\t\t\t<data android:host=\"www.milasub.co\" />\n\t\t\t\t<data android:host=\"mindafansub.online\" />\n\t\t\t\t<data android:host=\"nabiscans.com\" />\n\t\t\t\t<data android:host=\"niverafansub.org\" />\n\t\t\t\t<data android:host=\"opiatoon.biz\" />\n\t\t\t\t<data android:host=\"piedpiperfansubyy.me\" />\n\t\t\t\t<data android:host=\"piedpiperfansub.me\" />\n\t\t\t\t<data android:host=\"romantikmanga.com\" />\n\t\t\t\t<data android:host=\"www.ruya-manga.com\" />\n\t\t\t\t<data android:host=\"sarcasmscans.com\" />\n\t\t\t\t<data android:host=\"strayfansub.com\" />\n\t\t\t\t<data android:host=\"timenaight.com\" />\n\t\t\t\t<data android:host=\"titanmanga.com\" />\n\t\t\t\t<data android:host=\"tonizu.xyz\" />\n\t\t\t\t<data android:host=\"tortuga-ceviri.com\" />\n\t\t\t\t<data android:host=\"viyafansub.com\" />\n\t\t\t\t<data android:host=\"webtoonhatti.net\" />\n\t\t\t\t<data android:host=\"webtoontr.net\" />\n\t\t\t\t<data android:host=\"yaoimangaoku.com\" />\n\t\t\t\t<data android:host=\"yaoitr.fun\" />\n\t\t\t\t<data android:host=\"zamanmanga.com\" />\n\t\t\t\t<data android:host=\"fecomicc.xyz\" />\n\t\t\t\t<data android:host=\"hentaicb.lol\" />\n\t\t\t\t<data android:host=\"hentaivn.fit\" />\n\t\t\t\t<data android:host=\"mangazodiac.com\" />\n\t\t\t\t<data android:host=\"pinkteacomics.com\" />\n\t\t\t\t<data android:host=\"qadc.top\" />\n\t\t\t\t<data android:host=\"phetruyen.vip\" />\n\t\t\t\t<data android:host=\"truyennhameo.com\" />\n\t\t\t\t<data android:host=\"truyenvn.fit\" />\n\t\t\t\t<data android:host=\"bakamh.com\" />\n\t\t\t\t<data android:host=\"beehentai.com\" />\n\t\t\t\t<data android:host=\"mangabuddy.com\" />\n\t\t\t\t<data android:host=\"mangacute.com\" />\n\t\t\t\t<data android:host=\"mangaforest.me\" />\n\t\t\t\t<data android:host=\"mgjinx.com\" />\n\t\t\t\t<data android:host=\"mangapuma.com\" />\n\t\t\t\t<data android:host=\"mangaxyz.com\" />\n\t\t\t\t<data android:host=\"manhuascan.io\" />\n\t\t\t\t<data android:host=\"toonitube.com\" />\n\t\t\t\t<data android:host=\"toonily.me\" />\n\t\t\t\t<data android:host=\"truemanga.com\" />\n\t\t\t\t<data android:host=\"comic1000.com\" />\n\t\t\t\t<data android:host=\"hentai3z.cc\" />\n\t\t\t\t<data android:host=\"manga18.club\" />\n\t\t\t\t<data android:host=\"18porncomic.com\" />\n\t\t\t\t<data android:host=\"tumanhwas.club\" />\n\t\t\t\t<data android:host=\"hanman18.com\" />\n\t\t\t\t<data android:host=\"m.manganelo.com\" />\n\t\t\t\t<data android:host=\"chapmanganelo.com\" />\n\t\t\t\t<data android:host=\"h.mangabat.com\" />\n\t\t\t\t<data android:host=\"readmangabat.com\" />\n\t\t\t\t<data android:host=\"w.mangairo.com\" />\n\t\t\t\t<data android:host=\"chap.mangairo.com\" />\n\t\t\t\t<data android:host=\"mangakakalot.com\" />\n\t\t\t\t<data android:host=\"chapmanganato.com\" />\n\t\t\t\t<data android:host=\"ww8.mangakakalot.tv\" />\n\t\t\t\t<data android:host=\"manganato.com\" />\n\t\t\t\t<data android:host=\"chapmanganato.to\" />\n\t\t\t\t<data android:host=\"chapmanganato.com\" />\n\t\t\t\t<data android:host=\"arc-relight.com\" />\n\t\t\t\t<data android:host=\"assortedscans.com\" />\n\t\t\t\t<data android:host=\"ar.areascans.org\" />\n\t\t\t\t<data android:host=\"www.areascans.net\" />\n\t\t\t\t<data android:host=\"fl-ares.com\" />\n\t\t\t\t<data android:host=\"hijala.com\" />\n\t\t\t\t<data android:host=\"mangaatrend.net\" />\n\t\t\t\t<data android:host=\"mangaflame.org\" />\n\t\t\t\t<data android:host=\"promanga.pro\" />\n\t\t\t\t<data android:host=\"manjanoon.xyz\" />\n\t\t\t\t<data android:host=\"noonscan.com\" />\n\t\t\t\t<data android:host=\"healteer.com\" />\n\t\t\t\t<data android:host=\"peach-bl.com\" />\n\t\t\t\t<data android:host=\"potatomanga.xyz\" />\n\t\t\t\t<data android:host=\"scarmanga.com\" />\n\t\t\t\t<data android:host=\"stellarsaber.pro\" />\n\t\t\t\t<data android:host=\"ar-thunderepic.com\" />\n\t\t\t\t<data android:host=\"www.umimanga.com\" />\n\t\t\t\t<data android:host=\"vexmanga.com\" />\n\t\t\t\t<data android:host=\"evil-manga.eu\" />\n\t\t\t\t<data android:host=\"agscomics.com\" />\n\t\t\t\t<data android:host=\"altayscans.com\" />\n\t\t\t\t<data android:host=\"anigliscans.xyz\" />\n\t\t\t\t<data android:host=\"arvencomics.com\" />\n\t\t\t\t<data android:host=\"ascalonscans.com\" />\n\t\t\t\t<data android:host=\"astrascans.org\" />\n\t\t\t\t<data android:host=\"birdmanga.com\" />\n\t\t\t\t<data android:host=\"constellarcomic.com\" />\n\t\t\t\t<data android:host=\"cosmic-scans.com\" />\n\t\t\t\t<data android:host=\"culturedworks.com\" />\n\t\t\t\t<data android:host=\"cypherscans.xyz\" />\n\t\t\t\t<data android:host=\"dexhentai.com\" />\n\t\t\t\t<data android:host=\"drakecomic.com\" />\n\t\t\t\t<data android:host=\"ehentaimanga.com\" />\n\t\t\t\t<data android:host=\"elarctoon.com\" />\n\t\t\t\t<data android:host=\"en-thunderscans.com\" />\n\t\t\t\t<data android:host=\"enryumanga.net\" />\n\t\t\t\t<data android:host=\"erosscans.xyz\" />\n\t\t\t\t<data android:host=\"flamecomics.me\" />\n\t\t\t\t<data android:host=\"freakcomic.com\" />\n\t\t\t\t<data android:host=\"freakscans.com\" />\n\t\t\t\t<data android:host=\"kingofscans.com\" />\n\t\t\t\t<data android:host=\"hentai20.io\" />\n\t\t\t\t<data android:host=\"ponvi.online\" />\n\t\t\t\t<data android:host=\"komiklab.com\" />\n\t\t\t\t<data android:host=\"ponvi.online\" />\n\t\t\t\t<data android:host=\"radiantscans.com\" />\n\t\t\t\t<data android:host=\"lunarscan.org\" />\n\t\t\t\t<data android:host=\"mangagojo.com\" />\n\t\t\t\t<data android:host=\"manhuascan.us\" />\n\t\t\t\t<data android:host=\"manhwafreak.xyz\" />\n\t\t\t\t<data android:host=\"www.manhwalover.com\" />\n\t\t\t\t<data android:host=\"manhwax.org\" />\n\t\t\t\t<data android:host=\"noonscan.net\" />\n\t\t\t\t<data android:host=\"myshojo.com\" />\n\t\t\t\t<data android:host=\"nightsup.net\" />\n\t\t\t\t<data android:host=\"kenmanga.com\" />\n\t\t\t\t<data android:host=\"ravenscans.com\" />\n\t\t\t\t<data android:host=\"qscomics.org\" />\n\t\t\t\t<data android:host=\"novelstreams.com\" />\n\t\t\t\t<data android:host=\"reaper-scans.com\" />\n\t\t\t\t<data android:host=\"rizzfables.com\" />\n\t\t\t\t<data android:host=\"shojoscans.com\" />\n\t\t\t\t<data android:host=\"skymanga.work\" />\n\t\t\t\t<data android:host=\"snowscans.com\" />\n\t\t\t\t<data android:host=\"spiderscans.xyz\" />\n\t\t\t\t<data android:host=\"suryacomics.com\" />\n\t\t\t\t<data android:host=\"olyscans.xyz\" />\n\t\t\t\t<data android:host=\"varnascan.com\" />\n\t\t\t\t<data android:host=\"hivetoon.com\" />\n\t\t\t\t<data android:host=\"voidscans.co\" />\n\t\t\t\t<data android:host=\"xcalibrscans.com\" />\n\t\t\t\t<data android:host=\"yd-comics.com\" />\n\t\t\t\t<data android:host=\"zahard.xyz\" />\n\t\t\t\t<data android:host=\"bymichiby.com\" />\n\t\t\t\t<data android:host=\"carteldemanhwas.com\" />\n\t\t\t\t<data android:host=\"doujins.lat\" />\n\t\t\t\t<data android:host=\"dtupscan.com\" />\n\t\t\t\t<data android:host=\"gremorymangas.com\" />\n\t\t\t\t<data android:host=\"hentaireader.org\" />\n\t\t\t\t<data android:host=\"nakamatoon.com\" />\n\t\t\t\t<data android:host=\"inaripikav.org\" />\n\t\t\t\t<data android:host=\"lectorhentai.com\" />\n\t\t\t\t<data android:host=\"mangamukai.com\" />\n\t\t\t\t<data android:host=\"www.mangatv.net\" />\n\t\t\t\t<data android:host=\"lectormiau.com\" />\n\t\t\t\t<data android:host=\"ragnascan.com\" />\n\t\t\t\t<data android:host=\"raikiscan.com\" />\n\t\t\t\t<data android:host=\"senpaiediciones.com\" />\n\t\t\t\t<data android:host=\"shadowmangas.com\" />\n\t\t\t\t<data android:host=\"skymangas.com\" />\n\t\t\t\t<data android:host=\"tecnoscann.com\" />\n\t\t\t\t<data android:host=\"tenkaiscan.net\" />\n\t\t\t\t<data android:host=\"traduccionesmoonlight.com\" />\n\t\t\t\t<data android:host=\"tresdaos.com\" />\n\t\t\t\t<data android:host=\"tumanhwas.com\" />\n\t\t\t\t<data android:host=\"ukiyotoon.com\" />\n\t\t\t\t<data android:host=\"banana-scan.com\" />\n\t\t\t\t<data android:host=\"www.etheralradiance.eu\" />\n\t\t\t\t<data android:host=\"japscans.fr\" />\n\t\t\t\t<data android:host=\"www.lelmanga.com\" />\n\t\t\t\t<data android:host=\"gloryscans.fr\" />\n\t\t\t\t<data android:host=\"lunarscans.fr\" />\n\t\t\t\t<data android:host=\"mangas-scans.com\" />\n\t\t\t\t<data android:host=\"www.pantheon-scan.fr\" />\n\t\t\t\t<data android:host=\"phenixscans.fr\" />\n\t\t\t\t<data android:host=\"pornhwascans.fr\" />\n\t\t\t\t<data android:host=\"www.revolutionscantrad.com\" />\n\t\t\t\t<data android:host=\"rimuscans.fr\" />\n\t\t\t\t<data android:host=\"sushiscan.net\" />\n\t\t\t\t<data android:host=\"sushiscan.fr\" />\n\t\t\t\t<data android:host=\"www.vfscan.net\" />\n\t\t\t\t<data android:host=\"xxx.revolutionscantrad.com\" />\n\t\t\t\t<data android:host=\"ainzscans.net\" />\n\t\t\t\t<data android:host=\"alceacomic.my.id\" />\n\t\t\t\t<data android:host=\"alterkaiscans.my.id\" />\n\t\t\t\t<data android:host=\"comic21.me\" />\n\t\t\t\t<data android:host=\"comicaso.id\" />\n\t\t\t\t<data android:host=\"cosmicscans.id\" />\n\t\t\t\t<data android:host=\"dojing.net\" />\n\t\t\t\t<data android:host=\"doujindesu.click\" />\n\t\t\t\t<data android:host=\"doujinku.xyz\" />\n\t\t\t\t<data android:host=\"duniakomik.org\" />\n\t\t\t\t<data android:host=\"futari.info\" />\n\t\t\t\t<data android:host=\"kanzenin.info\" />\n\t\t\t\t<data android:host=\"katakomik.my.id\" />\n\t\t\t\t<data android:host=\"kiryuu.org\" />\n\t\t\t\t<data android:host=\"kombatch.cc\" />\n\t\t\t\t<data android:host=\"komikav.com\" />\n\t\t\t\t<data android:host=\"komikdewasa.online\" />\n\t\t\t\t<data android:host=\"komikremaja.icu\" />\n\t\t\t\t<data android:host=\"komikgo.xyz\" />\n\t\t\t\t<data android:host=\"komikindo.moe\" />\n\t\t\t\t<data android:host=\"komikindo.co\" />\n\t\t\t\t<data android:host=\"komikmu.fun\" />\n\t\t\t\t<data android:host=\"komiklovers.com\" />\n\t\t\t\t<data android:host=\"komikpix.com\" />\n\t\t\t\t<data android:host=\"komikpoi.com\" />\n\t\t\t\t<data android:host=\"komiksan.link\" />\n\t\t\t\t<data android:host=\"komiksay.info\" />\n\t\t\t\t<data android:host=\"komiktap.info\" />\n\t\t\t\t<data android:host=\"komikcast.cz\" />\n\t\t\t\t<data android:host=\"komiklokal.shop\" />\n\t\t\t\t<data android:host=\"komikstation.co\" />\n\t\t\t\t<data android:host=\"komiku.com\" />\n\t\t\t\t<data android:host=\"kyumik.com\" />\n\t\t\t\t<data android:host=\"www.lianscans.com\" />\n\t\t\t\t<data android:host=\"mangashiro.me\" />\n\t\t\t\t<data android:host=\"mangasusuku.xyz\" />\n\t\t\t\t<data android:host=\"mangatale.id\" />\n\t\t\t\t<data android:host=\"mangadop.net\" />\n\t\t\t\t<data android:host=\"mangakita.id\" />\n\t\t\t\t<data android:host=\"mangakyo.vip\" />\n\t\t\t\t<data android:host=\"www.nowheartruth.com\" />\n\t\t\t\t<data android:host=\"komikcinta.icu\" />\n\t\t\t\t<data android:host=\"manhwaindo.net\" />\n\t\t\t\t<data android:host=\"manhwaland.vip\" />\n\t\t\t\t<data android:host=\"www.manhwaland.ink\" />\n\t\t\t\t<data android:host=\"manhwalist.org\" />\n\t\t\t\t<data android:host=\"manhwablue.com\" />\n\t\t\t\t<data android:host=\"manhwadesu.cc\" />\n\t\t\t\t<data android:host=\"manhwaku.id\" />\n\t\t\t\t<data android:host=\"manhwalist.in\" />\n\t\t\t\t<data android:host=\"tenshi.id\" />\n\t\t\t\t<data android:host=\"mihentai.com\" />\n\t\t\t\t<data android:host=\"www.monzeekomik.my.id\" />\n\t\t\t\t<data android:host=\"natsu.id\" />\n\t\t\t\t<data android:host=\"ngomik.mom\" />\n\t\t\t\t<data android:host=\"noromax.my.id\" />\n\t\t\t\t<data android:host=\"otsugami.id\" />\n\t\t\t\t<data android:host=\"sekaikomik.guru\" />\n\t\t\t\t<data android:host=\"sektedoujin.cc\" />\n\t\t\t\t<data android:host=\"sheakomik.com\" />\n\t\t\t\t<data android:host=\"shirakami.xyz\" />\n\t\t\t\t<data android:host=\"sirenkomik.my.id\" />\n\t\t\t\t<data android:host=\"soulscans.my.id\" />\n\t\t\t\t<data android:host=\"tukangkomik.id\" />\n\t\t\t\t<data android:host=\"warungkomik.com\" />\n\t\t\t\t<data android:host=\"westmanga.fun\" />\n\t\t\t\t<data android:host=\"yumekomik.com\" />\n\t\t\t\t<data android:host=\"yurilab.my.id\" />\n\t\t\t\t<data android:host=\"www.walpurgiscan.it\" />\n\t\t\t\t<data android:host=\"www.witcomics.net\" />\n\t\t\t\t<data android:host=\"mangajp.top\" />\n\t\t\t\t<data android:host=\"manga-mate.org\" />\n\t\t\t\t<data android:host=\"rawkuma.com\" />\n\t\t\t\t<data android:host=\"skanlacje-feniksy.pl\" />\n\t\t\t\t<data android:host=\"seitacelestial.com\" />\n\t\t\t\t<data android:host=\"diskusscan.com\" />\n\t\t\t\t<data android:host=\"franxxmangas.net\" />\n\t\t\t\t<data android:host=\"hikariscan.org\" />\n\t\t\t\t<data android:host=\"irisscanlator.com.br\" />\n\t\t\t\t<data android:host=\"mangaschan.net\" />\n\t\t\t\t<data android:host=\"mangasonline.cc\" />\n\t\t\t\t<data android:host=\"origami-orpheans.com\" />\n\t\t\t\t<data android:host=\"silencescan.com.br\" />\n\t\t\t\t<data android:host=\"sssscanlator.com.br\" />\n\t\t\t\t<data android:host=\"tsundoku.com.br\" />\n\t\t\t\t<data android:host=\"doujin69.com\" />\n\t\t\t\t<data android:host=\"www.dragon-manga.com\" />\n\t\t\t\t<data android:host=\"ecchi-doujin.com\" />\n\t\t\t\t<data android:host=\"www.inu-manga.com\" />\n\t\t\t\t<data android:host=\"mangalami.com\" />\n\t\t\t\t<data android:host=\"mafia-manga.com\" />\n\t\t\t\t<data android:host=\"makimaaaaa.com\" />\n\t\t\t\t<data android:host=\"manga168.net\" />\n\t\t\t\t<data android:host=\"manga689.com\" />\n\t\t\t\t<data android:host=\"www.mangakimi.com\" />\n\t\t\t\t<data android:host=\"manga-moons.net\" />\n\t\t\t\t<data android:host=\"www.ntr-manga.com\" />\n\t\t\t\t<data android:host=\"popsmanga.com\" />\n\t\t\t\t<data android:host=\"reapertrans.com\" />\n\t\t\t\t<data android:host=\"www.xn--l3c0azab5a2gta.com\" />\n\t\t\t\t<data android:host=\"www.tanuki-manga.com\" />\n\t\t\t\t<data android:host=\"www.thaimanga.net\" />\n\t\t\t\t<data android:host=\"toomtam-manga.com\" />\n\t\t\t\t<data android:host=\"toonhunter.com\" />\n\t\t\t\t<data android:host=\"manga.adonisfansub.com\" />\n\t\t\t\t<data android:host=\"adumanga.com\" />\n\t\t\t\t<data android:host=\"afroditscans.com\" />\n\t\t\t\t<data android:host=\"arcurafansub.com\" />\n\t\t\t\t<data android:host=\"asemifansub.com\" />\n\t\t\t\t<data android:host=\"athenamanga.com\" />\n\t\t\t\t<data android:host=\"ayatoon.com\" />\n\t\t\t\t<data android:host=\"culturesubs.com\" />\n\t\t\t\t<data android:host=\"gaiatoon.com\" />\n\t\t\t\t<data android:host=\"golgebahcesi.com\" />\n\t\t\t\t<data android:host=\"mangagezgini.net\" />\n\t\t\t\t<data android:host=\"mangakings.com.tr\" />\n\t\t\t\t<data android:host=\"mangasiginagi.com\" />\n\t\t\t\t<data android:host=\"mangacim.com\" />\n\t\t\t\t<data android:host=\"mangaefendisi.net\" />\n\t\t\t\t<data android:host=\"mangaokutr.com\" />\n\t\t\t\t<data android:host=\"moondaisyscans.biz\" />\n\t\t\t\t<data android:host=\"nirvanamanga.com\" />\n\t\t\t\t<data android:host=\"noxscans.com\" />\n\t\t\t\t<data android:host=\"nyxmanga.com\" />\n\t\t\t\t<data android:host=\"www.patimanga.com\" />\n\t\t\t\t<data android:host=\"prunusscans.com\" />\n\t\t\t\t<data android:host=\"www.raindropteamfan.com\" />\n\t\t\t\t<data android:host=\"robinmanga.com\" />\n\t\t\t\t<data android:host=\"sereinscan.com\" />\n\t\t\t\t<data android:host=\"shijiescans.com\" />\n\t\t\t\t<data android:host=\"summertoon.biz\" />\n\t\t\t\t<data android:host=\"www.tarotscans.com\" />\n\t\t\t\t<data android:host=\"tempestfansub.net\" />\n\t\t\t\t<data android:host=\"adumanga.com\" />\n\t\t\t\t<data android:host=\"tempestfansub.com\" />\n\t\t\t\t<data android:host=\"zenithscans.com\" />\n\t\t\t\t<data android:host=\"mangaworld.ac\" />\n\t\t\t\t<data android:host=\"mangaworldadult.net\" />\n\t\t\t\t<data android:host=\"onma.me\" />\n\t\t\t\t<data android:host=\"bananascans.com\" />\n\t\t\t\t<data android:host=\"readcomicsonline.ru\" />\n\t\t\t\t<data android:host=\"www.anzmangashd.com\" />\n\t\t\t\t<data android:host=\"mangadoor.com\" />\n\t\t\t\t<data android:host=\"bentoscan.com\" />\n\t\t\t\t<data android:host=\"frscans.com\" />\n\t\t\t\t<data android:host=\"jpmangas.xyz\" />\n\t\t\t\t<data android:host=\"lirescanvf.com\" />\n\t\t\t\t<data android:host=\"mangascan-fr.net\" />\n\t\t\t\t<data android:host=\"scan-manga.me\" />\n\t\t\t\t<data android:host=\"scanmanga-vf.me\" />\n\t\t\t\t<data android:host=\"www.scan-vf.net\" />\n\t\t\t\t<data android:host=\"komikid.com\" />\n\t\t\t\t<data android:host=\"mangaid.click\" />\n\t\t\t\t<data android:host=\"www.mangadenizi.net\" />\n\t\t\t\t<data android:host=\"manga4life.com\" />\n\t\t\t\t<data android:host=\"mangasee123.com\" />\n\t\t\t\t<data android:host=\"berserkscan.com\" />\n\t\t\t\t<data android:host=\"bluelockscan.com\" />\n\t\t\t\t<data android:host=\"20thcenturyboys.fr\" />\n\t\t\t\t<data android:host=\"chainsawman-scan.com\" />\n\t\t\t\t<data android:host=\"dandadan.fr\" />\n\t\t\t\t<data android:host=\"demonslayerscan.com\" />\n\t\t\t\t<data android:host=\"drstone.fr\" />\n\t\t\t\t<data android:host=\"fireforce.fr\" />\n\t\t\t\t<data android:host=\"haikyuu.fr\" />\n\t\t\t\t<data android:host=\"hellsparadisescan.com\" />\n\t\t\t\t<data android:host=\"hunterxhunterscan.com\" />\n\t\t\t\t<data android:host=\"kaijuno8.fr\" />\n\t\t\t\t<data android:host=\"kingdomscan.com\" />\n\t\t\t\t<data android:host=\"mashlescan.fr\" />\n\t\t\t\t<data android:host=\"myheroacademiascan.com\" />\n\t\t\t\t<data android:host=\"onepiecescan.fr\" />\n\t\t\t\t<data android:host=\"onepunchmanscan.com\" />\n\t\t\t\t<data android:host=\"oshinoko.fr\" />\n\t\t\t\t<data android:host=\"sakamotodays.fr\" />\n\t\t\t\t<data android:host=\"scanboruto.fr\" />\n\t\t\t\t<data android:host=\"scanjujutsukaisen.com\" />\n\t\t\t\t<data android:host=\"snkscan.com\" />\n\t\t\t\t<data android:host=\"tokyorevengers.fr\" />\n\t\t\t\t<data android:host=\"vinlandsaga.fr\" />\n\t\t\t\t<data android:host=\"otakusan.net\" />\n\t\t\t\t<data android:host=\"otakusan.net\" />\n\t\t\t\t<data android:host=\"fmteam.fr\" />\n\t\t\t\t<data android:host=\"hni-scantrad.net\" />\n\t\t\t\t<data android:host=\"reader.gtothegreatsite.net\" />\n\t\t\t\t<data android:host=\"ddt.hastateam.com\" />\n\t\t\t\t<data android:host=\"reader.hastateam.com\" />\n\t\t\t\t<data android:host=\"lupiteam.net\" />\n\t\t\t\t<data android:host=\"www.phoenixscans.com\" />\n\t\t\t\t<data android:host=\"tuttoanimemanga.net\" />\n\t\t\t\t<data android:host=\"www.brmangas.net\" />\n\t\t\t\t<data android:host=\"lermanga.org\" />\n\t\t\t\t<data android:host=\"lermangaonline.com.br\" />\n\t\t\t\t<data android:host=\"luratoons.com\" />\n\t\t\t\t<data android:host=\"mangaonline.biz\" />\n\t\t\t\t<data android:host=\"www.muitohentai.com\" />\n\t\t\t\t<data android:host=\"onepieceex.net\" />\n\t\t\t\t<data android:host=\"yugenmangasbr.voblog.xyz\" />\n\t\t\t\t<data android:host=\"acomics.ru\" />\n\t\t\t\t<data android:host=\"desu.win\" />\n\t\t\t\t<data android:host=\"desu.me\" />\n\t\t\t\t<data android:host=\"manga.wtf\" />\n\t\t\t\t<data android:host=\"x.nude-moon.fun\" />\n\t\t\t\t<data android:host=\"nude-moon.org\" />\n\t\t\t\t<data android:host=\"nude-moon.net\" />\n\t\t\t\t<data android:host=\"remanga.org\" />\n\t\t\t\t<data android:host=\"реманга.орг\" />\n\t\t\t\t<data android:host=\"z.ahen.me\" />\n\t\t\t\t<data android:host=\"20.allhen.online\" />\n\t\t\t\t<data android:host=\"24.allhen.online\" />\n\t\t\t\t<data android:host=\"z.allhen.online\" />\n\t\t\t\t<data android:host=\"2023.allhen.online\" />\n\t\t\t\t<data android:host=\"24.mintmanga.one\" />\n\t\t\t\t<data android:host=\"mintmanga.live\" />\n\t\t\t\t<data android:host=\"mintmanga.com\" />\n\t\t\t\t<data android:host=\"zz.readmanga.io\" />\n\t\t\t\t<data android:host=\"readmanga.live\" />\n\t\t\t\t<data android:host=\"readmanga.io\" />\n\t\t\t\t<data android:host=\"readmanga.me\" />\n\t\t\t\t<data android:host=\"seimanga.me\" />\n\t\t\t\t<data android:host=\"selfmanga.live\" />\n\t\t\t\t<data android:host=\"web.usagi.one\" />\n\t\t\t\t<data android:host=\"xxxx.henchan.pro\" />\n\t\t\t\t<data android:host=\"xxl.hentaichan.live\" />\n\t\t\t\t<data android:host=\"xxx.henchan.pro\" />\n\t\t\t\t<data android:host=\"y.hentaichan.live\" />\n\t\t\t\t<data android:host=\"xxx.hentaichan.live\" />\n\t\t\t\t<data android:host=\"xx.hentaichan.live\" />\n\t\t\t\t<data android:host=\"x.henchan.pro\" />\n\t\t\t\t<data android:host=\"hentaichan.live\" />\n\t\t\t\t<data android:host=\"manga-chan.me\" />\n\t\t\t\t<data android:host=\"v3.yaoi-chan.me\" />\n\t\t\t\t<data android:host=\"v2.yaoi-chan.me\" />\n\t\t\t\t<data android:host=\"v1.yaoi-chan.me\" />\n\t\t\t\t<data android:host=\"yaoi-chan.me\" />\n\t\t\t\t<data android:host=\"lib.social\" />\n\t\t\t\t<data android:host=\"lib.social\" />\n\t\t\t\t<data android:host=\"lib.social\" />\n\t\t\t\t<data android:host=\"www.mangafr.org\" />\n\t\t\t\t<data android:host=\"scan-trad.com\" />\n\t\t\t\t<data android:host=\"scanvf.org\" />\n\t\t\t\t<data android:host=\"manga-italia.com\" />\n\t\t\t\t<data android:host=\"scanita.org\" />\n\t\t\t\t<data android:host=\"mangabr.net\" />\n\t\t\t\t<data android:host=\"manga-terra.com\" />\n\t\t\t\t<data android:host=\"www.gufengmh9.com\" />\n\t\t\t\t<data android:host=\"manga-ay.com\" />\n\t\t\t\t<data android:host=\"sadscans.com\" />\n\t\t\t\t<data android:host=\"trwebtoon.com\" />\n\t\t\t\t<data android:host=\"www.yaoiflix.dev\" />\n\t\t\t\t<data android:host=\"hentaiukr.com\" />\n\t\t\t\t<data android:host=\"honey-manga.com.ua\" />\n\t\t\t\t<data android:host=\"manga.in.ua\" />\n\t\t\t\t<data android:host=\"blogtruyenmoi.com\" />\n\t\t\t\t<data android:host=\"blogtruyen.vn\" />\n\t\t\t\t<data android:host=\"cuutruyen.net\" />\n\t\t\t\t<data android:host=\"nettrom.com\" />\n\t\t\t\t<data android:host=\"hetcuutruyen.net\" />\n\t\t\t\t<data android:host=\"cuutruyent9sv7.xyz\" />\n\t\t\t\t<data android:host=\"hentaiayame.net\" />\n\t\t\t\t<data android:host=\"lxmanga.life\" />\n\t\t\t\t<data android:host=\"truyenqqto.com\" />\n\t\t\t\t<data android:host=\"yurineko.net\" />\n\t\t\t\t<data android:host=\"vercomicsporno.com\" />\n\t\t\t\t<data android:host=\"vermangasporno.com\" />\n\t\t\t\t<data android:host=\"xoxocomic.com\" />\n\t\t\t\t<data android:host=\"mangaraw.xyz\" />\n\t\t\t\t<data android:host=\"www.nettruyenupp.com\" />\n\t\t\t\t<data android:host=\"nettruyenww.com\" />\n\t\t\t\t<data android:host=\"nettruyenx.com\" />\n\t\t\t\t<data android:host=\"nettruyenhe.com\" />\n\t\t\t\t<data android:host=\"nettruyenll.com\" />\n\t\t\t\t<data android:host=\"nettruyenssr.com\" />\n\t\t\t\t<data android:host=\"nhattruyenvn.com\" />\n\t\t\t\t<data android:host=\"www.nhattruyenss.net\" />\n\t\t\t\t<data android:host=\"www.arabsdoujin.online\" />\n\t\t\t\t<data android:host=\"hijala.blogspot.com\" />\n\t\t\t\t<data android:host=\"loner-tl.blogspot.com\" />\n\t\t\t\t<data android:host=\"manga-ai-land.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.mangahub.link\" />\n\t\t\t\t<data android:host=\"www.manga-soul.com\" />\n\t\t\t\t<data android:host=\"manhatok.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.xsano-manga.com\" />\n\t\t\t\t<data android:host=\"yokai-team.blogspot.com\" />\n\t\t\t\t<data android:host=\"yurimoonsub.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.aiyumanhua.com\" />\n\t\t\t\t<data android:host=\"datgarscanlation.blogspot.com\" />\n\t\t\t\t<data android:host=\"gistamishousefansub.blogspot.com\" />\n\t\t\t\t<data android:host=\"nekoscanlationlector.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.asupankomik.my.id\" />\n\t\t\t\t<data android:host=\"ichiromanga.my.id\" />\n\t\t\t\t<data android:host=\"www.kishisan.site\" />\n\t\t\t\t<data android:host=\"klmanhua.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.komikges.my.id\" />\n\t\t\t\t<data android:host=\"www.komikrealm.my.id\" />\n\t\t\t\t<data android:host=\"www.magerin.com\" />\n\t\t\t\t<data android:host=\"www.mikoroku.web.id\" />\n\t\t\t\t<data android:host=\"ngamenkomik05.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.nimemob.my.id\" />\n\t\t\t\t<data android:host=\"www.re-yume.my.id\" />\n\t\t\t\t<data android:host=\"shiyurasub.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.sobatmanku19.cab\" />\n\t\t\t\t<data android:host=\"www.tooncubus.top\" />\n\t\t\t\t<data android:host=\"www.ulascomic.xyz\" />\n\t\t\t\t<data android:host=\"www.animexnovel.com\" />\n\t\t\t\t<data android:host=\"elevenscanlator.blogspot.com\" />\n\t\t\t\t<data android:host=\"galaxscanlator.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.guildatierdraw.top\" />\n\t\t\t\t<data android:host=\"heckscans.blogspot.com\" />\n\t\t\t\t<data android:host=\"ler999.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.maxgsscan.online\" />\n\t\t\t\t<data android:host=\"raysscan.blogspot.com\" />\n\t\t\t\t<data android:host=\"solooscan.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.temakimangas.xyz\" />\n\t\t\t\t<data android:host=\"www.tyrantscans.com\" />\n\t\t\t\t<data android:host=\"wolfscanbr.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.yaoifanclub.com\" />\n\t\t\t\t<data android:host=\"www.zscanlation.com\" />\n\t\t\t\t<data android:host=\"www.epikman.ga\" />\n\t\t\t\t<data android:host=\"www.hyperionscans.site\" />\n\t\t\t\t<data android:host=\"mikrokosmosfb.blogspot.com\" />\n\t\t\t\t<data android:host=\"shadowceviri.blogspot.com\" />\n\t\t\t\t<data android:host=\"snscoeurturkey.blogspot.com\" />\n\t\t\t\t<data android:host=\"www.baozimh.com\" />\n\t\t\t\t<data android:host=\"hensekai.com\" />\n\t\t\t\t<data android:host=\"komikindo.info\" />\n\t\t\t\t<data android:host=\"www.maid.my.id\" />\n\t\t\t\t<data android:host=\"shirodoujin.com\" />\n\n\t\t\t</intent-filter>\n\n\t\t</activity-alias>\n\n\t</application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/assets/isrgrootx1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\nWhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\nZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\nh77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\nA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\nT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\nB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\nB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\nKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\nOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\njh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\nqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\nrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\nhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\nubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\nNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\nORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\nTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\njNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\noyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\nmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\nemyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/AlternativesUseCase.kt",
    "content": "package org.koitharu.kotatsu.alternatives.domain\n\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.emptyFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.ext.toLocale\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.search.domain.SearchV2Helper\nimport java.util.Locale\nimport javax.inject.Inject\n\nprivate const val MAX_PARALLELISM = 4\n\nclass AlternativesUseCase @Inject constructor(\n\tprivate val sourcesRepository: MangaSourcesRepository,\n\tprivate val searchHelperFactory: SearchV2Helper.Factory,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\n\tsuspend operator fun invoke(manga: Manga, throughDisabledSources: Boolean): Flow<Manga> {\n\t\tval sources = getSources(manga.source, throughDisabledSources)\n\t\tif (sources.isEmpty()) {\n\t\t\treturn emptyFlow()\n\t\t}\n\t\tval semaphore = Semaphore(MAX_PARALLELISM)\n\t\treturn channelFlow {\n\t\t\tfor (source in sources) {\n\t\t\t\tlaunch {\n\t\t\t\t\tval searchHelper = searchHelperFactory.create(source)\n\t\t\t\t\tval list = runCatchingCancellable {\n\t\t\t\t\t\tsemaphore.withPermit {\n\t\t\t\t\t\t\tsearchHelper(manga.title, SearchKind.TITLE)?.manga\n\t\t\t\t\t\t}\n\t\t\t\t\t}.getOrNull()\n\t\t\t\t\tlist?.forEach { m ->\n\t\t\t\t\t\tif (m.id != manga.id) {\n\t\t\t\t\t\t\tlaunch {\n\t\t\t\t\t\t\t\tval details = runCatchingCancellable {\n\t\t\t\t\t\t\t\t\tmangaRepositoryFactory.create(m.source).getDetails(m)\n\t\t\t\t\t\t\t\t}.getOrDefault(m)\n\t\t\t\t\t\t\t\tsend(details)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun getSources(ref: MangaSource, disabled: Boolean): List<MangaSource> = if (disabled) {\n\t\tsourcesRepository.getDisabledSources()\n\t} else {\n\t\tsourcesRepository.getEnabledSources()\n\t}.sortedByDescending { it.priority(ref) }\n\n\tprivate fun MangaSource.priority(ref: MangaSource): Int {\n\t\tvar res = 0\n\t\tif (this is MangaParserSource && ref is MangaParserSource) {\n\t\t\tif (locale == ref.locale) {\n\t\t\t\tres += 4\n\t\t\t} else if (locale.toLocale() == Locale.getDefault()) {\n\t\t\t\tres += 2\n\t\t\t}\n\t\t\tif (contentType == ref.contentType) {\n\t\t\t\tres++\n\t\t\t}\n\t\t}\n\t\treturn res\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/AutoFixUseCase.kt",
    "content": "package org.koitharu.kotatsu.alternatives.domain\n\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.filter\nimport kotlinx.coroutines.flow.lastOrNull\nimport kotlinx.coroutines.flow.runningFold\nimport kotlinx.coroutines.flow.transformWhile\nimport kotlinx.coroutines.flow.withIndex\nimport kotlinx.coroutines.launch\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport org.koitharu.kotatsu.core.model.chaptersCount\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.ext.concat\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport kotlin.coroutines.cancellation.CancellationException\n\nclass AutoFixUseCase @Inject constructor(\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val alternativesUseCase: AlternativesUseCase,\n\tprivate val migrateUseCase: MigrateUseCase,\n\tprivate val mangaDataRepository: MangaDataRepository,\n) {\n\n\tsuspend operator fun invoke(mangaId: Long): Pair<Manga, Manga?> {\n\t\tval seed = checkNotNull(\n\t\t\tmangaDataRepository.findMangaById(mangaId, withChapters = true),\n\t\t) { \"Manga $mangaId not found\" }.getDetailsSafe()\n\t\tif (seed.isHealthy()) {\n\t\t\treturn seed to null // no fix required\n\t\t}\n\t\tval replacement = alternativesUseCase(seed, throughDisabledSources = false)\n\t\t\t.concat(alternativesUseCase(seed, throughDisabledSources = true))\n\t\t\t.filter { it.isHealthy() }\n\t\t\t.runningFold<Manga, Manga?>(null) { best, candidate ->\n\t\t\t\tif (best == null || best < candidate) {\n\t\t\t\t\tcandidate\n\t\t\t\t} else {\n\t\t\t\t\tbest\n\t\t\t\t}\n\t\t\t}.selectLastWithTimeout(4, 40, TimeUnit.SECONDS)\n\t\tmigrateUseCase(seed, replacement ?: throw NoAlternativesException(ParcelableManga(seed)))\n\t\treturn seed to replacement\n\t}\n\n\tprivate suspend fun Manga.isHealthy(): Boolean = runCatchingCancellable {\n\t\tval repo = mangaRepositoryFactory.create(source)\n\t\tval details = if (this.chapters != null) this else repo.getDetails(this)\n\t\tval firstChapter = details.chapters?.firstOrNull() ?: return@runCatchingCancellable false\n\t\tval pageUrl = repo.getPageUrl(repo.getPages(firstChapter).first())\n\t\tpageUrl.toHttpUrlOrNull() != null\n\t}.getOrDefault(false)\n\n\tprivate suspend fun Manga.getDetailsSafe() = runCatchingCancellable {\n\t\tmangaRepositoryFactory.create(source).getDetails(this)\n\t}.getOrDefault(this)\n\n\tprivate operator fun Manga.compareTo(other: Manga) = chaptersCount().compareTo(other.chaptersCount())\n\n\t@Suppress(\"UNCHECKED_CAST\", \"OPT_IN_USAGE\")\n\tprivate suspend fun <T> Flow<T>.selectLastWithTimeout(\n\t\tminCount: Int,\n\t\ttimeout: Long,\n\t\ttimeUnit: TimeUnit\n\t): T? = channelFlow<T?> {\n\t\tvar lastValue: T? = null\n\t\tlaunch {\n\t\t\tdelay(timeUnit.toMillis(timeout))\n\t\t\tclose(InternalTimeoutException(lastValue))\n\t\t}\n\t\twithIndex().transformWhile { (index, value) ->\n\t\t\tlastValue = value\n\t\t\temit(value)\n\t\t\tindex < minCount && !isClosedForSend\n\t\t}.collect {\n\t\t\tsend(it)\n\t\t}\n\t}.catch { e ->\n\t\tif (e is InternalTimeoutException) {\n\t\t\temit(e.value as T?)\n\t\t} else {\n\t\t\tthrow e\n\t\t}\n\t}.lastOrNull()\n\n\tclass NoAlternativesException(val seed: ParcelableManga) : NoSuchElementException()\n\n\tprivate class InternalTimeoutException(val value: Any?) : CancellationException()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/MigrateUseCase.kt",
    "content": "package org.koitharu.kotatsu.alternatives.domain\n\nimport androidx.room.withTransaction\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.model.getPreferredBranch\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.details.domain.ProgressUpdateUseCase\nimport org.koitharu.kotatsu.history.data.HistoryEntity\nimport org.koitharu.kotatsu.history.data.toMangaHistory\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport org.koitharu.kotatsu.tracker.data.TrackEntity\nimport javax.inject.Inject\n\nclass MigrateUseCase\n@Inject\nconstructor(\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val mangaDataRepository: MangaDataRepository,\n\tprivate val database: MangaDatabase,\n\tprivate val progressUpdateUseCase: ProgressUpdateUseCase,\n\tprivate val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,\n) {\n\tsuspend operator fun invoke(\n\t\toldManga: Manga,\n\t\tnewManga: Manga,\n\t) {\n\t\tval oldDetails = if (oldManga.chapters.isNullOrEmpty()) {\n\t\t\trunCatchingCancellable {\n\t\t\t\tmangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)\n\t\t\t}.getOrDefault(oldManga)\n\t\t} else {\n\t\t\toldManga\n\t\t}\n\t\tval newDetails = if (newManga.chapters.isNullOrEmpty()) {\n\t\t\tmangaRepositoryFactory.create(newManga.source).getDetails(newManga)\n\t\t} else {\n\t\t\tnewManga\n\t\t}\n\t\tmangaDataRepository.storeManga(newDetails, replaceExisting = true)\n\t\tdatabase.withTransaction {\n\t\t\t// replace favorites\n\t\t\tval favoritesDao = database.getFavouritesDao()\n\t\t\tval oldFavourites = favoritesDao.findAllRaw(oldDetails.id)\n\t\t\tif (oldFavourites.isNotEmpty()) {\n\t\t\t\tfavoritesDao.delete(oldManga.id)\n\t\t\t\tfor (f in oldFavourites) {\n\t\t\t\t\tval e =\n\t\t\t\t\t\tf.copy(\n\t\t\t\t\t\t\tmangaId = newManga.id,\n\t\t\t\t\t\t)\n\t\t\t\t\tfavoritesDao.upsert(e)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// replace history\n\t\t\tval historyDao = database.getHistoryDao()\n\t\t\tval oldHistory = historyDao.find(oldDetails.id)\n\t\t\tval newHistory =\n\t\t\t\tif (oldHistory != null) {\n\t\t\t\t\tval newHistory = makeNewHistory(oldDetails, newDetails, oldHistory)\n\t\t\t\t\thistoryDao.delete(oldDetails.id)\n\t\t\t\t\thistoryDao.upsert(newHistory)\n\t\t\t\t\tnewHistory\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t// track\n\t\t\tval tracksDao = database.getTracksDao()\n\t\t\tval oldTrack = tracksDao.find(oldDetails.id)\n\t\t\tif (oldTrack != null) {\n\t\t\t\tval lastChapter = newDetails.chapters?.lastOrNull()\n\t\t\t\tval newTrack =\n\t\t\t\t\tTrackEntity(\n\t\t\t\t\t\tmangaId = newDetails.id,\n\t\t\t\t\t\tlastChapterId = lastChapter?.id ?: 0L,\n\t\t\t\t\t\tnewChapters = 0,\n\t\t\t\t\t\tlastCheckTime = System.currentTimeMillis(),\n\t\t\t\t\t\tlastChapterDate = lastChapter?.uploadDate ?: 0L,\n\t\t\t\t\t\tlastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,\n\t\t\t\t\t\tlastError = null,\n\t\t\t\t\t)\n\t\t\t\ttracksDao.delete(oldDetails.id)\n\t\t\t\ttracksDao.upsert(newTrack)\n\t\t\t}\n\t\t\t// scrobbling\n\t\t\tfor (scrobbler in scrobblers) {\n\t\t\t\tif (!scrobbler.isEnabled) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tval prevInfo = scrobbler.getScrobblingInfoOrNull(oldDetails.id) ?: continue\n\t\t\t\tscrobbler.unregisterScrobbling(oldDetails.id)\n\t\t\t\tscrobbler.linkManga(newDetails.id, prevInfo.targetId)\n\t\t\t\tscrobbler.updateScrobblingInfo(\n\t\t\t\t\tmangaId = newDetails.id,\n\t\t\t\t\trating = prevInfo.rating,\n\t\t\t\t\tstatus =\n\t\t\t\t\t\tprevInfo.status ?: when {\n\t\t\t\t\t\t\tnewHistory == null -> ScrobblingStatus.PLANNED\n\t\t\t\t\t\t\tnewHistory.percent == 1f -> ScrobblingStatus.COMPLETED\n\t\t\t\t\t\t\telse -> ScrobblingStatus.READING\n\t\t\t\t\t\t},\n\t\t\t\t\tcomment = prevInfo.comment,\n\t\t\t\t)\n\t\t\t\tif (newHistory != null) {\n\t\t\t\t\tscrobbler.scrobble(\n\t\t\t\t\t\tmanga = newDetails,\n\t\t\t\t\t\tchapterId = newHistory.chapterId,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tprogressUpdateUseCase(newManga)\n\t}\n\n\tprivate fun makeNewHistory(\n\t\toldManga: Manga,\n\t\tnewManga: Manga,\n\t\thistory: HistoryEntity,\n\t): HistoryEntity {\n\t\tif (oldManga.chapters.isNullOrEmpty()) { // probably broken manga/source\n\t\t\tval branch = newManga.getPreferredBranch(null)\n\t\t\tval chapters = checkNotNull(newManga.getChapters(branch))\n\t\t\tval currentChapter =\n\t\t\t\tif (history.percent in 0f..1f) {\n\t\t\t\t\tchapters[(chapters.lastIndex * history.percent).toInt()]\n\t\t\t\t} else {\n\t\t\t\t\tchapters.first()\n\t\t\t\t}\n\t\t\treturn HistoryEntity(\n\t\t\t\tmangaId = newManga.id,\n\t\t\t\tcreatedAt = history.createdAt,\n\t\t\t\tupdatedAt = history.updatedAt,\n\t\t\t\tchapterId = currentChapter.id,\n\t\t\t\tpage = history.page,\n\t\t\t\tscroll = history.scroll,\n\t\t\t\tpercent = history.percent,\n\t\t\t\tdeletedAt = 0,\n\t\t\t\tchaptersCount = chapters.count { it.branch == currentChapter.branch },\n\t\t\t)\n\t\t}\n\t\tval branch = oldManga.getPreferredBranch(history.toMangaHistory())\n\t\tval oldChapters = checkNotNull(oldManga.getChapters(branch))\n\t\tvar index = oldChapters.indexOfFirst { it.id == history.chapterId }\n\t\tif (index < 0) {\n\t\t\tindex =\n\t\t\t\tif (history.percent in 0f..1f) {\n\t\t\t\t\t(oldChapters.lastIndex * history.percent).toInt()\n\t\t\t\t} else {\n\t\t\t\t\t0\n\t\t\t\t}\n\t\t}\n\t\tval newChapters = checkNotNull(newManga.chapters).groupBy { it.branch }\n\t\tval newBranch =\n\t\t\tif (newChapters.containsKey(branch)) {\n\t\t\t\tbranch\n\t\t\t} else {\n\t\t\t\tnewManga.getPreferredBranch(null)\n\t\t\t}\n\t\tval newChapterId =\n\t\t\tcheckNotNull(newChapters[newBranch])\n\t\t\t\t.let {\n\t\t\t\t\tval oldChapter = oldChapters[index]\n\t\t\t\t\tit.findByNumber(oldChapter.volume, oldChapter.number) ?: it.getOrNull(index) ?: it.last()\n\t\t\t\t}.id\n\n\t\treturn HistoryEntity(\n\t\t\tmangaId = newManga.id,\n\t\t\tcreatedAt = history.createdAt,\n\t\t\tupdatedAt = history.updatedAt,\n\t\t\tchapterId = newChapterId,\n\t\t\tpage = history.page,\n\t\t\tscroll = history.scroll,\n\t\t\tpercent = PROGRESS_NONE,\n\t\t\tdeletedAt = 0,\n\t\t\tchaptersCount = checkNotNull(newChapters[newBranch]).size,\n\t\t)\n\t}\n\n\tprivate fun List<MangaChapter>.findByNumber(\n\t\tvolume: Int,\n\t\tnumber: Float,\n\t): MangaChapter? =\n\t\tif (number <= 0f) {\n\t\t\tnull\n\t\t} else {\n\t\t\tfirstOrNull { it.volume == volume && it.number == number }\n\t\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt",
    "content": "package org.koitharu.kotatsu.alternatives.ui\n\nimport android.text.style.ForegroundColorSpan\nimport androidx.core.content.ContextCompat\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.inSpans\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.LifecycleOwner\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport coil3.request.allowRgb565\nimport coil3.request.crossfade\nimport coil3.request.error\nimport coil3.request.fallback\nimport coil3.request.lifecycle\nimport coil3.request.placeholder\nimport coil3.request.transformations\nimport coil3.transform.RoundedCornersTransformation\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.parser.favicon.faviconUri\nimport org.koitharu.kotatsu.core.ui.image.ChipIconTarget\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.enqueueWith\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.databinding.ItemMangaAlternativeBinding\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport kotlin.math.sign\nimport com.google.android.material.R as materialR\n\nfun alternativeAD(\n\tcoil: ImageLoader,\n\tlifecycleOwner: LifecycleOwner,\n\tlistener: OnListItemClickListener<MangaAlternativeModel>,\n) = adapterDelegateViewBinding<MangaAlternativeModel, ListModel, ItemMangaAlternativeBinding>(\n\t{ inflater, parent -> ItemMangaAlternativeBinding.inflate(inflater, parent, false) },\n) {\n\n\tval colorGreen = ContextCompat.getColor(context, R.color.common_green)\n\tval colorRed = ContextCompat.getColor(context, R.color.common_red)\n\tval clickListener = AdapterDelegateClickListenerAdapter(this, listener)\n\titemView.setOnClickListener(clickListener)\n\tbinding.buttonMigrate.setOnClickListener(clickListener)\n\tbinding.chipSource.setOnClickListener(clickListener)\n\n\tbind { payloads ->\n\t\tbinding.textViewTitle.text = item.mangaModel.title\n\t\twith(binding.iconsView) {\n\t\t\tclearIcons()\n\t\t\tif (item.mangaModel.isSaved) addIcon(R.drawable.ic_storage)\n\t\t\tif (item.mangaModel.isFavorite) addIcon(R.drawable.ic_heart_outline)\n\t\t\tisVisible = iconsCount > 0\n\t\t}\n\t\tbinding.textViewSubtitle.text = buildSpannedString {\n\t\t\tif (item.chaptersCount > 0) {\n\t\t\t\tappend(\n\t\t\t\t\tcontext.resources.getQuantityStringSafe(\n\t\t\t\t\t\tR.plurals.chapters,\n\t\t\t\t\t\titem.chaptersCount,\n\t\t\t\t\t\titem.chaptersCount,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tappend(context.getString(R.string.no_chapters))\n\t\t\t}\n\t\t\twhen (item.chaptersDiff.sign) {\n\t\t\t\t-1 -> inSpans(ForegroundColorSpan(colorRed)) {\n\t\t\t\t\tappend(\"  ▼ \")\n\t\t\t\t\tappend(item.chaptersDiff.toString())\n\t\t\t\t}\n\n\t\t\t\t1 -> inSpans(ForegroundColorSpan(colorGreen)) {\n\t\t\t\t\tappend(\"  ▲ +\")\n\t\t\t\t\tappend(item.chaptersDiff.toString())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbinding.progressView.setProgress(\n\t\t\titem.mangaModel.progress,\n\t\t\tListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads,\n\t\t)\n\t\tbinding.chipSource.also { chip ->\n\t\t\tchip.text = item.manga.source.getTitle(chip.context)\n\t\t\tImageRequest.Builder(context)\n\t\t\t\t.data(item.manga.source.faviconUri())\n\t\t\t\t.lifecycle(lifecycleOwner)\n\t\t\t\t.crossfade(false)\n\t\t\t\t.size(context.resources.getDimensionPixelSize(materialR.dimen.m3_chip_icon_size))\n\t\t\t\t.target(ChipIconTarget(chip))\n\t\t\t\t.placeholder(R.drawable.ic_web)\n\t\t\t\t.fallback(R.drawable.ic_web)\n\t\t\t\t.error(R.drawable.ic_web)\n\t\t\t\t.mangaSourceExtra(item.manga.source)\n\t\t\t\t.transformations(RoundedCornersTransformation(context.resources.getDimension(R.dimen.chip_icon_corner)))\n\t\t\t\t.allowRgb565(true)\n\t\t\t\t.enqueueWith(coil)\n\t\t}\n\t\tbinding.imageViewCover.setImageAsync(item.manga.coverUrl, item.manga)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt",
    "content": "package org.koitharu.kotatsu.alternatives.ui\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport coil3.ImageLoader\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityAlternativesBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.adapter.buttonFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),\n\tListStateHolderListener,\n\tOnListItemClickListener<MangaAlternativeModel> {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate val viewModel by viewModels<AlternativesViewModel>()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityAlternativesBinding.inflate(layoutInflater))\n\t\tsupportActionBar?.run {\n\t\t\tsetDisplayHomeAsUpEnabled(true)\n\t\t\tsubtitle = viewModel.manga.title\n\t\t}\n\t\tval listAdapter = BaseListAdapter<ListModel>()\n\t\t\t.addDelegate(ListItemType.MANGA_LIST_DETAILED, alternativeAD(coil, this, this))\n\t\t\t.addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null))\n\t\t\t.addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())\n\t\t\t.addDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\t\t.addDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(this))\n\t\twith(viewBinding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, addHorizontalPadding = false))\n\t\t\tadapter = listAdapter\n\t\t}\n\n\t\tviewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))\n\t\tviewModel.list.observe(this, listAdapter)\n\t\tviewModel.onMigrated.observeEvent(this) {\n\t\t\tToast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show()\n\t\t\trouter.openDetails(it)\n\t\t\tfinishAfterTransition()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.recyclerView.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\ttop = barsInsets.top,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onItemClick(item: MangaAlternativeModel, view: View) {\n\t\twhen (view.id) {\n\t\t\tR.id.chip_source -> router.openSearch(item.manga.source, viewModel.manga.title)\n\t\t\tR.id.button_migrate -> confirmMigration(item.manga)\n\t\t\telse -> router.openDetails(item.manga)\n\t\t}\n\t}\n\n\toverride fun onRetryClick(error: Throwable) = viewModel.retry()\n\n\toverride fun onEmptyActionClick() = Unit\n\n\toverride fun onFooterButtonClick() = viewModel.continueSearch()\n\n\tprivate fun confirmMigration(target: Manga) {\n\t\tbuildAlertDialog(this, isCentered = true) {\n\t\t\tsetIcon(R.drawable.ic_replace)\n\t\t\tsetTitle(R.string.manga_migration)\n\t\t\tsetMessage(\n\t\t\t\tgetString(\n\t\t\t\t\tR.string.migrate_confirmation,\n\t\t\t\t\tviewModel.manga.title,\n\t\t\t\t\tviewModel.manga.source.getTitle(context),\n\t\t\t\t\ttarget.title,\n\t\t\t\t\ttarget.source.getTitle(context),\n\t\t\t\t),\n\t\t\t)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.migrate) { _, _ ->\n\t\t\t\tviewModel.migrate(target)\n\t\t\t}\n\t\t}.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesViewModel.kt",
    "content": "package org.koitharu.kotatsu.alternatives.ui\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.alternatives.domain.AlternativesUseCase\nimport org.koitharu.kotatsu.alternatives.domain.MigrateUseCase\nimport org.koitharu.kotatsu.core.model.chaptersCount\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.append\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.ui.model.ButtonFooter\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingFooter\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.MangaGridModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.getOrDefault\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport javax.inject.Inject\n\n@HiltViewModel\nclass AlternativesViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val alternativesUseCase: AlternativesUseCase,\n\tprivate val migrateUseCase: MigrateUseCase,\n\tprivate val mangaListMapper: MangaListMapper,\n) : BaseViewModel() {\n\n\tval manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga\n\n\tprivate var includeDisabledSources = MutableStateFlow(false)\n\tprivate val results = MutableStateFlow<List<MangaAlternativeModel>>(emptyList())\n\n\tprivate var migrationJob: Job? = null\n\tprivate var searchJob: Job? = null\n\n\tprivate val mangaDetails = suspendLazy {\n\t\tmangaRepositoryFactory.create(manga.source).getDetails(manga)\n\t}\n\n\tval onMigrated = MutableEventFlow<Manga>()\n\n\tval list: StateFlow<List<ListModel>> = combine(\n\t\tresults,\n\t\tisLoading,\n\t\tincludeDisabledSources,\n\t) { list, loading, includeDisabled ->\n\t\twhen {\n\t\t\tlist.isEmpty() -> listOf(\n\t\t\t\twhen {\n\t\t\t\t\tloading -> LoadingState\n\t\t\t\t\telse -> EmptyState(\n\t\t\t\t\t\ticon = R.drawable.ic_empty_common,\n\t\t\t\t\t\ttextPrimary = R.string.nothing_found,\n\t\t\t\t\t\ttextSecondary = R.string.text_search_holder_secondary,\n\t\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tloading -> list + LoadingFooter()\n\t\t\tincludeDisabled -> list\n\t\t\telse -> list + ButtonFooter(R.string.search_disabled_sources)\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tinit {\n\t\tdoSearch(throughDisabledSources = false)\n\t}\n\n\tfun retry() {\n\t\tsearchJob?.cancel()\n\t\tresults.value = emptyList()\n\t\tincludeDisabledSources.value = false\n\t\tdoSearch(throughDisabledSources = false)\n\t}\n\n\tfun continueSearch() {\n\t\tif (includeDisabledSources.value) {\n\t\t\treturn\n\t\t}\n\t\tval prevJob = searchJob\n\t\tsearchJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tincludeDisabledSources.value = true\n\t\t\tprevJob?.join()\n\t\t\tdoSearch(throughDisabledSources = true)\n\t\t}\n\t}\n\n\tfun migrate(target: Manga) {\n\t\tif (migrationJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tmigrationJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tmigrateUseCase(manga, target)\n\t\t\tonMigrated.call(target)\n\t\t}\n\t}\n\n\tprivate fun doSearch(throughDisabledSources: Boolean) {\n\t\tval prevJob = searchJob\n\t\tsearchJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tval ref = mangaDetails.getOrDefault(manga)\n\t\t\tval refCount = ref.chaptersCount()\n\t\t\talternativesUseCase.invoke(ref, throughDisabledSources)\n\t\t\t\t.collect {\n\t\t\t\t\tval model = MangaAlternativeModel(\n\t\t\t\t\t\tmangaModel = mangaListMapper.toListModel(it, ListMode.GRID) as MangaGridModel,\n\t\t\t\t\t\treferenceChapters = refCount,\n\t\t\t\t\t)\n\t\t\t\t\tresults.append(model)\n\t\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AutoFixService.kt",
    "content": "package org.koitharu.kotatsu.alternatives.ui\n\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.content.ContextCompat\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.alternatives.domain.AutoFixUseCase\nimport org.koitharu.kotatsu.alternatives.domain.AutoFixUseCase.NoAlternativesException\nimport org.koitharu.kotatsu.core.ErrorReporterReceiver\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.powerManager\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toBitmapOrNull\nimport org.koitharu.kotatsu.core.util.ext.withPartialWakeLock\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\nclass AutoFixService : CoroutineIntentService() {\n\n\t@Inject\n\tlateinit var autoFixUseCase: AutoFixUseCase\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate lateinit var notificationManager: NotificationManagerCompat\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tnotificationManager = NotificationManagerCompat.from(this)\n\t}\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tval ids = requireNotNull(intent.getLongArrayExtra(DATA_IDS))\n\t\tstartForeground(this)\n\t\tfor (mangaId in ids) {\n\t\t\tpowerManager.withPartialWakeLock(TAG) {\n\t\t\t\tval result = runCatchingCancellable {\n\t\t\t\t\tautoFixUseCase.invoke(mangaId)\n\t\t\t\t}\n\t\t\t\tif (checkNotificationPermission(CHANNEL_ID)) {\n\t\t\t\t\tval notification = buildNotification(startId, result)\n\t\t\t\t\tnotificationManager.notify(TAG, startId, notification)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) {\n\t\tif (checkNotificationPermission(CHANNEL_ID)) {\n\t\t\tval notification = runBlocking { buildNotification(startId, Result.failure(error)) }\n\t\t\tnotificationManager.notify(TAG, startId, notification)\n\t\t}\n\t}\n\n\t@SuppressLint(\"InlinedApi\")\n\tprivate fun startForeground(jobContext: IntentJobContext) {\n\t\tval title = getString(R.string.fixing_manga)\n\t\tval channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_MIN)\n\t\t\t.setName(title)\n\t\t\t.setShowBadge(false)\n\t\t\t.setVibrationEnabled(false)\n\t\t\t.setSound(null, null)\n\t\t\t.setLightsEnabled(false)\n\t\t\t.build()\n\t\tnotificationManager.createNotificationChannel(channel)\n\n\t\tval notification = NotificationCompat.Builder(this, CHANNEL_ID)\n\t\t\t.setContentTitle(title)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_MIN)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setOngoing(true)\n\t\t\t.setProgress(0, 0, true)\n\t\t\t.setSmallIcon(R.drawable.ic_stat_auto_fix)\n\t\t\t.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)\n\t\t\t.setCategory(NotificationCompat.CATEGORY_PROGRESS)\n\t\t\t.addAction(\n\t\t\t\tappcompatR.drawable.abc_ic_clear_material,\n\t\t\t\tgetString(android.R.string.cancel),\n\t\t\t\tjobContext.getCancelIntent(),\n\t\t\t)\n\t\t\t.build()\n\n\t\tjobContext.setForeground(\n\t\t\tFOREGROUND_NOTIFICATION_ID,\n\t\t\tnotification,\n\t\t\tServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,\n\t\t)\n\t}\n\n\tprivate suspend fun buildNotification(startId: Int, result: Result<Pair<Manga, Manga?>>): Notification {\n\t\tval notification = NotificationCompat.Builder(this, CHANNEL_ID)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_DEFAULT)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setAutoCancel(true)\n\t\tresult.onSuccess { (seed, replacement) ->\n\t\t\tif (replacement != null) {\n\t\t\t\tnotification.setLargeIcon(\n\t\t\t\t\tcoil.execute(\n\t\t\t\t\t\tImageRequest.Builder(this)\n\t\t\t\t\t\t\t.data(replacement.coverUrl)\n\t\t\t\t\t\t\t.mangaSourceExtra(replacement.source)\n\t\t\t\t\t\t\t.build(),\n\t\t\t\t\t).toBitmapOrNull(),\n\t\t\t\t)\n\t\t\t\tnotification.setSubText(replacement.title)\n\t\t\t\tval intent = AppRouter.detailsIntent(this, replacement)\n\t\t\t\tnotification.setContentIntent(\n\t\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\treplacement.id.toInt(),\n\t\t\t\t\t\tintent,\n\t\t\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t),\n\t\t\t\t).setVisibility(\n\t\t\t\t\tif (replacement.isNsfw()) {\n\t\t\t\t\t\tNotificationCompat.VISIBILITY_SECRET\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNotificationCompat.VISIBILITY_PUBLIC\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tnotification\n\t\t\t\t\t.setContentTitle(getString(R.string.fixed))\n\t\t\t\t\t.setContentText(\n\t\t\t\t\t\tgetString(\n\t\t\t\t\t\t\tR.string.manga_replaced,\n\t\t\t\t\t\t\tseed.title,\n\t\t\t\t\t\t\tseed.source.getTitle(this),\n\t\t\t\t\t\t\treplacement.title,\n\t\t\t\t\t\t\treplacement.source.getTitle(this),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\t.setSmallIcon(R.drawable.ic_stat_done)\n\t\t\t} else {\n\t\t\t\tnotification\n\t\t\t\t\t.setContentTitle(getString(R.string.fixing_manga))\n\t\t\t\t\t.setContentText(getString(R.string.no_fix_required, seed.title))\n\t\t\t\t\t.setSmallIcon(android.R.drawable.stat_sys_warning)\n\t\t\t}\n\t\t}.onFailure { error ->\n\t\t\tnotification\n\t\t\t\t.setContentTitle(getString(R.string.error_occurred))\n\t\t\t\t.setContentText(\n\t\t\t\t\tif (error is NoAlternativesException) {\n\t\t\t\t\t\tgetString(R.string.no_alternatives_found, error.seed.manga.title)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terror.getDisplayMessage(resources)\n\t\t\t\t\t},\n\t\t\t\t).setSmallIcon(android.R.drawable.stat_notify_error)\n\t\t\tErrorReporterReceiver.getNotificationAction(\n\t\t\t\tcontext = this,\n\t\t\t\te = error,\n\t\t\t\tnotificationId = startId,\n\t\t\t\tnotificationTag = TAG,\n\t\t\t)?.let { action ->\n\t\t\t\tnotification.addAction(action)\n\t\t\t}\n\t\t}\n\t\treturn notification.build()\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val DATA_IDS = \"ids\"\n\t\tprivate const val TAG = \"auto_fix\"\n\t\tprivate const val CHANNEL_ID = \"auto_fix\"\n\t\tprivate const val FOREGROUND_NOTIFICATION_ID = 38\n\n\t\tfun start(context: Context, mangaIds: Collection<Long>): Boolean = try {\n\t\t\tval intent = Intent(context, AutoFixService::class.java)\n\t\t\tintent.putExtra(DATA_IDS, mangaIds.toLongArray())\n\t\t\tContextCompat.startForegroundService(context, intent)\n\t\t\ttrue\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTraceDebug()\n\t\t\tfalse\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/MangaAlternativeModel.kt",
    "content": "package org.koitharu.kotatsu.alternatives.ui\n\nimport org.koitharu.kotatsu.core.model.chaptersCount\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaGridModel\nimport org.koitharu.kotatsu.parsers.model.Manga\n\ndata class MangaAlternativeModel(\n\tval mangaModel: MangaGridModel,\n\tprivate val referenceChapters: Int,\n) : ListModel {\n\n\tval manga: Manga\n\t\tget() = mangaModel.manga\n\n\tval chaptersCount = manga.chaptersCount()\n\n\tval chaptersDiff: Int\n\t\tget() = if (referenceChapters == 0 || chaptersCount == 0) 0 else chaptersCount - referenceChapters\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is MangaAlternativeModel && other.manga.id == manga.id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = if (previousState is MangaAlternativeModel) {\n\t\tmangaModel.getChangePayload(previousState.mangaModel)\n\t} else {\n\t\tnull\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/BackupRepository.kt",
    "content": "package org.koitharu.kotatsu.backups.data\n\nimport androidx.collection.ArrayMap\nimport androidx.room.withTransaction\nimport dagger.Reusable\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.asFlow\nimport kotlinx.coroutines.flow.collectIndexed\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.serialization.DeserializationStrategy\nimport kotlinx.serialization.SerializationStrategy\nimport kotlinx.serialization.json.DecodeSequenceMode\nimport kotlinx.serialization.json.Json\nimport kotlinx.serialization.json.decodeToSequence\nimport kotlinx.serialization.json.encodeToStream\nimport kotlinx.serialization.serializer\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.backups.data.model.BackupIndex\nimport org.koitharu.kotatsu.backups.data.model.BookmarkBackup\nimport org.koitharu.kotatsu.backups.data.model.CategoryBackup\nimport org.koitharu.kotatsu.backups.data.model.FavouriteBackup\nimport org.koitharu.kotatsu.backups.data.model.HistoryBackup\nimport org.koitharu.kotatsu.backups.data.model.MangaBackup\nimport org.koitharu.kotatsu.backups.data.model.ScrobblingBackup\nimport org.koitharu.kotatsu.backups.data.model.SourceBackup\nimport org.koitharu.kotatsu.backups.data.model.StatisticBackup\nimport org.koitharu.kotatsu.backups.domain.BackupSection\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.CompositeResult\nimport org.koitharu.kotatsu.core.util.progress.Progress\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.filter.data.PersistableFilter\nimport org.koitharu.kotatsu.filter.data.SavedFiltersRepository\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.data.TapGridSettings\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipInputStream\nimport java.util.zip.ZipOutputStream\nimport javax.inject.Inject\n\n@Reusable\nclass BackupRepository @Inject constructor(\n    private val database: MangaDatabase,\n    private val settings: AppSettings,\n    private val tapGridSettings: TapGridSettings,\n    private val mangaSourcesRepository: MangaSourcesRepository,\n    private val savedFiltersRepository: SavedFiltersRepository,\n) {\n\n    private val json = Json {\n        allowSpecialFloatingPointValues = true\n        coerceInputValues = true\n        encodeDefaults = true\n        ignoreUnknownKeys = true\n        useAlternativeNames = false\n    }\n\n    suspend fun createBackup(\n        output: ZipOutputStream,\n        progress: FlowCollector<Progress>?,\n    ) {\n        progress?.emit(Progress.INDETERMINATE)\n        var commonProgress = Progress(0, BackupSection.entries.size)\n        for (section in BackupSection.entries) {\n            when (section) {\n                BackupSection.INDEX -> output.writeJsonArray(\n                    section = BackupSection.INDEX,\n                    data = flowOf(BackupIndex()),\n                    serializer = serializer(),\n                )\n\n                BackupSection.HISTORY -> output.writeJsonArray(\n                    section = BackupSection.HISTORY,\n                    data = database.getHistoryDao().dump().map { HistoryBackup(it) },\n                    serializer = serializer(),\n                )\n\n                BackupSection.CATEGORIES -> output.writeJsonArray(\n                    section = BackupSection.CATEGORIES,\n                    data = database.getFavouriteCategoriesDao().findAll().asFlow().map { CategoryBackup(it) },\n                    serializer = serializer(),\n                )\n\n                BackupSection.FAVOURITES -> output.writeJsonArray(\n                    section = BackupSection.FAVOURITES,\n                    data = database.getFavouritesDao().dump().map { FavouriteBackup(it) },\n                    serializer = serializer(),\n                )\n\n                BackupSection.SETTINGS -> output.writeString(\n                    section = BackupSection.SETTINGS,\n                    data = dumpSettings(),\n                )\n\n                BackupSection.SETTINGS_READER_GRID -> output.writeString(\n                    section = BackupSection.SETTINGS_READER_GRID,\n                    data = dumpReaderGridSettings(),\n                )\n\n                BackupSection.BOOKMARKS -> output.writeJsonArray(\n                    section = BackupSection.BOOKMARKS,\n                    data = database.getBookmarksDao().dump().map { BookmarkBackup(it.first, it.second) },\n                    serializer = serializer(),\n                )\n\n                BackupSection.SOURCES -> output.writeJsonArray(\n                    section = BackupSection.SOURCES,\n                    data = database.getSourcesDao().dumpEnabled().map { SourceBackup(it) },\n                    serializer = serializer(),\n                )\n\n                BackupSection.SCROBBLING -> output.writeJsonArray(\n                    section = BackupSection.SCROBBLING,\n                    data = database.getScrobblingDao().dumpEnabled().map { ScrobblingBackup(it) },\n                    serializer = serializer(),\n                )\n\n                BackupSection.STATS -> output.writeJsonArray(\n                    section = BackupSection.STATS,\n                    data = database.getStatsDao().dumpEnabled().map { StatisticBackup(it) },\n                    serializer = serializer(),\n                )\n\n                BackupSection.SAVED_FILTERS -> {\n                    val sources = mangaSourcesRepository.getEnabledSources()\n                    val filters = sources.flatMap { source ->\n                        savedFiltersRepository.getAll(source)\n                    }\n                    output.writeJsonArray(\n                        section = BackupSection.SAVED_FILTERS,\n                        data = filters.asFlow(),\n                        serializer = serializer(),\n                    )\n                }\n            }\n            progress?.emit(commonProgress)\n            commonProgress++\n        }\n        progress?.emit(commonProgress)\n    }\n\n    suspend fun restoreBackup(\n        input: ZipInputStream,\n        sections: Set<BackupSection>,\n        progress: FlowCollector<Progress>?,\n    ): CompositeResult {\n        progress?.emit(Progress.INDETERMINATE)\n        var commonProgress = Progress(0, sections.size)\n        var entry = input.nextEntry\n        var result = CompositeResult.EMPTY\n        while (entry != null) {\n            val section = BackupSection.of(entry)\n            if (section in sections) {\n                result += when (section) {\n                    BackupSection.INDEX -> CompositeResult.EMPTY // useless in our case\n                    BackupSection.HISTORY -> input.readJsonArray<HistoryBackup>(serializer()).restoreToDb {\n                        upsertManga(it.manga)\n                        getHistoryDao().upsert(it.toEntity())\n                    }\n\n                    BackupSection.CATEGORIES -> input.readJsonArray<CategoryBackup>(serializer()).restoreToDb {\n                        getFavouriteCategoriesDao().upsert(it.toEntity())\n                    }\n\n                    BackupSection.FAVOURITES -> input.readJsonArray<FavouriteBackup>(serializer()).restoreToDb {\n                        upsertManga(it.manga)\n                        getFavouritesDao().upsert(it.toEntity())\n                    }\n\n                    BackupSection.SETTINGS -> input.readMap().let {\n                        settings.upsertAll(it)\n                        CompositeResult.success()\n                    }\n\n                    BackupSection.SETTINGS_READER_GRID -> input.readMap().let {\n                        tapGridSettings.upsertAll(it)\n                        CompositeResult.success()\n                    }\n\n                    BackupSection.BOOKMARKS -> input.readJsonArray<BookmarkBackup>(serializer()).restoreToDb {\n                        upsertManga(it.manga)\n                        getBookmarksDao().upsert(it.bookmarks.map { b -> b.toEntity() })\n                    }\n\n                    BackupSection.SOURCES -> input.readJsonArray<SourceBackup>(serializer()).restoreToDb {\n                        getSourcesDao().upsert(it.toEntity())\n                    }\n\n                    BackupSection.SCROBBLING -> input.readJsonArray<ScrobblingBackup>(serializer()).restoreToDb {\n                        getScrobblingDao().upsert(it.toEntity())\n                    }\n\n                    BackupSection.STATS -> input.readJsonArray<StatisticBackup>(serializer()).restoreToDb {\n                        getStatsDao().upsert(it.toEntity())\n                    }\n\n                    BackupSection.SAVED_FILTERS -> input.readJsonArray<PersistableFilter>(serializer())\n                        .restoreWithoutTransaction {\n                            savedFiltersRepository.save(it)\n                        }\n\n                    null -> CompositeResult.EMPTY // skip unknown entries\n                }\n                progress?.emit(commonProgress)\n                commonProgress++\n            }\n            input.closeEntry()\n            entry = input.nextEntry\n        }\n        progress?.emit(commonProgress)\n        return result\n    }\n\n    private suspend fun <T> ZipOutputStream.writeJsonArray(\n        section: BackupSection,\n        data: Flow<T>,\n        serializer: SerializationStrategy<T>,\n    ) {\n        data.onStart {\n            putNextEntry(ZipEntry(section.entryName))\n            write(\"[\")\n        }.onCompletion { error ->\n            if (error == null) {\n                write(\"]\")\n            }\n            closeEntry()\n            flush()\n        }.collectIndexed { index, value ->\n            if (index > 0) {\n                write(\",\")\n            }\n            json.encodeToStream(serializer, value, this)\n        }\n    }\n\n    private fun <T> InputStream.readJsonArray(\n        serializer: DeserializationStrategy<T>,\n    ): Sequence<T> = json.decodeToSequence(this, serializer, DecodeSequenceMode.ARRAY_WRAPPED)\n\n    private fun InputStream.readMap(): Map<String, Any?> {\n        val jo = JSONArray(readString()).getJSONObject(0)\n        val map = ArrayMap<String, Any?>(jo.length())\n        val keys = jo.keys()\n        while (keys.hasNext()) {\n            val key = keys.next()\n            map[key] = jo.get(key)\n        }\n        return map\n    }\n\n    private fun ZipOutputStream.writeString(\n        section: BackupSection,\n        data: String,\n    ) {\n        putNextEntry(ZipEntry(section.entryName))\n        try {\n            write(\"[\")\n            write(data)\n            write(\"]\")\n        } finally {\n            closeEntry()\n            flush()\n        }\n    }\n\n    private fun OutputStream.write(str: String) = write(str.toByteArray())\n\n    private fun InputStream.readString(): String = readBytes().decodeToString()\n\n    private fun dumpSettings(): String {\n        val map = settings.getAllValues().toMutableMap()\n        map.remove(AppSettings.KEY_APP_PASSWORD)\n        map.remove(AppSettings.KEY_PROXY_PASSWORD)\n        map.remove(AppSettings.KEY_PROXY_LOGIN)\n        map.remove(AppSettings.KEY_INCOGNITO_MODE)\n        return JSONObject(map).toString()\n    }\n\n    private fun dumpReaderGridSettings(): String {\n        return JSONObject(tapGridSettings.getAllValues()).toString()\n    }\n\n    private suspend fun MangaDatabase.upsertManga(manga: MangaBackup) {\n        val tags = manga.tags.map { it.toEntity() }\n        getTagsDao().upsert(tags)\n        getMangaDao().upsert(manga.toEntity(), tags)\n    }\n\n    private suspend inline fun <T> Sequence<T>.restoreToDb(crossinline block: suspend MangaDatabase.(T) -> Unit): CompositeResult {\n        return fold(CompositeResult.EMPTY) { result, item ->\n            result + runCatchingCancellable {\n                database.withTransaction {\n                    database.block(item)\n                }\n            }\n        }\n    }\n\n    private suspend inline fun <T> Sequence<T>.restoreWithoutTransaction(crossinline block: suspend (T) -> Unit): CompositeResult {\n        return fold(CompositeResult.EMPTY) { result, item ->\n            result + runCatchingCancellable {\n                block(item)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/BackupIndex.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.BuildConfig\n\n@Serializable\nclass BackupIndex(\n\t@SerialName(\"app_id\") val appId: String,\n\t@SerialName(\"app_version\") val appVersion: Int,\n\t@SerialName(\"created_at\") val createdAt: Long,\n) {\n\n\tconstructor() : this(\n\t\tappId = BuildConfig.APPLICATION_ID,\n\t\tappVersion = BuildConfig.VERSION_CODE,\n\t\tcreatedAt = System.currentTimeMillis(),\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/BookmarkBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.bookmarks.data.BookmarkEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.parsers.util.mapToSet\n\n@Serializable\nclass BookmarkBackup(\n\t@SerialName(\"manga\") val manga: MangaBackup,\n\t@SerialName(\"tags\") val tags: Set<TagBackup>,\n\t@SerialName(\"bookmarks\") val bookmarks: List<Bookmark>,\n) {\n\n\t@Serializable\n\tclass Bookmark(\n\t\t@SerialName(\"manga_id\") val mangaId: Long,\n\t\t@SerialName(\"page_id\") val pageId: Long,\n\t\t@SerialName(\"chapter_id\") val chapterId: Long,\n\t\t@SerialName(\"page\") val page: Int,\n\t\t@SerialName(\"scroll\") val scroll: Int,\n\t\t@SerialName(\"image_url\") val imageUrl: String,\n\t\t@SerialName(\"created_at\") val createdAt: Long,\n\t\t@SerialName(\"percent\") val percent: Float,\n\t) {\n\n\t\tfun toEntity() = BookmarkEntity(\n\t\t\tmangaId = mangaId,\n\t\t\tpageId = pageId,\n\t\t\tchapterId = chapterId,\n\t\t\tpage = page,\n\t\t\tscroll = scroll,\n\t\t\timageUrl = imageUrl,\n\t\t\tcreatedAt = createdAt,\n\t\t\tpercent = percent,\n\t\t)\n\t}\n\n\tconstructor(manga: MangaWithTags, entities: List<BookmarkEntity>) : this(\n\t\tmanga = MangaBackup(manga.copy(tags = emptyList())),\n\t\ttags = manga.tags.mapToSet { TagBackup(it) },\n\t\tbookmarks = entities.map {\n\t\t\tBookmark(\n\t\t\t\tmangaId = it.mangaId,\n\t\t\t\tpageId = it.pageId,\n\t\t\t\tchapterId = it.chapterId,\n\t\t\t\tpage = it.page,\n\t\t\t\tscroll = it.scroll,\n\t\t\t\timageUrl = it.imageUrl,\n\t\t\t\tcreatedAt = it.createdAt,\n\t\t\t\tpercent = it.percent,\n\t\t\t)\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/CategoryBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\n\n@Serializable\nclass CategoryBackup(\n\t@SerialName(\"category_id\") val categoryId: Int,\n\t@SerialName(\"created_at\") val createdAt: Long,\n\t@SerialName(\"sort_key\") val sortKey: Int,\n\t@SerialName(\"title\") val title: String,\n\t@SerialName(\"order\") val order: String = ListSortOrder.NEWEST.name,\n\t@SerialName(\"track\") val track: Boolean = true,\n\t@SerialName(\"show_in_lib\") val isVisibleInLibrary: Boolean = true,\n) {\n\n\tconstructor(entity: FavouriteCategoryEntity) : this(\n\t\tcategoryId = entity.categoryId,\n\t\tcreatedAt = entity.createdAt,\n\t\tsortKey = entity.sortKey,\n\t\ttitle = entity.title,\n\t\torder = entity.order,\n\t\ttrack = entity.track,\n\t\tisVisibleInLibrary = entity.isVisibleInLibrary,\n\t)\n\n\tfun toEntity() = FavouriteCategoryEntity(\n\t\tcategoryId = categoryId,\n\t\tcreatedAt = createdAt,\n\t\tsortKey = sortKey,\n\t\ttitle = title,\n\t\torder = order,\n\t\ttrack = track,\n\t\tisVisibleInLibrary = isVisibleInLibrary,\n\t\tdeletedAt = 0L,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/FavouriteBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.favourites.data.FavouriteEntity\nimport org.koitharu.kotatsu.favourites.data.FavouriteManga\n\n@Serializable\nclass FavouriteBackup(\n\t@SerialName(\"manga_id\") val mangaId: Long,\n\t@SerialName(\"category_id\") val categoryId: Long,\n\t@SerialName(\"sort_key\") val sortKey: Int = 0,\n\t@SerialName(\"pinned\") val isPinned: Boolean = false,\n\t@SerialName(\"created_at\") val createdAt: Long,\n\t@SerialName(\"manga\") val manga: MangaBackup,\n) {\n\n\tconstructor(entity: FavouriteManga) : this(\n\t\tmangaId = entity.manga.id,\n\t\tcategoryId = entity.favourite.categoryId,\n\t\tsortKey = entity.favourite.sortKey,\n\t\tisPinned = entity.favourite.isPinned,\n\t\tcreatedAt = entity.favourite.createdAt,\n\t\tmanga = MangaBackup(MangaWithTags(entity.manga, entity.tags)),\n\t)\n\n\tfun toEntity() = FavouriteEntity(\n\t\tmangaId = mangaId,\n\t\tcategoryId = categoryId,\n\t\tsortKey = sortKey,\n\t\tisPinned = isPinned,\n\t\tcreatedAt = createdAt,\n\t\tdeletedAt = 0L,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/HistoryBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.history.data.HistoryEntity\nimport org.koitharu.kotatsu.history.data.HistoryWithManga\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\n\n@Serializable\nclass HistoryBackup(\n\t@SerialName(\"manga_id\") val mangaId: Long,\n\t@SerialName(\"created_at\") val createdAt: Long,\n\t@SerialName(\"updated_at\") val updatedAt: Long,\n\t@SerialName(\"chapter_id\") val chapterId: Long,\n\t@SerialName(\"page\") val page: Int,\n\t@SerialName(\"scroll\") val scroll: Float,\n\t@SerialName(\"percent\") val percent: Float = PROGRESS_NONE,\n\t@SerialName(\"chapters\") val chaptersCount: Int = 0,\n\t@SerialName(\"manga\") val manga: MangaBackup,\n) {\n\n\tconstructor(entity: HistoryWithManga) : this(\n\t\tmangaId = entity.manga.id,\n\t\tcreatedAt = entity.history.createdAt,\n\t\tupdatedAt = entity.history.updatedAt,\n\t\tchapterId = entity.history.chapterId,\n\t\tpage = entity.history.page,\n\t\tscroll = entity.history.scroll,\n\t\tpercent = entity.history.percent,\n\t\tchaptersCount = entity.history.chaptersCount,\n\t\tmanga = MangaBackup(MangaWithTags(entity.manga, entity.tags)),\n\t)\n\n\tfun toEntity() = HistoryEntity(\n\t\tmangaId = mangaId,\n\t\tcreatedAt = createdAt,\n\t\tupdatedAt = updatedAt,\n\t\tchapterId = chapterId,\n\t\tpage = page,\n\t\tscroll = scroll,\n\t\tpercent = percent,\n\t\tdeletedAt = 0L,\n\t\tchaptersCount = chaptersCount,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/MangaBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN\nimport org.koitharu.kotatsu.parsers.util.mapToSet\n\n@Serializable\nclass MangaBackup(\n\t@SerialName(\"id\") val id: Long,\n\t@SerialName(\"title\") val title: String,\n\t@SerialName(\"alt_title\") val altTitles: String? = null,\n\t@SerialName(\"url\") val url: String,\n\t@SerialName(\"public_url\") val publicUrl: String,\n\t@SerialName(\"rating\") val rating: Float = RATING_UNKNOWN,\n\t@SerialName(\"nsfw\") val isNsfw: Boolean = false,\n\t@SerialName(\"content_rating\") val contentRating: String? = null,\n\t@SerialName(\"cover_url\") val coverUrl: String,\n\t@SerialName(\"large_cover_url\") val largeCoverUrl: String? = null,\n\t@SerialName(\"state\") val state: String? = null,\n\t@SerialName(\"author\") val authors: String? = null,\n\t@SerialName(\"source\") val source: String,\n\t@SerialName(\"tags\") val tags: Set<TagBackup> = emptySet(),\n) {\n\n\tconstructor(entity: MangaWithTags) : this(\n\t\tid = entity.manga.id,\n\t\ttitle = entity.manga.title,\n\t\taltTitles = entity.manga.altTitles,\n\t\turl = entity.manga.url,\n\t\tpublicUrl = entity.manga.publicUrl,\n\t\trating = entity.manga.rating,\n\t\tisNsfw = entity.manga.isNsfw,\n\t\tcontentRating = entity.manga.contentRating,\n\t\tcoverUrl = entity.manga.coverUrl,\n\t\tlargeCoverUrl = entity.manga.largeCoverUrl,\n\t\tstate = entity.manga.state,\n\t\tauthors = entity.manga.authors,\n\t\tsource = entity.manga.source,\n\t\ttags = entity.tags.mapToSet { TagBackup(it) },\n\t)\n\n\tfun toEntity() = MangaEntity(\n\t\tid = id,\n\t\ttitle = title,\n\t\taltTitles = altTitles,\n\t\turl = url,\n\t\tpublicUrl = publicUrl,\n\t\trating = rating,\n\t\tisNsfw = isNsfw,\n\t\tcontentRating = contentRating,\n\t\tcoverUrl = coverUrl,\n\t\tlargeCoverUrl = largeCoverUrl,\n\t\tstate = state,\n\t\tauthors = authors,\n\t\tsource = source,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/ScrobblingBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity\n\n@Serializable\nclass ScrobblingBackup(\n\t@SerialName(\"scrobbler\") val scrobbler: Int,\n\t@SerialName(\"id\") val id: Int,\n\t@SerialName(\"manga_id\") val mangaId: Long,\n\t@SerialName(\"target_id\") val targetId: Long,\n\t@SerialName(\"status\") val status: String?,\n\t@SerialName(\"chapter\") val chapter: Int,\n\t@SerialName(\"comment\") val comment: String?,\n\t@SerialName(\"rating\") val rating: Float,\n) {\n\n\tconstructor(entity: ScrobblingEntity) : this(\n\t\tscrobbler = entity.scrobbler,\n\t\tid = entity.id,\n\t\tmangaId = entity.mangaId,\n\t\ttargetId = entity.targetId,\n\t\tstatus = entity.status,\n\t\tchapter = entity.chapter,\n\t\tcomment = entity.comment,\n\t\trating = entity.rating,\n\t)\n\n\tfun toEntity() = ScrobblingEntity(\n\t\tscrobbler = scrobbler,\n\t\tid = id,\n\t\tmangaId = mangaId,\n\t\ttargetId = targetId,\n\t\tstatus = status,\n\t\tchapter = chapter,\n\t\tcomment = comment,\n\t\trating = rating,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/SourceBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.db.entity.MangaSourceEntity\n\n@Serializable\nclass SourceBackup(\n\t@SerialName(\"source\") val source: String,\n\t@SerialName(\"sort_key\") val sortKey: Int,\n\t@SerialName(\"used_at\") val lastUsedAt: Long,\n\t@SerialName(\"added_in\") val addedIn: Int,\n\t@SerialName(\"pinned\") val isPinned: Boolean = false,\n\t@SerialName(\"enabled\") val isEnabled: Boolean = true, // for compatibility purposes, should be only true\n) {\n\n\tconstructor(entity: MangaSourceEntity) : this(\n\t\tsource = entity.source,\n\t\tsortKey = entity.sortKey,\n\t\tlastUsedAt = entity.lastUsedAt,\n\t\taddedIn = entity.addedIn,\n\t\tisPinned = entity.isPinned,\n\t\tisEnabled = entity.isEnabled,\n\t)\n\n\tfun toEntity() = MangaSourceEntity(\n\t\tsource = source,\n\t\tisEnabled = isEnabled,\n\t\tsortKey = sortKey,\n\t\taddedIn = addedIn,\n\t\tlastUsedAt = lastUsedAt,\n\t\tisPinned = isPinned,\n\t\tcfState = 0,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/StatisticBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.stats.data.StatsEntity\n\n@Serializable\nclass StatisticBackup(\n\t@SerialName(\"manga_id\") val mangaId: Long,\n\t@SerialName(\"started_at\") val startedAt: Long,\n\t@SerialName(\"duration\") val duration: Long,\n\t@SerialName(\"pages\") val pages: Int,\n) {\n\n\tconstructor(entity: StatsEntity) : this(\n\t\tmangaId = entity.mangaId,\n\t\tstartedAt = entity.startedAt,\n\t\tduration = entity.duration,\n\t\tpages = entity.pages,\n\t)\n\n\tfun toEntity() = StatsEntity(\n\t\tmangaId = mangaId,\n\t\tstartedAt = startedAt,\n\t\tduration = duration,\n\t\tpages = pages,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/data/model/TagBackup.kt",
    "content": "package org.koitharu.kotatsu.backups.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\n@Serializable\nclass TagBackup(\n\t@SerialName(\"id\") val id: Long,\n\t@SerialName(\"title\") val title: String,\n\t@SerialName(\"key\") val key: String,\n\t@SerialName(\"source\") val source: String,\n\t@SerialName(\"pinned\") val isPinned: Boolean = false,\n) {\n\n\tconstructor(entity: TagEntity) : this(\n\t\tid = entity.id,\n\t\ttitle = entity.title,\n\t\tkey = entity.key,\n\t\tsource = entity.source,\n\t\tisPinned = entity.isPinned,\n\t)\n\n\tfun toEntity() = TagEntity(\n\t\tid = id,\n\t\ttitle = title,\n\t\tkey = key,\n\t\tsource = source,\n\t\tisPinned = isPinned,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/domain/AppBackupAgent.kt",
    "content": "package org.koitharu.kotatsu.backups.domain\n\nimport android.app.backup.BackupAgent\nimport android.app.backup.BackupDataInput\nimport android.app.backup.BackupDataOutput\nimport android.app.backup.FullBackupDataOutput\nimport android.content.Context\nimport android.os.ParcelFileDescriptor\nimport androidx.annotation.VisibleForTesting\nimport com.google.common.io.ByteStreams\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.backups.data.BackupRepository\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.filter.data.SavedFiltersRepository\nimport org.koitharu.kotatsu.reader.data.TapGridSettings\nimport java.io.File\nimport java.io.FileDescriptor\nimport java.io.FileInputStream\nimport java.util.EnumSet\nimport java.util.zip.ZipInputStream\nimport java.util.zip.ZipOutputStream\n\nclass AppBackupAgent : BackupAgent() {\n\n\toverride fun onBackup(\n\t\toldState: ParcelFileDescriptor?,\n\t\tdata: BackupDataOutput?,\n\t\tnewState: ParcelFileDescriptor?\n\t) = Unit\n\n\toverride fun onRestore(\n\t\tdata: BackupDataInput?,\n\t\tappVersionCode: Int,\n\t\tnewState: ParcelFileDescriptor?\n\t) = Unit\n\n\toverride fun onFullBackup(data: FullBackupDataOutput) {\n\t\tsuper.onFullBackup(data)\n\t\tval file = createBackupFile(\n\t\t\tthis,\n\t\t\tBackupRepository(\n\t\t\t\tdatabase = MangaDatabase(context = applicationContext),\n\t\t\t\tsettings = AppSettings(applicationContext),\n\t\t\t\ttapGridSettings = TapGridSettings(applicationContext),\n\t\t\t\tmangaSourcesRepository = MangaSourcesRepository(\n\t\t\t\t\tcontext = applicationContext,\n\t\t\t\t\tdb = MangaDatabase(context = applicationContext),\n\t\t\t\t\tsettings = AppSettings(applicationContext),\n\t\t\t\t),\n\t\t\t\tsavedFiltersRepository = SavedFiltersRepository(\n\t\t\t\t\tcontext = applicationContext,\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t\ttry {\n\t\t\tfullBackupFile(file, data)\n\t\t} finally {\n\t\t\tfile.delete()\n\t\t}\n\t}\n\n\toverride fun onRestoreFile(\n\t\tdata: ParcelFileDescriptor,\n\t\tsize: Long,\n\t\tdestination: File?,\n\t\ttype: Int,\n\t\tmode: Long,\n\t\tmtime: Long\n\t) {\n\t\tif (destination?.name?.endsWith(\".bk.zip\") == true) {\n\t\t\trestoreBackupFile(\n\t\t\t\tdata.fileDescriptor,\n\t\t\t\tsize,\n\t\t\t\tBackupRepository(\n\t\t\t\t\tdatabase = MangaDatabase(applicationContext),\n\t\t\t\t\tsettings = AppSettings(applicationContext),\n\t\t\t\t\ttapGridSettings = TapGridSettings(applicationContext),\n\t\t\t\t\tmangaSourcesRepository = MangaSourcesRepository(\n\t\t\t\t\t\tcontext = applicationContext,\n\t\t\t\t\t\tdb = MangaDatabase(context = applicationContext),\n\t\t\t\t\t\tsettings = AppSettings(applicationContext),\n\t\t\t\t\t),\n\t\t\t\t\tsavedFiltersRepository = SavedFiltersRepository(\n\t\t\t\t\t\tcontext = applicationContext,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t\tdestination.delete()\n\t\t} else {\n\t\t\tsuper.onRestoreFile(data, size, destination, type, mode, mtime)\n\t\t}\n\t}\n\n\t@VisibleForTesting\n\tfun createBackupFile(context: Context, repository: BackupRepository): File {\n\t\tval file = BackupUtils.createTempFile(context)\n\t\tZipOutputStream(file.outputStream()).use { output ->\n\t\t\trunBlocking {\n\t\t\t\trepository.createBackup(output, null)\n\t\t\t}\n\t\t}\n\t\treturn file\n\t}\n\n\t@VisibleForTesting\n\tfun restoreBackupFile(fd: FileDescriptor, size: Long, repository: BackupRepository) {\n\t\tZipInputStream(ByteStreams.limit(FileInputStream(fd), size)).use { input ->\n\t\t\tval sections = EnumSet.allOf(BackupSection::class.java)\n\t\t\t// managed externally\n\t\t\tsections.remove(BackupSection.SETTINGS)\n\t\t\tsections.remove(BackupSection.SETTINGS_READER_GRID)\n\t\t\trunBlocking {\n\t\t\t\trepository.restoreBackup(input, sections, null)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/domain/BackupFile.kt",
    "content": "package org.koitharu.kotatsu.backups.domain\n\nimport android.net.Uri\nimport java.util.Date\n\ndata class BackupFile(\n\tval uri: Uri,\n\tval dateTime: Date,\n) : Comparable<BackupFile> {\n\n\toverride fun compareTo(other: BackupFile): Int = compareValues(dateTime, other.dateTime)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/domain/BackupObserver.kt",
    "content": "package org.koitharu.kotatsu.backups.domain\n\nimport android.app.backup.BackupManager\nimport android.content.Context\nimport androidx.room.InvalidationTracker\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES\nimport org.koitharu.kotatsu.core.db.TABLE_HISTORY\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass BackupObserver @Inject constructor(\n\t@ApplicationContext context: Context,\n) : InvalidationTracker.Observer(\n\tarrayOf(\n\t\tTABLE_HISTORY,\n\t\tTABLE_FAVOURITES,\n\t\tTABLE_FAVOURITE_CATEGORIES,\n\t),\n) {\n\n\tprivate val backupManager = BackupManager(context)\n\n\toverride fun onInvalidated(tables: Set<String>) {\n\t\tbackupManager.dataChanged()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/domain/BackupSection.kt",
    "content": "package org.koitharu.kotatsu.backups.domain\n\nimport java.util.Locale\nimport java.util.zip.ZipEntry\n\nenum class BackupSection(\n\tval entryName: String,\n) {\n\n\tINDEX(\"index\"),\n\tHISTORY(\"history\"),\n\tCATEGORIES(\"categories\"),\n\tFAVOURITES(\"favourites\"),\n\tSETTINGS(\"settings\"),\n\tSETTINGS_READER_GRID(\"reader_grid\"),\n\tBOOKMARKS(\"bookmarks\"),\n\tSOURCES(\"sources\"),\n\tSCROBBLING(\"scrobbling\"),\n\tSTATS(\"statistics\"),\n\tSAVED_FILTERS(\"saved_filters\"),\n\t;\n\n\tcompanion object {\n\n\t\tfun of(entry: ZipEntry): BackupSection? {\n\t\t\tval name = entry.name.lowercase(Locale.ROOT)\n\t\t\treturn entries.find { x -> x.entryName == name }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/domain/BackupUtils.kt",
    "content": "package org.koitharu.kotatsu.backups.domain\n\nimport android.content.Context\nimport androidx.annotation.CheckResult\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport java.io.File\nimport java.text.ParseException\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\nobject BackupUtils {\n\n\tprivate const val DIR_BACKUPS = \"backups\"\n\tprivate val dateTimeFormat = SimpleDateFormat(\"yyyyMMdd-HHmm\")\n\n\t@CheckResult\n\tfun createTempFile(context: Context): File {\n\t\tval dir = getAppBackupDir(context)\n\t\tdir.mkdirs()\n\t\treturn File(dir, generateFileName(context))\n\t}\n\n\tfun getAppBackupDir(context: Context) = context.run {\n\t\tgetExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)\n\t}\n\n\tfun parseBackupDateTime(fileName: String): Date? = try {\n\t\tdateTimeFormat.parse(fileName.substringAfterLast('_').substringBefore('.'))\n\t} catch (e: ParseException) {\n\t\te.printStackTraceDebug()\n\t\tnull\n\t}\n\n\tfun generateFileName(context: Context) = buildString {\n\t\tappend(context.getString(R.string.app_name).replace(' ', '_').lowercase(Locale.ROOT))\n\t\tappend('_')\n\t\tappend(dateTimeFormat.format(Date()))\n\t\tappend(\".bk.zip\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/domain/ExternalBackupStorage.kt",
    "content": "package org.koitharu.kotatsu.backups.domain\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.annotation.CheckResult\nimport androidx.documentfile.provider.DocumentFile\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport okio.buffer\nimport okio.sink\nimport okio.source\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\nimport javax.inject.Inject\n\nclass ExternalBackupStorage @Inject constructor(\n\t@ApplicationContext private val context: Context,\n\tprivate val settings: AppSettings,\n) {\n\n\tsuspend fun list(): List<BackupFile> = runInterruptible(Dispatchers.IO) {\n\t\tgetRootOrThrow().listFiles().mapNotNull {\n\t\t\tif (it.isFile && it.canRead()) {\n\t\t\t\tBackupFile(\n\t\t\t\t\turi = it.uri,\n\t\t\t\t\tdateTime = it.name?.let { fileName ->\n\t\t\t\t\t\tBackupUtils.parseBackupDateTime(fileName)\n\t\t\t\t\t} ?: return@mapNotNull null,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}.sortedDescending()\n\t}\n\n\tsuspend fun listOrNull() = runCatchingCancellable {\n\t\tlist()\n\t}.onFailure { e ->\n\t\te.printStackTraceDebug()\n\t}.getOrNull()\n\n\tsuspend fun put(file: File): Uri = runInterruptible(Dispatchers.IO) {\n\t\tval out = checkNotNull(\n\t\t\tgetRootOrThrow().createFile(\n\t\t\t\t\"application/zip\",\n\t\t\t\tfile.nameWithoutExtension,\n\t\t\t),\n\t\t) {\n\t\t\t\"Cannot create target backup file\"\n\t\t}\n\t\tcheckNotNull(context.contentResolver.openOutputStream(out.uri, \"wt\")).sink().use { sink ->\n\t\t\tfile.source().buffer().use { src ->\n\t\t\t\tsrc.readAll(sink)\n\t\t\t}\n\t\t}\n\t\tout.uri\n\t}\n\n\t@CheckResult\n\tsuspend fun delete(victim: BackupFile) = runInterruptible(Dispatchers.IO) {\n\t\tval df = DocumentFile.fromSingleUri(context, victim.uri)\n\t\tdf != null && df.delete()\n\t}\n\n\tsuspend fun getLastBackupDate() = listOrNull()?.maxOfOrNull { it.dateTime }\n\n\tsuspend fun trim(maxCount: Int): Boolean {\n\t\tif (maxCount == Int.MAX_VALUE) {\n\t\t\treturn false\n\t\t}\n\t\tval list = listOrNull()\n\t\tif (list == null || list.size <= maxCount) {\n\t\t\treturn false\n\t\t}\n\t\tvar result = false\n\t\tfor (i in maxCount until list.size) {\n\t\t\tif (delete(list[i])) {\n\t\t\t\tresult = true\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\t@Blocking\n\tprivate fun getRootOrThrow(): DocumentFile {\n\t\tval uri = checkNotNull(settings.periodicalBackupDirectory) {\n\t\t\t\"Backup directory is not specified\"\n\t\t}\n\t\tval root = DocumentFile.fromTreeUri(context, uri)\n\t\treturn checkNotNull(root) { \"Cannot obtain DocumentFile from $uri\" }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/BaseBackupRestoreService.kt",
    "content": "package org.koitharu.kotatsu.backups.ui\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.app.ShareCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ErrorReporterReceiver\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport org.koitharu.kotatsu.core.util.CompositeResult\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getFileDisplayName\nimport androidx.appcompat.R as appcompatR\n\nabstract class BaseBackupRestoreService : CoroutineIntentService() {\n\n\tprotected abstract val notificationTag: String\n\tprotected abstract val isRestoreService: Boolean\n\n\tprotected lateinit var notificationManager: NotificationManagerCompat\n\t\tprivate set\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tnotificationManager = NotificationManagerCompat.from(applicationContext)\n\t\tcreateNotificationChannel(this)\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) {\n\t\tshowResultNotification(null, CompositeResult.failure(error))\n\t}\n\n\tprotected fun IntentJobContext.showResultNotification(\n\t\tfileUri: Uri?,\n\t\tresult: CompositeResult,\n\t) {\n\t\tif (!applicationContext.checkNotificationPermission(CHANNEL_ID)) {\n\t\t\treturn\n\t\t}\n\t\tval notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_HIGH)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setAutoCancel(true)\n\t\t\t.setSubText(fileUri?.let { contentResolver.getFileDisplayName(it) })\n\t\twhen {\n\t\t\tresult.isAllSuccess -> {\n\t\t\t\tif (isRestoreService) {\n\t\t\t\t\tnotification\n\t\t\t\t\t\t.setContentTitle(getString(R.string.restoring_backup))\n\t\t\t\t\t\t.setContentText(getString(R.string.data_restored_success))\n\t\t\t\t} else {\n\t\t\t\t\tnotification\n\t\t\t\t\t\t.setContentTitle(getString(R.string.backup_saved))\n\t\t\t\t\t\t.setContentText(fileUri?.let { contentResolver.getFileDisplayName(it) })\n\t\t\t\t\t\t.setSubText(null)\n\n\t\t\t\t}\n\t\t\t\tnotification.setSmallIcon(R.drawable.ic_stat_done)\n\t\t\t}\n\n\t\t\tresult.isAllFailed || !isRestoreService -> {\n\t\t\t\tval title = getString(if (isRestoreService) R.string.data_not_restored else R.string.error_occurred)\n\t\t\t\tval message = result.failures.joinToString(\"\\n\") {\n\t\t\t\t\tit.getDisplayMessage(applicationContext.resources)\n\t\t\t\t}\n\t\t\t\tnotification\n\t\t\t\t\t.setContentText(if (isRestoreService) getString(R.string.data_not_restored_text) else message)\n\t\t\t\t\t.setBigText(title, message)\n\t\t\t\t\t.setSmallIcon(android.R.drawable.stat_notify_error)\n\t\t\t\tresult.failures.firstNotNullOfOrNull { error ->\n\t\t\t\t\tErrorReporterReceiver.getNotificationAction(applicationContext, error, startId, notificationTag)\n\t\t\t\t}?.let { action ->\n\t\t\t\t\tnotification.addAction(action)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tnotification\n\t\t\t\t\t.setContentTitle(getString(R.string.restoring_backup))\n\t\t\t\t\t.setContentText(getString(R.string.data_restored_with_errors))\n\t\t\t\t\t.setSmallIcon(R.drawable.ic_stat_done)\n\t\t\t}\n\t\t}\n\t\tnotification.setContentIntent(\n\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\tapplicationContext,\n\t\t\t\t0,\n\t\t\t\tAppRouter.homeIntent(this@BaseBackupRestoreService),\n\t\t\t\t0,\n\t\t\t\tfalse,\n\t\t\t),\n\t\t)\n\t\tif (!isRestoreService && fileUri != null) {\n\t\t\tval shareIntent = ShareCompat.IntentBuilder(this@BaseBackupRestoreService)\n\t\t\t\t.setStream(fileUri)\n\t\t\t\t.setType(\"application/zip\")\n\t\t\t\t.setChooserTitle(R.string.share_backup)\n\t\t\t\t.createChooserIntent()\n\t\t\tnotification.addAction(\n\t\t\t\tappcompatR.drawable.abc_ic_menu_share_mtrl_alpha,\n\t\t\t\tgetString(R.string.share),\n\t\t\t\tPendingIntentCompat.getActivity(this@BaseBackupRestoreService, 0, shareIntent, 0, false),\n\t\t\t)\n\t\t}\n\t\tnotificationManager.notify(notificationTag, startId, notification.build())\n\t}\n\n\tprotected fun NotificationCompat.Builder.setBigText(title: String, text: CharSequence) = setStyle(\n\t\tNotificationCompat.BigTextStyle()\n\t\t\t.bigText(text)\n\t\t\t.setSummaryText(text)\n\t\t\t.setBigContentTitle(title),\n\t)\n\n\tcompanion object {\n\n\t\tconst val CHANNEL_ID = \"backup_restore\"\n\n\t\tfun createNotificationChannel(context: Context) {\n\t\t\tval channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_HIGH)\n\t\t\t\t.setName(context.getString(R.string.backup_restore))\n\t\t\t\t.setShowBadge(true)\n\t\t\t\t.setVibrationEnabled(false)\n\t\t\t\t.setSound(null, null)\n\t\t\t\t.setLightsEnabled(false)\n\t\t\t\t.build()\n\t\t\tNotificationManagerCompat.from(context).createNotificationChannel(channel)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/backup/BackupDialogFragment.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.backup\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.progress.Progress\nimport org.koitharu.kotatsu.databinding.DialogProgressBinding\n\n@AndroidEntryPoint\nclass BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {\n\n\tprivate val viewModel by viewModels<BackupViewModel>()\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = DialogProgressBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: DialogProgressBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.textViewTitle.setText(R.string.create_backup)\n\t\tbinding.textViewSubtitle.setText(R.string.processing_)\n\n\t\tviewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged)\n\t\tviewModel.onBackupDone.observeEvent(viewLifecycleOwner, this::onBackupDone)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, this::onError)\n\t}\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder)\n\t\t\t.setCancelable(false)\n\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tMaterialAlertDialogBuilder(context ?: return)\n\t\t\t.setNegativeButton(R.string.close, null)\n\t\t\t.setTitle(R.string.error)\n\t\t\t.setMessage(e.getDisplayMessage(resources))\n\t\t\t.show()\n\t\tdismiss()\n\t}\n\n\tprivate fun onProgressChanged(value: Progress) {\n\t\twith(requireViewBinding().progressBar) {\n\t\t\tisVisible = true\n\t\t\tval wasIndeterminate = isIndeterminate\n\t\t\tisIndeterminate = value.isIndeterminate\n\t\t\tif (!value.isIndeterminate) {\n\t\t\t\tmax = value.total\n\t\t\t\tsetProgressCompat(value.progress, !wasIndeterminate)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onBackupDone(uri: Uri) {\n\t\tToast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_SHORT).show()\n\t\tdismiss()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/backup/BackupService.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.backup\n\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport android.net.Uri\nimport android.widget.Toast\nimport androidx.annotation.CheckResult\nimport androidx.core.app.NotificationCompat\nimport androidx.core.content.ContextCompat\nimport androidx.documentfile.provider.DocumentFile\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.data.BackupRepository\nimport org.koitharu.kotatsu.backups.ui.BaseBackupRestoreService\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.util.CompositeResult\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.powerManager\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.core.util.ext.withPartialWakeLock\nimport org.koitharu.kotatsu.core.util.progress.Progress\nimport java.io.FileNotFoundException\nimport java.util.zip.ZipOutputStream\nimport javax.inject.Inject\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\n@SuppressLint(\"InlinedApi\")\nclass BackupService : BaseBackupRestoreService() {\n\n\toverride val notificationTag = TAG\n\toverride val isRestoreService = false\n\n\t@Inject\n\tlateinit var repository: BackupRepository\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tval notification = buildNotification(Progress.INDETERMINATE)\n\t\tsetForeground(\n\t\t\tFOREGROUND_NOTIFICATION_ID,\n\t\t\tnotification,\n\t\t\tServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,\n\t\t)\n\t\tval destination = intent.getStringExtra(AppRouter.KEY_DATA)?.toUriOrNull() ?: throw FileNotFoundException()\n\t\tpowerManager.withPartialWakeLock(TAG) {\n\t\t\tval progress = MutableStateFlow(Progress.INDETERMINATE)\n\t\t\tval progressUpdateJob = if (checkNotificationPermission(CHANNEL_ID)) {\n\t\t\t\tlaunch {\n\t\t\t\t\tprogress.collect {\n\t\t\t\t\t\tnotificationManager.notify(FOREGROUND_NOTIFICATION_ID, buildNotification(it))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tZipOutputStream(contentResolver.openOutputStream(destination)).use { output ->\n\t\t\t\t\trepository.createBackup(output, progress)\n\t\t\t\t}\n\t\t\t} catch (e: Throwable) {\n\t\t\t\ttry {\n\t\t\t\t\tDocumentFile.fromSingleUri(applicationContext, destination)?.delete()\n\t\t\t\t} catch (e2: Throwable) {\n\t\t\t\t\te.addSuppressed(e2)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t}\n\t\t\tprogressUpdateJob?.cancelAndJoin()\n\t\t\tcontentResolver.notifyChange(destination, null)\n\t\t\tshowResultNotification(destination, CompositeResult.success())\n\t\t\twithContext(Dispatchers.Main) {\n\t\t\t\tToast.makeText(this@BackupService, R.string.backup_saved, Toast.LENGTH_SHORT).show()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun IntentJobContext.buildNotification(progress: Progress): Notification {\n\t\treturn NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\t\t.setContentTitle(getString(R.string.creating_backup))\n\t\t\t.setPriority(NotificationCompat.PRIORITY_HIGH)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setOngoing(true)\n\t\t\t.setProgress(\n\t\t\t\tprogress.total.coerceAtLeast(0),\n\t\t\t\tprogress.progress.coerceAtLeast(0),\n\t\t\t\tprogress.isIndeterminate,\n\t\t\t)\n\t\t\t.setContentText(\n\t\t\t\tif (progress.isIndeterminate) {\n\t\t\t\t\tgetString(R.string.processing_)\n\t\t\t\t} else {\n\t\t\t\t\tgetString(R.string.fraction_pattern, progress.progress, progress.total)\n\t\t\t\t},\n\t\t\t)\n\t\t\t.setSmallIcon(android.R.drawable.stat_sys_upload)\n\t\t\t.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)\n\t\t\t.setCategory(NotificationCompat.CATEGORY_PROGRESS)\n\t\t\t.addAction(\n\t\t\t\tappcompatR.drawable.abc_ic_clear_material,\n\t\t\t\tapplicationContext.getString(android.R.string.cancel),\n\t\t\t\tgetCancelIntent(),\n\t\t\t).build()\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val TAG = \"BACKUP\"\n\t\tprivate const val FOREGROUND_NOTIFICATION_ID = 33\n\n\t\t@CheckResult\n\t\tfun start(context: Context, uri: Uri): Boolean = try {\n\t\t\tval intent = Intent(context, BackupService::class.java)\n\t\t\tintent.putExtra(AppRouter.KEY_DATA, uri.toString())\n\t\t\tContextCompat.startForegroundService(context, intent)\n\t\t\ttrue\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTraceDebug()\n\t\t\tfalse\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/backup/BackupViewModel.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.backup\n\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.net.Uri\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.backups.data.BackupRepository\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.core.util.progress.Progress\nimport java.util.zip.Deflater\nimport java.util.zip.ZipOutputStream\nimport javax.inject.Inject\n\n@HiltViewModel\nclass BackupViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val repository: BackupRepository,\n\t@ApplicationContext context: Context,\n) : BaseViewModel() {\n\n\tval progress = MutableStateFlow(Progress.INDETERMINATE)\n\tval onBackupDone = MutableEventFlow<Uri>()\n\n\tprivate val destination = savedStateHandle.require<Uri>(AppRouter.KEY_DATA)\n\tprivate val contentResolver: ContentResolver = context.contentResolver\n\n\tinit {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tZipOutputStream(checkNotNull(contentResolver.openOutputStream(destination))).use {\n\t\t\t\tit.setLevel(Deflater.BEST_COMPRESSION)\n\t\t\t\trepository.createBackup(it, progress)\n\t\t\t}\n\t\t\tonBackupDone.call(destination)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/periodical/PeriodicalBackupService.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.periodical\n\nimport android.content.Intent\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.data.BackupRepository\nimport org.koitharu.kotatsu.backups.domain.BackupUtils\nimport org.koitharu.kotatsu.backups.domain.ExternalBackupStorage\nimport org.koitharu.kotatsu.backups.ui.BaseBackupRestoreService\nimport org.koitharu.kotatsu.core.ErrorReporterReceiver\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport java.util.zip.ZipOutputStream\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass PeriodicalBackupService : CoroutineIntentService() {\n\n\t@Inject\n\tlateinit var externalBackupStorage: ExternalBackupStorage\n\n\t@Inject\n\tlateinit var telegramBackupUploader: TelegramBackupUploader\n\n\t@Inject\n\tlateinit var repository: BackupRepository\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tif (!settings.isPeriodicalBackupEnabled || settings.periodicalBackupDirectory == null) {\n\t\t\treturn\n\t\t}\n\t\tval lastBackupDate = externalBackupStorage.getLastBackupDate()\n\t\tif (lastBackupDate != null && lastBackupDate.time + settings.periodicalBackupFrequencyMillis > System.currentTimeMillis()) {\n\t\t\treturn\n\t\t}\n\t\tval output = BackupUtils.createTempFile(applicationContext)\n\t\ttry {\n\t\t\tZipOutputStream(output.outputStream()).use {\n\t\t\t\trepository.createBackup(it, null)\n\t\t\t}\n\t\t\texternalBackupStorage.put(output)\n\t\t\texternalBackupStorage.trim(settings.periodicalBackupMaxCount)\n\t\t\tif (settings.isBackupTelegramUploadEnabled && telegramBackupUploader.isAvailable) {\n\t\t\t\ttelegramBackupUploader.uploadBackup(output)\n\t\t\t}\n\t\t} finally {\n\t\t\toutput.delete()\n\t\t}\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) {\n\t\tif (!applicationContext.checkNotificationPermission(CHANNEL_ID)) {\n\t\t\treturn\n\t\t}\n\t\tBaseBackupRestoreService.createNotificationChannel(applicationContext)\n\t\tval notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_HIGH)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setAutoCancel(true)\n\t\tval title = getString(R.string.periodic_backups)\n\t\tval message = getString(\n\t\t\tR.string.inline_preference_pattern,\n\t\t\tgetString(R.string.packup_creation_failed),\n\t\t\terror.getDisplayMessage(resources),\n\t\t)\n\t\tnotification\n\t\t\t.setContentText(message)\n\t\t\t.setSmallIcon(android.R.drawable.stat_notify_error)\n\t\t\t.setStyle(\n\t\t\t\tNotificationCompat.BigTextStyle()\n\t\t\t\t\t.bigText(message)\n\t\t\t\t\t.setSummaryText(getString(R.string.packup_creation_failed))\n\t\t\t\t\t.setBigContentTitle(title),\n\t\t\t)\n\t\tErrorReporterReceiver.getNotificationAction(applicationContext, error, startId, TAG)?.let { action ->\n\t\t\tnotification.addAction(action)\n\t\t}\n\t\tnotification.setContentIntent(\n\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\tapplicationContext,\n\t\t\t\t0,\n\t\t\t\tAppRouter.periodicBackupSettingsIntent(applicationContext),\n\t\t\t\t0,\n\t\t\t\tfalse,\n\t\t\t),\n\t\t)\n\t\tNotificationManagerCompat.from(applicationContext).notify(TAG, startId, notification.build())\n\t}\n\n\tprivate companion object {\n\n\t\tconst val CHANNEL_ID = BaseBackupRestoreService.CHANNEL_ID\n\t\tconst val TAG = \"periodical_backup\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/periodical/PeriodicalBackupSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.periodical\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.text.format.DateUtils\nimport android.view.View\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.fragment.app.viewModels\nimport androidx.preference.EditTextPreference\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceCategory\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.os.OpenDocumentTreeHelper\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.settings.utils.EditTextFallbackSummaryProvider\nimport java.util.Date\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass PeriodicalBackupSettingsFragment : BasePreferenceFragment(R.string.periodic_backups),\n\tActivityResultCallback<Uri?> {\n\n\t@Inject\n\tlateinit var telegramBackupUploader: TelegramBackupUploader\n\n\tprivate val viewModel by viewModels<PeriodicalBackupSettingsViewModel>()\n\n\tprivate val outputSelectCall = OpenDocumentTreeHelper(this, this)\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_backup_periodic)\n\t\tfindPreference<PreferenceCategory>(AppSettings.KEY_BACKUP_TG)?.isVisible = viewModel.isTelegramAvailable\n\t\tfindPreference<EditTextPreference>(AppSettings.KEY_BACKUP_TG_CHAT)?.summaryProvider =\n\t\t\tEditTextFallbackSummaryProvider(R.string.telegram_chat_id_summary)\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tviewModel.lastBackupDate.observe(viewLifecycleOwner, ::bindLastBackupInfo)\n\t\tviewModel.backupsDirectory.observe(viewLifecycleOwner, ::bindOutputSummary)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))\n\t\tviewModel.isTelegramCheckLoading.observe(viewLifecycleOwner) {\n\t\t\tfindPreference<Preference>(AppSettings.KEY_BACKUP_TG_TEST)?.isEnabled = !it\n\t\t}\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\tval result = when (preference.key) {\n\t\t\tAppSettings.KEY_BACKUP_PERIODICAL_OUTPUT -> outputSelectCall.tryLaunch(null)\n\t\t\tAppSettings.KEY_BACKUP_TG_OPEN -> telegramBackupUploader.openBotInApp(router)\n\t\t\tAppSettings.KEY_BACKUP_TG_TEST -> {\n\t\t\t\tviewModel.checkTelegram()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> return super.onPreferenceTreeClick(preference)\n\t\t}\n\t\tif (!result) {\n\t\t\tSnackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onActivityResult(result: Uri?) {\n\t\tif (result != null) {\n\t\t\tval takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION\n\t\t\tcontext?.contentResolver?.takePersistableUriPermission(result, takeFlags)\n\t\t\tsettings.periodicalBackupDirectory = result\n\t\t\tviewModel.updateSummaryData()\n\t\t}\n\t}\n\n\tprivate fun bindOutputSummary(path: String?) {\n\t\tval preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT) ?: return\n\t\tpreference.summary = when (path) {\n\t\t\tnull -> getString(R.string.invalid_value_message)\n\t\t\t\"\" -> null\n\t\t\telse -> path\n\t\t}\n\t\tpreference.icon = if (path == null) {\n\t\t\tgetWarningIcon()\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate fun bindLastBackupInfo(lastBackupDate: Date?) {\n\t\tval preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_LAST) ?: return\n\t\tpreference.summary = lastBackupDate?.let {\n\t\t\tpreference.context.getString(\n\t\t\t\tR.string.last_successful_backup,\n\t\t\t\tDateUtils.getRelativeTimeSpanString(it.time),\n\t\t\t)\n\t\t}\n\t\tpreference.isVisible = lastBackupDate != null\n\t}\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/periodical/PeriodicalBackupSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.periodical\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.documentfile.provider.DocumentFile\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.domain.BackupUtils\nimport org.koitharu.kotatsu.backups.domain.ExternalBackupStorage\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.resolveFile\nimport java.util.Date\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PeriodicalBackupSettingsViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val telegramUploader: TelegramBackupUploader,\n\tprivate val backupStorage: ExternalBackupStorage,\n\t@ApplicationContext private val appContext: Context,\n) : BaseViewModel() {\n\n\tval isTelegramAvailable\n\t\tget() = telegramUploader.isAvailable\n\n\tval lastBackupDate = MutableStateFlow<Date?>(null)\n\tval backupsDirectory = MutableStateFlow<String?>(\"\")\n\tval isTelegramCheckLoading = MutableStateFlow(false)\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\n\tinit {\n\t\tupdateSummaryData()\n\t}\n\n\tfun checkTelegram() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\ttry {\n\t\t\t\tisTelegramCheckLoading.value = true\n\t\t\t\ttelegramUploader.sendTestMessage()\n\t\t\t\tonActionDone.call(ReversibleAction(R.string.connection_ok, null))\n\t\t\t} finally {\n\t\t\t\tisTelegramCheckLoading.value = false\n\t\t\t}\n\t\t}\n\t}\n\n\tfun updateSummaryData() {\n\t\tupdateBackupsDirectory()\n\t\tupdateLastBackupDate()\n\t}\n\n\tprivate fun updateBackupsDirectory() = launchJob(Dispatchers.Default) {\n\t\tval dir = settings.periodicalBackupDirectory\n\t\tbackupsDirectory.value = if (dir != null) {\n\t\t\tdir.toUserFriendlyString()\n\t\t} else {\n\t\t\tBackupUtils.getAppBackupDir(appContext).path\n\t\t}\n\t}\n\n\tprivate fun updateLastBackupDate() = launchJob(Dispatchers.Default) {\n\t\tlastBackupDate.value = backupStorage.getLastBackupDate()\n\t}\n\n\tprivate fun Uri.toUserFriendlyString(): String? {\n\t\tval df = DocumentFile.fromTreeUri(appContext, this)\n\t\tif (df?.canWrite() != true) {\n\t\t\treturn null\n\t\t}\n\t\treturn resolveFile(appContext)?.path ?: toString()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/periodical/TelegramBackupUploader.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.periodical\n\nimport android.content.Context\nimport androidx.annotation.CheckResult\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport okhttp3.HttpUrl\nimport okhttp3.MediaType.Companion.toMediaTypeOrNull\nimport okhttp3.MultipartBody\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.asRequestBody\nimport okhttp3.Response\nimport okhttp3.internal.closeQuietly\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault\nimport org.koitharu.kotatsu.parsers.util.json.getStringOrNull\nimport org.koitharu.kotatsu.parsers.util.parseJson\nimport java.io.File\nimport javax.inject.Inject\n\nclass TelegramBackupUploader @Inject constructor(\n\tprivate val settings: AppSettings,\n\t@BaseHttpClient private val client: OkHttpClient,\n\t@ApplicationContext private val context: Context,\n) {\n\n\tprivate val botToken = context.getString(R.string.tg_backup_bot_token)\n\n\tval isAvailable: Boolean\n\t\tget() = botToken.isNotEmpty()\n\n\tsuspend fun uploadBackup(file: File) {\n\t\tval requestBody = file.asRequestBody(\"application/zip\".toMediaTypeOrNull())\n\t\tval multipartBody = MultipartBody.Builder()\n\t\t\t.setType(MultipartBody.FORM)\n\t\t\t.addFormDataPart(\"chat_id\", requireChatId())\n\t\t\t.addFormDataPart(\"document\", file.name, requestBody)\n\t\t\t.build()\n\t\tval request = Request.Builder()\n\t\t\t.url(urlOf(\"sendDocument\").build())\n\t\t\t.post(multipartBody)\n\t\t\t.build()\n\t\tclient.newCall(request).await().consume()\n\t}\n\n\tsuspend fun sendTestMessage() {\n\t\tval request = Request.Builder()\n\t\t\t.url(urlOf(\"getMe\").build())\n\t\t\t.build()\n\t\tclient.newCall(request).await().consume()\n\t\tsendMessage(context.getString(R.string.backup_tg_echo))\n\t}\n\n\t@CheckResult\n\tfun openBotInApp(router: AppRouter): Boolean {\n\t\tval botUsername = context.getString(R.string.tg_backup_bot_name)\n\t\treturn router.openExternalBrowser(\"tg://resolve?domain=$botUsername\") ||\n\t\t\trouter.openExternalBrowser(\"https://t.me/$botUsername\")\n\t}\n\n\tprivate suspend fun sendMessage(message: String) {\n\t\tval url = urlOf(\"sendMessage\")\n\t\t\t.addQueryParameter(\"chat_id\", requireChatId())\n\t\t\t.addQueryParameter(\"text\", message)\n\t\t\t.build()\n\t\tval request = Request.Builder()\n\t\t\t.url(url)\n\t\t\t.build()\n\t\tclient.newCall(request).await().consume()\n\t}\n\n\tprivate fun requireChatId() = checkNotNull(settings.backupTelegramChatId) {\n\t\t\"Telegram chat ID not set in settings\"\n\t}\n\n\tprivate fun Response.consume() {\n\t\tif (isSuccessful) {\n\t\t\tcloseQuietly()\n\t\t\treturn\n\t\t}\n\t\tval jo = parseJson()\n\t\tif (!jo.getBooleanOrDefault(\"ok\", true)) {\n\t\t\tthrow RuntimeException(jo.getStringOrNull(\"description\"))\n\t\t}\n\t}\n\n\tprivate fun urlOf(method: String) = HttpUrl.Builder()\n\t\t.scheme(\"https\")\n\t\t.host(\"api.telegram.org\")\n\t\t.addPathSegment(\"bot$botToken\")\n\t\t.addPathSegment(method)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/restore/BackupEntriesAdapter.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.restore\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.setChecked\nimport org.koitharu.kotatsu.databinding.ItemCheckableMultipleBinding\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_CHECKED_CHANGED\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\n\nclass BackupSectionsAdapter(\n\tclickListener: OnListItemClickListener<BackupSectionModel>,\n) : BaseListAdapter<BackupSectionModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.NAV_ITEM, backupSectionAD(clickListener))\n\t}\n}\n\nprivate fun backupSectionAD(\n\tclickListener: OnListItemClickListener<BackupSectionModel>,\n) = adapterDelegateViewBinding<BackupSectionModel, BackupSectionModel, ItemCheckableMultipleBinding>(\n\t{ layoutInflater, parent -> ItemCheckableMultipleBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.root.setOnClickListener { v ->\n\t\tclickListener.onItemClick(item, v)\n\t}\n\n\tbind { payloads ->\n\t\twith(binding.root) {\n\t\t\tsetText(item.titleResId)\n\t\t\tsetChecked(item.isChecked, PAYLOAD_CHECKED_CHANGED in payloads)\n\t\t\tisEnabled = item.isEnabled\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/restore/BackupSectionModel.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.restore\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.domain.BackupSection\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class BackupSectionModel(\n\tval section: BackupSection,\n\tval isChecked: Boolean,\n\tval isEnabled: Boolean,\n) : ListModel {\n\n\t@get:StringRes\n\tval titleResId: Int\n\t\tget() = when (section) {\n\t\t\tBackupSection.INDEX -> 0 // should not appear here\n\t\t\tBackupSection.HISTORY -> R.string.history\n\t\t\tBackupSection.CATEGORIES -> R.string.favourites_categories\n\t\t\tBackupSection.FAVOURITES -> R.string.favourites\n\t\t\tBackupSection.SETTINGS -> R.string.settings\n\t\t\tBackupSection.SETTINGS_READER_GRID -> R.string.reader_actions\n\t\t\tBackupSection.BOOKMARKS -> R.string.bookmarks\n\t\t\tBackupSection.SOURCES -> R.string.remote_sources\n\t\t\tBackupSection.SCROBBLING -> R.string.tracking\n\t\t\tBackupSection.STATS -> R.string.statistics\n\t\t\tBackupSection.SAVED_FILTERS -> R.string.saved_filters\n\t\t}\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is BackupSectionModel && other.section == section\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\tif (previousState !is BackupSectionModel) {\n\t\t\treturn null\n\t\t}\n\t\treturn if (previousState.isEnabled != isEnabled) {\n\t\t\tListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED\n\t\t} else if (previousState.isChecked != isChecked) {\n\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/restore/RestoreDialogFragment.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.restore\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.combine\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.DialogRestoreBinding\nimport java.text.DateFormat\nimport java.text.SimpleDateFormat\nimport java.util.Date\n\n@AndroidEntryPoint\nclass RestoreDialogFragment : AlertDialogFragment<DialogRestoreBinding>(), OnListItemClickListener<BackupSectionModel>,\n\tView.OnClickListener {\n\n\tprivate val viewModel: RestoreViewModel by viewModels()\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = DialogRestoreBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: DialogRestoreBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval adapter = BackupSectionsAdapter(this)\n\t\tbinding.recyclerView.adapter = adapter\n\t\tbinding.buttonCancel.setOnClickListener(this)\n\t\tbinding.buttonRestore.setOnClickListener(this)\n\t\tviewModel.availableEntries.observe(viewLifecycleOwner, adapter)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, this::onError)\n\t\tcombine(\n\t\t\tviewModel.isLoading,\n\t\t\tviewModel.availableEntries,\n\t\t\tviewModel.backupDate,\n\t\t\t::Triple,\n\t\t).observe(viewLifecycleOwner, this::onLoadingChanged)\n\t}\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder)\n\t\t\t.setTitle(R.string.restore_backup)\n\t\t\t.setCancelable(false)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_cancel -> dismiss()\n\t\t\tR.id.button_restore -> {\n\t\t\t\tif (startRestoreService()) {\n\t\t\t\t\tToast.makeText(v.context, R.string.backup_restored_background, Toast.LENGTH_SHORT).show()\n\t\t\t\t\trouter.closeWelcomeSheet()\n\t\t\t\t\tdismiss()\n\t\t\t\t} else {\n\t\t\t\t\tToast.makeText(v.context, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onItemClick(item: BackupSectionModel, view: View) {\n\t\tviewModel.onItemClick(item)\n\t}\n\n\tprivate fun onLoadingChanged(value: Triple<Boolean, List<BackupSectionModel>, Date?>) {\n\t\tval (isLoading, entries, backupDate) = value\n\t\tval hasEntries = entries.isNotEmpty()\n\t\twith(requireViewBinding()) {\n\t\t\tprogressBar.isVisible = isLoading\n\t\t\trecyclerView.isGone = isLoading\n\t\t\ttextViewSubtitle.textAndVisible =\n\t\t\t\twhen {\n\t\t\t\t\t!isLoading -> backupDate?.formatBackupDate()\n\t\t\t\t\thasEntries -> getString(R.string.processing_)\n\t\t\t\t\telse -> getString(R.string.loading_)\n\t\t\t\t}\n\t\t\tbuttonRestore.isEnabled = !isLoading && entries.any { it.isChecked }\n\t\t}\n\t}\n\n\tprivate fun startRestoreService(): Boolean {\n\t\treturn RestoreService.start(\n\t\t\tcontext ?: return false,\n\t\t\tviewModel.uri ?: return false,\n\t\t\tviewModel.getCheckedSections(),\n\t\t)\n\t}\n\n\tprivate fun Date.formatBackupDate(): String {\n\t\treturn getString(\n\t\t\tR.string.backup_date_,\n\t\t\tSimpleDateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(this),\n\t\t)\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tMaterialAlertDialogBuilder(context ?: return)\n\t\t\t.setNegativeButton(R.string.close, null)\n\t\t\t.setTitle(R.string.error)\n\t\t\t.setMessage(e.getDisplayMessage(resources))\n\t\t\t.show()\n\t\tdismiss()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/restore/RestoreService.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.restore\n\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport android.net.Uri\nimport androidx.annotation.CheckResult\nimport androidx.core.app.NotificationCompat\nimport androidx.core.content.ContextCompat\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.data.BackupRepository\nimport org.koitharu.kotatsu.backups.domain.BackupSection\nimport org.koitharu.kotatsu.backups.ui.BaseBackupRestoreService\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.powerManager\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.core.util.ext.withPartialWakeLock\nimport org.koitharu.kotatsu.core.util.progress.Progress\nimport java.io.FileNotFoundException\nimport java.util.zip.ZipInputStream\nimport javax.inject.Inject\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\n@SuppressLint(\"InlinedApi\")\nclass RestoreService : BaseBackupRestoreService() {\n\n\toverride val notificationTag = TAG\n\toverride val isRestoreService = true\n\n\t@Inject\n\tlateinit var repository: BackupRepository\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tval notification = buildNotification(Progress.INDETERMINATE)\n\t\tsetForeground(\n\t\t\tFOREGROUND_NOTIFICATION_ID,\n\t\t\tnotification,\n\t\t\tServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,\n\t\t)\n\t\tval source = intent.getStringExtra(AppRouter.KEY_DATA)?.toUriOrNull() ?: throw FileNotFoundException()\n\t\tval sections =\n\t\t\trequireNotNull(intent.getSerializableExtraCompat<Array<BackupSection>>(AppRouter.KEY_ENTRIES)?.toSet())\n\t\tpowerManager.withPartialWakeLock(TAG) {\n\t\t\tval progress = MutableStateFlow(Progress.INDETERMINATE)\n\t\t\tval progressUpdateJob = if (checkNotificationPermission(CHANNEL_ID)) {\n\t\t\t\tlaunch {\n\t\t\t\t\tprogress.collect {\n\t\t\t\t\t\tnotificationManager.notify(FOREGROUND_NOTIFICATION_ID, buildNotification(it))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t\tval result = ZipInputStream(contentResolver.openInputStream(source)).use { input ->\n\t\t\t\trepository.restoreBackup(input, sections, progress)\n\t\t\t}\n\t\t\tprogressUpdateJob?.cancelAndJoin()\n\t\t\tshowResultNotification(source, result)\n\t\t}\n\t}\n\n\tprivate fun IntentJobContext.buildNotification(progress: Progress): Notification {\n\t\treturn NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\t\t.setContentTitle(getString(R.string.restoring_backup))\n\t\t\t.setPriority(NotificationCompat.PRIORITY_HIGH)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setOngoing(true)\n\t\t\t.setProgress(\n\t\t\t\tprogress.total.coerceAtLeast(0),\n\t\t\t\tprogress.progress.coerceAtLeast(0),\n\t\t\t\tprogress.isIndeterminate,\n\t\t\t)\n\t\t\t.setContentText(\n\t\t\t\tif (progress.isIndeterminate) {\n\t\t\t\t\tgetString(R.string.processing_)\n\t\t\t\t} else {\n\t\t\t\t\tgetString(R.string.fraction_pattern, progress.progress, progress.total)\n\t\t\t\t},\n\t\t\t)\n\t\t\t.setSmallIcon(android.R.drawable.stat_sys_upload)\n\t\t\t.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)\n\t\t\t.setCategory(NotificationCompat.CATEGORY_PROGRESS)\n\t\t\t.addAction(\n\t\t\t\tappcompatR.drawable.abc_ic_clear_material,\n\t\t\t\tapplicationContext.getString(android.R.string.cancel),\n\t\t\t\tgetCancelIntent(),\n\t\t\t).build()\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val TAG = \"RESTORE\"\n\t\tprivate const val FOREGROUND_NOTIFICATION_ID = 39\n\n\t\t@CheckResult\n\t\tfun start(context: Context, uri: Uri, sections: Set<BackupSection>): Boolean = try {\n\t\t\tval intent = Intent(context, RestoreService::class.java)\n\t\t\tintent.putExtra(AppRouter.KEY_DATA, uri.toString())\n\t\t\tintent.putExtra(AppRouter.KEY_ENTRIES, sections.toTypedArray())\n\t\t\tContextCompat.startForegroundService(context, intent)\n\t\t\ttrue\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTraceDebug()\n\t\t\tfalse\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/backups/ui/restore/RestoreViewModel.kt",
    "content": "package org.koitharu.kotatsu.backups.ui.restore\n\nimport android.content.Context\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.serialization.json.Json\nimport kotlinx.serialization.json.decodeFromStream\nimport org.koitharu.kotatsu.backups.data.model.BackupIndex\nimport org.koitharu.kotatsu.backups.domain.BackupSection\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport java.io.FileNotFoundException\nimport java.io.InputStream\nimport java.util.Date\nimport java.util.EnumMap\nimport java.util.EnumSet\nimport java.util.zip.ZipInputStream\nimport javax.inject.Inject\n\n@HiltViewModel\nclass RestoreViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\t@ApplicationContext context: Context,\n) : BaseViewModel() {\n\n\tval uri = savedStateHandle.get<String>(AppRouter.KEY_FILE)?.toUriOrNull()\n\tprivate val contentResolver = context.contentResolver\n\n\tval availableEntries = MutableStateFlow<List<BackupSectionModel>>(emptyList())\n\tval backupDate = MutableStateFlow<Date?>(null)\n\n\tinit {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tloadBackupInfo()\n\t\t}\n\t}\n\n\tprivate suspend fun loadBackupInfo() {\n\t\tval sections = runInterruptible(Dispatchers.IO) {\n\t\t\tif (uri == null) throw FileNotFoundException()\n\t\t\tZipInputStream(contentResolver.openInputStream(uri)).use { stream ->\n\t\t\t\tval result = EnumSet.noneOf(BackupSection::class.java)\n\t\t\t\tvar entry = stream.nextEntry\n\t\t\t\twhile (entry != null) {\n\t\t\t\t\tval s = BackupSection.of(entry)\n\t\t\t\t\tif (s != null) {\n\t\t\t\t\t\tresult.add(s)\n\t\t\t\t\t\tif (s == BackupSection.INDEX) {\n\t\t\t\t\t\t\tbackupDate.value = stream.readDate()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tstream.closeEntry()\n\t\t\t\t\tentry = stream.nextEntry\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t\t}\n\t\tavailableEntries.value = BackupSection.entries.mapNotNull { entry ->\n\t\t\tif (entry == BackupSection.INDEX || entry !in sections) {\n\t\t\t\treturn@mapNotNull null\n\t\t\t}\n\t\t\tBackupSectionModel(\n\t\t\t\tsection = entry,\n\t\t\t\tisChecked = true,\n\t\t\t\tisEnabled = true,\n\t\t\t)\n\t\t}\n\t}\n\n\tfun onItemClick(item: BackupSectionModel) {\n\t\tval map = availableEntries.value.associateByTo(EnumMap(BackupSection::class.java)) { it.section }\n\t\tmap[item.section] = item.copy(isChecked = !item.isChecked)\n\t\tmap.validate()\n\t\tavailableEntries.value = map.values.sortedBy { it.section.ordinal }\n\t}\n\n\tfun getCheckedSections(): Set<BackupSection> = availableEntries.value\n\t\t.mapNotNullTo(EnumSet.noneOf(BackupSection::class.java)) {\n\t\t\tif (it.isChecked) it.section else null\n\t\t}\n\n\t/**\n\t * Check for inconsistent user selection\n\t * Favorites cannot be restored without categories\n\t */\n\tprivate fun MutableMap<BackupSection, BackupSectionModel>.validate() {\n\t\tval favorites = this[BackupSection.FAVOURITES] ?: return\n\t\tval categories = this[BackupSection.CATEGORIES]\n\t\tif (categories?.isChecked == true) {\n\t\t\tif (!favorites.isEnabled) {\n\t\t\t\tthis[BackupSection.FAVOURITES] = favorites.copy(isEnabled = true)\n\t\t\t}\n\t\t} else {\n\t\t\tif (favorites.isEnabled) {\n\t\t\t\tthis[BackupSection.FAVOURITES] = favorites.copy(isEnabled = false, isChecked = false)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun InputStream.readDate(): Date? = runCatching {\n\t\tval index = Json.decodeFromStream<List<BackupIndex>>(this)\n\t\tDate(index.single().createdAt)\n\t}.onFailure { e ->\n\t\te.printStackTraceDebug()\n\t}.getOrNull()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/BookmarkEntity.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.data\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\n@Entity(\n\ttableName = \"bookmarks\",\n\tprimaryKeys = [\"manga_id\", \"page_id\"],\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE\n\t\t),\n\t]\n)\ndata class BookmarkEntity(\n\t@ColumnInfo(name = \"manga_id\", index = true) val mangaId: Long,\n\t@ColumnInfo(name = \"page_id\", index = true) val pageId: Long,\n\t@ColumnInfo(name = \"chapter_id\") val chapterId: Long,\n\t@ColumnInfo(name = \"page\") val page: Int,\n\t@ColumnInfo(name = \"scroll\") val scroll: Int,\n\t@ColumnInfo(name = \"image\") val imageUrl: String,\n\t@ColumnInfo(name = \"created_at\") val createdAt: Long,\n\t@ColumnInfo(name = \"percent\") val percent: Float,\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/BookmarksDao.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.data\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport androidx.room.Upsert\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\n\n@Dao\nabstract class BookmarksDao {\n\n\t@Query(\"SELECT * FROM bookmarks WHERE page_id = :pageId\")\n\tabstract suspend fun find(pageId: Long): BookmarkEntity?\n\n\t@Transaction\n\t@Query(\n\t\t\"SELECT * FROM manga JOIN bookmarks ON bookmarks.manga_id = manga.manga_id ORDER BY percent LIMIT :limit OFFSET :offset\",\n\t)\n\tabstract suspend fun findAll(offset: Int, limit: Int): Map<MangaWithTags, List<BookmarkEntity>>\n\n\t@Query(\"SELECT * FROM bookmarks WHERE manga_id = :mangaId AND chapter_id = :chapterId AND page = :page ORDER BY percent\")\n\tabstract fun observe(mangaId: Long, chapterId: Long, page: Int): Flow<BookmarkEntity?>\n\n\t@Query(\"SELECT * FROM bookmarks WHERE manga_id = :mangaId ORDER BY percent\")\n\tabstract fun observe(mangaId: Long): Flow<List<BookmarkEntity>>\n\n\t@Transaction\n\t@Query(\n\t\t\"SELECT * FROM manga JOIN bookmarks ON bookmarks.manga_id = manga.manga_id ORDER BY percent\",\n\t)\n\tabstract fun observe(): Flow<Map<MangaWithTags, List<BookmarkEntity>>>\n\n\t@Insert\n\tabstract suspend fun insert(entity: BookmarkEntity)\n\n\t@Delete\n\tabstract suspend fun delete(entity: BookmarkEntity)\n\n\t@Query(\"DELETE FROM bookmarks WHERE page_id = :pageId\")\n\tabstract suspend fun delete(pageId: Long): Int\n\n\t@Query(\"DELETE FROM bookmarks WHERE manga_id = :mangaId AND chapter_id = :chapterId AND page = :page\")\n\tabstract suspend fun delete(mangaId: Long, chapterId: Long, page: Int): Int\n\n\t@Upsert\n\tabstract suspend fun upsert(bookmarks: Collection<BookmarkEntity>)\n\n\tfun dump(): Flow<Pair<MangaWithTags, List<BookmarkEntity>>> = flow {\n\t\tval window = 4\n\t\tvar offset = 0\n\t\twhile (currentCoroutineContext().isActive) {\n\t\t\tval list = findAll(offset, window)\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset += window\n\t\t\tlist.forEach { emit(it.key to it.value) }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/data/EntityMapping.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.data\n\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.time.Instant\n\nfun BookmarkEntity.toBookmark(manga: Manga) = Bookmark(\n\tmanga = manga,\n\tpageId = pageId,\n\tchapterId = chapterId,\n\tpage = page,\n\tscroll = scroll,\n\timageUrl = imageUrl,\n\tcreatedAt = Instant.ofEpochMilli(createdAt),\n\tpercent = percent,\n)\n\nfun Bookmark.toEntity() = BookmarkEntity(\n\tmangaId = manga.id,\n\tpageId = pageId,\n\tchapterId = chapterId,\n\tpage = page,\n\tscroll = scroll,\n\timageUrl = imageUrl,\n\tcreatedAt = createdAt.toEpochMilli(),\n\tpercent = percent,\n)\n\nfun Collection<BookmarkEntity>.toBookmarks(manga: Manga) = map {\n\tit.toBookmark(manga)\n}\n\n@JvmName(\"bookmarksIds\")\nfun Collection<Bookmark>.ids() = map { it.pageId }\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/domain/Bookmark.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.domain\n\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.isImage\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport java.time.Instant\n\ndata class Bookmark(\n\tval manga: Manga,\n\tval pageId: Long,\n\tval chapterId: Long,\n\tval page: Int,\n\tval scroll: Int,\n\tval imageUrl: String,\n\tval createdAt: Instant,\n\tval percent: Float,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is Bookmark &&\n\t\t\tmanga.id == other.manga.id &&\n\t\t\tchapterId == other.chapterId &&\n\t\t\tpage == other.page\n\t}\n\n\tfun toMangaPage() = MangaPage(\n\t\tid = pageId,\n\t\turl = imageUrl,\n\t\tpreview = imageUrl.takeIf {\n\t\t\tMimeTypes.getMimeTypeFromUrl(it)?.isImage == true\n\t\t},\n\t\tsource = manga.source,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.domain\n\nimport android.database.SQLException\nimport androidx.room.withTransaction\nimport dagger.Reusable\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.bookmarks.data.BookmarkEntity\nimport org.koitharu.kotatsu.bookmarks.data.toBookmark\nimport org.koitharu.kotatsu.bookmarks.data.toBookmarks\nimport org.koitharu.kotatsu.bookmarks.data.toEntity\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toEntities\nimport org.koitharu.kotatsu.core.db.entity.toEntity\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.ui.util.ReversibleHandle\nimport org.koitharu.kotatsu.core.util.ext.mapItems\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\n@Reusable\nclass BookmarksRepository @Inject constructor(\n\tprivate val db: MangaDatabase,\n) {\n\n\tfun observeBookmark(manga: Manga, chapterId: Long, page: Int): Flow<Bookmark?> {\n\t\treturn db.getBookmarksDao().observe(manga.id, chapterId, page).map { it?.toBookmark(manga) }\n\t}\n\n\tfun observeBookmarks(manga: Manga): Flow<List<Bookmark>> {\n\t\treturn db.getBookmarksDao().observe(manga.id).mapItems { it.toBookmark(manga) }\n\t}\n\n\tfun observeBookmarks(): Flow<Map<Manga, List<Bookmark>>> {\n\t\treturn db.getBookmarksDao().observe().map { map ->\n\t\t\tval res = LinkedHashMap<Manga, List<Bookmark>>(map.size)\n\t\t\tfor ((k, v) in map) {\n\t\t\t\tval manga = k.toManga()\n\t\t\t\tres[manga] = v.toBookmarks(manga)\n\t\t\t}\n\t\t\tres\n\t\t}\n\t}\n\n\tsuspend fun addBookmark(bookmark: Bookmark) {\n\t\tdb.withTransaction {\n\t\t\tval tags = bookmark.manga.tags.toEntities()\n\t\t\tdb.getTagsDao().upsert(tags)\n\t\t\tdb.getMangaDao().upsert(bookmark.manga.toEntity(), tags)\n\t\t\tdb.getBookmarksDao().insert(bookmark.toEntity())\n\t\t}\n\t}\n\n\tsuspend fun updateBookmark(bookmark: Bookmark, imageUrl: String) {\n\t\tval entity = bookmark.toEntity().copy(\n\t\t\timageUrl = imageUrl,\n\t\t)\n\t\tdb.getBookmarksDao().upsert(listOf(entity))\n\t}\n\n\tsuspend fun removeBookmark(mangaId: Long, chapterId: Long, page: Int) {\n\t\tcheck(db.getBookmarksDao().delete(mangaId, chapterId, page) != 0) {\n\t\t\t\"Bookmark not found\"\n\t\t}\n\t}\n\n\tsuspend fun removeBookmark(bookmark: Bookmark) {\n\t\tremoveBookmark(bookmark.manga.id, bookmark.chapterId, bookmark.page)\n\t}\n\n\tsuspend fun removeBookmarks(ids: Set<Long>): ReversibleHandle {\n\t\tval entities = ArrayList<BookmarkEntity>(ids.size)\n\t\tdb.withTransaction {\n\t\t\tval dao = db.getBookmarksDao()\n\t\t\tfor (pageId in ids) {\n\t\t\t\tval e = dao.find(pageId)\n\t\t\t\tif (e != null) {\n\t\t\t\t\tentities.add(e)\n\t\t\t\t}\n\t\t\t\tdao.delete(pageId)\n\t\t\t}\n\t\t}\n\t\treturn BookmarksRestorer(entities)\n\t}\n\n\tprivate inner class BookmarksRestorer(\n\t\tprivate val entities: Collection<BookmarkEntity>,\n\t) : ReversibleHandle {\n\n\t\toverride suspend fun reverse() {\n\t\t\tdb.withTransaction {\n\t\t\t\tfor (e in entities) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdb.getBookmarksDao().insert(e)\n\t\t\t\t\t} catch (e: SQLException) {\n\t\t\t\t\t\te.printStackTraceDebug()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksActivity.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.ui\n\nimport org.koitharu.kotatsu.core.ui.FragmentContainerActivity\n\nclass AllBookmarksActivity : FragmentContainerActivity(AllBookmarksFragment::class.java)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.ui\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.FragmentListSimpleBinding\nimport org.koitharu.kotatsu.list.ui.GridSpanResolver\nimport org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass AllBookmarksFragment :\n\tBaseFragment<FragmentListSimpleBinding>(),\n\tListStateHolderListener,\n\tOnListItemClickListener<Bookmark>,\n\tListSelectionController.Callback,\n\tFastScroller.FastScrollListener, ListHeaderClickListener {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\t@Inject\n\tlateinit var pageSaveHelperFactory: PageSaveHelper.Factory\n\n\tprivate lateinit var pageSaveHelper: PageSaveHelper\n\tprivate val viewModel by viewModels<AllBookmarksViewModel>()\n\tprivate var bookmarksAdapter: BookmarksAdapter? = null\n\tprivate var selectionController: ListSelectionController? = null\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tpageSaveHelper = pageSaveHelperFactory.create(this)\n\t}\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t): FragmentListSimpleBinding {\n\t\treturn FragmentListSimpleBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(\n\t\tbinding: FragmentListSimpleBinding,\n\t\tsavedInstanceState: Bundle?,\n\t) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = checkNotNull(findAppCompatDelegate()),\n\t\t\tdecoration = BookmarksSelectionDecoration(binding.root.context),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = this,\n\t\t)\n\t\tbookmarksAdapter = BookmarksAdapter(\n\t\t\tclickListener = this,\n\t\t\theaderClickListener = this,\n\t\t)\n\t\tval spanSizeLookup = SpanSizeLookup()\n\t\twith(binding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\tval spanResolver = GridSpanResolver(resources)\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\tadapter = bookmarksAdapter\n\t\t\taddOnLayoutChangeListener(spanResolver)\n\t\t\tspanResolver.setGridSize(settings.gridSize / 100f, this)\n\t\t\tval lm = GridLayoutManager(context, spanResolver.spanCount)\n\t\t\tlm.spanSizeLookup = spanSizeLookup\n\t\t\tlayoutManager = lm\n\t\t\tselectionController?.attachToRecyclerView(this)\n\t\t}\n\t\tviewModel.content.observe(viewLifecycleOwner) {\n\t\t\tbookmarksAdapter?.setItems(it, spanSizeLookup)\n\t\t}\n\t\tviewModel.onError.observeEvent(\n\t\t\tviewLifecycleOwner,\n\t\t\tSnackbarErrorObserver(binding.recyclerView, this),\n\t\t)\n\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval basePadding = resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\tbarsInsets.left + basePadding,\n\t\t\tbarsInsets.top + basePadding,\n\t\t\tbarsInsets.right + basePadding,\n\t\t\tbarsInsets.bottom + basePadding,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\tbookmarksAdapter = null\n\t\tselectionController = null\n\t}\n\n\toverride fun onItemClick(item: Bookmark, view: View) {\n\t\tif (selectionController?.onItemClick(item.pageId) != true) {\n\t\t\tval intent = ReaderIntent.Builder(view.context)\n\t\t\t\t.bookmark(item)\n\t\t\t\t.incognito()\n\t\t\t\t.build()\n\t\t\trouter.openReader(intent)\n\t\t\tToast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()\n\t\t}\n\t}\n\n\toverride fun onListHeaderClick(item: ListHeader, view: View) {\n\t\tval manga = item.payload as? Manga ?: return\n\t\trouter.openDetails(manga)\n\t}\n\n\toverride fun onItemLongClick(item: Bookmark, view: View): Boolean {\n\t\treturn selectionController?.onItemLongClick(view, item.pageId) == true\n\t}\n\n\toverride fun onItemContextClick(item: Bookmark, view: View): Boolean {\n\t\treturn selectionController?.onItemContextClick(view, item.pageId) == true\n\t}\n\n\toverride fun onRetryClick(error: Throwable) = Unit\n\n\toverride fun onEmptyActionClick() = Unit\n\n\toverride fun onFastScrollStart(fastScroller: FastScroller) {\n\t\t(activity as? AppBarOwner)?.appBar?.setExpanded(false, true)\n\t}\n\n\toverride fun onFastScrollStop(fastScroller: FastScroller) = Unit\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\trequireViewBinding().recyclerView.invalidateItemDecorations()\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu,\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_bookmarks, menu)\n\t\treturn true\n\t}\n\n\toverride fun onActionItemClicked(\n\t\tcontroller: ListSelectionController,\n\t\tmode: ActionMode?,\n\t\titem: MenuItem,\n\t): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_remove -> {\n\t\t\t\tval ids = selectionController?.snapshot() ?: return false\n\t\t\t\tviewModel.removeBookmarks(ids)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_save -> {\n\t\t\t\tviewModel.savePages(pageSaveHelper, selectionController?.snapshot() ?: return false)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\tprivate inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup(), Runnable {\n\n\t\tinit {\n\t\t\tisSpanIndexCacheEnabled = true\n\t\t\tisSpanGroupIndexCacheEnabled = true\n\t\t}\n\n\t\toverride fun getSpanSize(position: Int): Int {\n\t\t\tval total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount\n\t\t\t\t?: return 1\n\t\t\treturn when (bookmarksAdapter?.getItemViewType(position)) {\n\t\t\t\tListItemType.PAGE_THUMB.ordinal -> 1\n\t\t\t\telse -> total\n\t\t\t}\n\t\t}\n\n\t\toverride fun run() {\n\t\t\tinvalidateSpanGroupIndexCache()\n\t\t\tinvalidateSpanIndexCache()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksViewModel.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.ui\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport javax.inject.Inject\n\n@HiltViewModel\nclass AllBookmarksViewModel @Inject constructor(\n\tprivate val repository: BookmarksRepository,\n) : BaseViewModel() {\n\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\n\tval content: StateFlow<List<ListModel>> = repository.observeBookmarks()\n\t\t.map { list ->\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tlistOf(\n\t\t\t\t\tEmptyState(\n\t\t\t\t\t\ticon = R.drawable.ic_empty_favourites,\n\t\t\t\t\t\ttextPrimary = R.string.no_bookmarks_yet,\n\t\t\t\t\t\ttextSecondary = R.string.no_bookmarks_summary,\n\t\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tmapList(list)\n\t\t\t}\n\t\t}\n\t\t.catch { e -> emit(listOf(e.toErrorState(canRetry = false))) }\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tfun removeBookmarks(ids: Set<Long>) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval handle = repository.removeBookmarks(ids)\n\t\t\tonActionDone.call(ReversibleAction(R.string.bookmarks_removed, handle))\n\t\t}\n\t}\n\n\tfun savePages(pageSaveHelper: PageSaveHelper, ids: Set<Long>) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval tasks = content.value.mapNotNull {\n\t\t\t\tif (it !is Bookmark || it.pageId !in ids) return@mapNotNull null\n\t\t\t\tPageSaveHelper.Task(\n\t\t\t\t\tmanga = it.manga,\n\t\t\t\t\tchapterId = it.chapterId,\n\t\t\t\t\tpageNumber = it.page + 1,\n\t\t\t\t\tpage = it.toMangaPage(),\n\t\t\t\t)\n\t\t\t}\n\t\t\tval dest = pageSaveHelper.save(tasks)\n\t\t\tval msg = if (dest.size == 1) R.string.page_saved else R.string.pages_saved\n\t\t\tonActionDone.call(ReversibleAction(msg, null))\n\t\t}\n\t}\n\n\tprivate fun mapList(data: Map<Manga, List<Bookmark>>): List<ListModel> {\n\t\tval result = ArrayList<ListModel>(data.values.sumOf { it.size + 1 })\n\t\tfor ((manga, bookmarks) in data) {\n\t\t\tresult.add(ListHeader(manga.title, R.string.more, manga))\n\t\t\tresult.addAll(bookmarks)\n\t\t}\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.ui\n\nimport android.content.Context\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.list.ui.MangaSelectionDecoration\n\nclass BookmarksSelectionDecoration(context: Context) : MangaSelectionDecoration(context) {\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return RecyclerView.NO_ID\n\t\tval item = holder.getItem(Bookmark::class.java) ?: return RecyclerView.NO_ID\n\t\treturn item.pageId\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkLargeAD.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.databinding.ItemBookmarkLargeBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun bookmarkLargeAD(\n\tclickListener: OnListItemClickListener<Bookmark>,\n) = adapterDelegateViewBinding<Bookmark, ListModel, ItemBookmarkLargeBinding>(\n\t{ inflater, parent -> ItemBookmarkLargeBinding.inflate(inflater, parent, false) },\n) {\n\tAdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)\n\n\tbind {\n\t\tbinding.imageViewThumb.setImageAsync(item)\n\t\tbinding.progressView.setProgress(item.percent, false)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt",
    "content": "package org.koitharu.kotatsu.bookmarks.ui.adapter\n\nimport android.content.Context\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.errorStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.listHeaderAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass BookmarksAdapter(\n\tclickListener: OnListItemClickListener<Bookmark>,\n\theaderClickListener: ListHeaderClickListener?,\n) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {\n\n\tinit {\n\t\taddDelegate(ListItemType.PAGE_THUMB, bookmarkLargeAD(clickListener))\n\t\taddDelegate(ListItemType.HEADER, listHeaderAD(headerClickListener))\n\t\taddDelegate(ListItemType.STATE_ERROR, errorStateListAD(null))\n\t\taddDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null))\n\t}\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn findHeader(position)?.getText(context)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/AdListUpdateService.kt",
    "content": "package org.koitharu.kotatsu.browser\n\nimport android.content.Intent\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.network.webview.adblock.AdBlock\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass AdListUpdateService : CoroutineIntentService() {\n\n\t@Inject\n\tlateinit var updater: AdBlock.Updater\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tupdater.updateList()\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/BaseBrowserActivity.kt",
    "content": "package org.koitharu.kotatsu.browser\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePadding\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.network.proxy.ProxyProvider\nimport org.koitharu.kotatsu.core.network.webview.adblock.AdBlock\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.util.ext.configureForParser\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.databinding.ActivityBrowserBinding\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nabstract class BaseBrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {\n\n\t@Inject\n\tlateinit var proxyProvider: ProxyProvider\n\n\t@Inject\n\tlateinit var mangaRepositoryFactory: MangaRepository.Factory\n\n\t@Inject\n\tlateinit var adBlock: AdBlock\n\n\tprivate lateinit var onBackPressedCallback: WebViewBackPressedCallback\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tif (!setContentViewWebViewSafe { ActivityBrowserBinding.inflate(layoutInflater) }) {\n\t\t\treturn\n\t\t}\n\t\tviewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)\n\t\tonBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)\n\t\tonBackPressedDispatcher.addCallback(onBackPressedCallback)\n\n\t\tval mangaSource = MangaSource(intent?.getStringExtra(AppRouter.KEY_SOURCE))\n\t\tval repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository\n\t\tval userAgent = intent?.getStringExtra(AppRouter.KEY_USER_AGENT)?.nullIfEmpty()\n\t\t\t?: repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)\n\t\tviewBinding.webView.configureForParser(userAgent)\n\n\t\tonCreate2(savedInstanceState, mangaSource, repository)\n\t}\n\n\tprotected abstract fun onCreate2(\n\t\tsavedInstanceState: Bundle?,\n\t\tsource: MangaSource,\n\t\trepository: ParserMangaRepository?\n\t)\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval type = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()\n\t\tval barsInsets = insets.getInsets(type)\n\t\tviewBinding.webView.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\ttop = barsInsets.top,\n\t\t)\n\t\treturn insets.consumeAll(type)\n\t}\n\n\toverride fun onPause() {\n\t\tviewBinding.webView.onPause()\n\t\tsuper.onPause()\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tviewBinding.webView.onResume()\n\t}\n\n\toverride fun onDestroy() {\n\t\tsuper.onDestroy()\n\t\tif (hasViewBinding()) {\n\t\t\tviewBinding.webView.stopLoading()\n\t\t\tviewBinding.webView.destroy()\n\t\t}\n\t}\n\n\toverride fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tviewBinding.progressBar.isVisible = isLoading\n\t}\n\n\toverride fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {\n\t\tthis.title = title\n\t\tsupportActionBar?.subtitle = subtitle\n\t}\n\n\toverride fun onHistoryChanged() {\n\t\tonBackPressedCallback.onHistoryChanged()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserActivity.kt",
    "content": "package org.koitharu.kotatsu.browser\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\n@AndroidEntryPoint\nclass BrowserActivity : BaseBrowserActivity() {\n\n\toverride fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) {\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tviewBinding.webView.webViewClient = BrowserClient(this, adBlock)\n\t\tlifecycleScope.launch {\n\t\t\ttry {\n\t\t\t\tproxyProvider.applyWebViewConfig()\n\t\t\t} catch (e: Exception) {\n\t\t\t\te.printStackTraceDebug()\n\t\t\t\tSnackbar.make(viewBinding.webView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()\n\t\t\t}\n\t\t\tif (savedInstanceState == null) {\n\t\t\t\tval url = intent?.dataString\n\t\t\t\tif (url.isNullOrEmpty()) {\n\t\t\t\t\tfinishAfterTransition()\n\t\t\t\t} else {\n\t\t\t\t\tonTitleChanged(\n\t\t\t\t\t\tintent?.getStringExtra(AppRouter.KEY_TITLE) ?: getString(R.string.loading_),\n\t\t\t\t\t\turl,\n\t\t\t\t\t)\n\t\t\t\t\tviewBinding.webView.loadUrl(url)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCreateOptionsMenu(menu: Menu): Boolean {\n\t\tsuper.onCreateOptionsMenu(menu)\n\t\tmenuInflater.inflate(R.menu.opt_browser, menu)\n\t\treturn true\n\t}\n\n\toverride fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {\n\t\tandroid.R.id.home -> {\n\t\t\tviewBinding.webView.stopLoading()\n\t\t\tfinishAfterTransition()\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_browser -> {\n\t\t\tif (!router.openExternalBrowser(viewBinding.webView.url.orEmpty(), item.title)) {\n\t\t\t\tSnackbar.make(viewBinding.webView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\t\t}\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onOptionsItemSelected(item)\n\t}\n\n\tclass Contract : ActivityResultContract<InteractiveActionRequiredException, Unit>() {\n\t\toverride fun createIntent(\n\t\t\tcontext: Context,\n\t\t\tinput: InteractiveActionRequiredException\n\t\t): Intent = AppRouter.browserIntent(\n\t\t\tcontext = context,\n\t\t\turl = input.url,\n\t\t\tsource = input.source,\n\t\t\ttitle = null,\n\t\t)\n\n\t\toverride fun parseResult(resultCode: Int, intent: Intent?): Unit = Unit\n\t}\n\n\tcompanion object {\n\n\t\tconst val TAG = \"BrowserActivity\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserCallback.kt",
    "content": "package org.koitharu.kotatsu.browser\n\ninterface BrowserCallback : OnHistoryChangedListener {\n\n\tfun onLoadingStateChanged(isLoading: Boolean)\n\n\tfun onTitleChanged(title: CharSequence, subtitle: CharSequence?)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/BrowserClient.kt",
    "content": "package org.koitharu.kotatsu.browser\n\nimport android.annotation.SuppressLint\nimport android.graphics.Bitmap\nimport android.os.Looper\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebResourceResponse\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.annotation.AnyThread\nimport androidx.annotation.WorkerThread\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.core.network.webview.adblock.AdBlock\nimport java.io.ByteArrayInputStream\n\nopen class BrowserClient(\n\tprivate val callback: BrowserCallback,\n\tprivate val adBlock: AdBlock?,\n) : WebViewClient() {\n\n\t/**\n\t * https://stackoverflow.com/questions/57414530/illegalstateexception-reasonphrase-cant-be-empty-with-android-webview\n\t */\n\n\toverride fun onPageFinished(webView: WebView, url: String) {\n\t\tsuper.onPageFinished(webView, url)\n\t\tcallback.onLoadingStateChanged(isLoading = false)\n\t}\n\n\toverride fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n\t\tsuper.onPageStarted(view, url, favicon)\n\t\tcallback.onLoadingStateChanged(isLoading = true)\n\t}\n\n\toverride fun onPageCommitVisible(view: WebView, url: String) {\n\t\tsuper.onPageCommitVisible(view, url)\n\t\tcallback.onTitleChanged(view.title.orEmpty(), url)\n\t}\n\n\toverride fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {\n\t\tsuper.doUpdateVisitedHistory(view, url, isReload)\n\t\tcallback.onHistoryChanged()\n\t}\n\n\t@WorkerThread\n\t@Deprecated(\"Deprecated in Java\")\n\toverride fun shouldInterceptRequest(\n\t\tview: WebView?,\n\t\turl: String?\n\t): WebResourceResponse? = if (url.isNullOrEmpty() || adBlock?.shouldLoadUrl(url, view?.getUrlSafe()) ?: true) {\n\t\tsuper.shouldInterceptRequest(view, url)\n\t} else {\n\t\temptyResponse()\n\t}\n\n\t@WorkerThread\n\toverride fun shouldInterceptRequest(\n\t\tview: WebView?,\n\t\trequest: WebResourceRequest?\n\t): WebResourceResponse? =\n\t\tif (request == null || adBlock?.shouldLoadUrl(request.url.toString(), view?.getUrlSafe()) ?: true) {\n\t\t\tsuper.shouldInterceptRequest(view, request)\n\t\t} else {\n\t\t\temptyResponse()\n\t\t}\n\n\tprivate fun emptyResponse(): WebResourceResponse =\n\t\tWebResourceResponse(\"text/plain\", \"utf-8\", ByteArrayInputStream(byteArrayOf()))\n\n\t@SuppressLint(\"WrongThread\")\n\t@AnyThread\n\tprivate fun WebView.getUrlSafe(): String? = if (Looper.myLooper() == Looper.getMainLooper()) {\n\t\turl\n\t} else {\n\t\trunBlocking(Dispatchers.Main.immediate) {\n\t\t\turl\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/OnHistoryChangedListener.kt",
    "content": "package org.koitharu.kotatsu.browser\n\nfun interface OnHistoryChangedListener {\n\n\tfun onHistoryChanged()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/ProgressChromeClient.kt",
    "content": "package org.koitharu.kotatsu.browser\n\nimport android.webkit.WebChromeClient\nimport android.webkit.WebView\nimport androidx.core.view.isVisible\nimport com.google.android.material.progressindicator.BaseProgressIndicator\n\nprivate const val PROGRESS_MAX = 100\n\nclass ProgressChromeClient(\n\tprivate val progressIndicator: BaseProgressIndicator<*>,\n) : WebChromeClient() {\n\n\tinit {\n\t\tprogressIndicator.max = PROGRESS_MAX\n\t}\n\n\toverride fun onProgressChanged(view: WebView?, newProgress: Int) {\n\t\tsuper.onProgressChanged(view, newProgress)\n\t\tif (!progressIndicator.isVisible) {\n\t\t\treturn\n\t\t}\n\t\tif (newProgress in 1 until PROGRESS_MAX) {\n\t\t\tprogressIndicator.isIndeterminate = false\n\t\t\tprogressIndicator.setProgressCompat(newProgress.coerceAtMost(PROGRESS_MAX), true)\n\t\t} else {\n\t\t\tprogressIndicator.isIndeterminate = true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/WebViewBackPressedCallback.kt",
    "content": "package org.koitharu.kotatsu.browser\n\nimport android.webkit.WebView\nimport androidx.activity.OnBackPressedCallback\n\nclass WebViewBackPressedCallback(\n\tprivate val webView: WebView,\n) : OnBackPressedCallback(false), OnHistoryChangedListener {\n\n\tinit {\n\t\tonHistoryChanged()\n\t}\n\n\toverride fun handleOnBackPressed() {\n\t\twebView.goBack()\n\t}\n\n\toverride fun onHistoryChanged() {\n\t\tisEnabled = webView.canGoBack()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt",
    "content": "package org.koitharu.kotatsu.browser.cloudflare\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.core.view.isInvisible\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.yield\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.browser.BaseBrowserActivity\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass CloudFlareActivity : BaseBrowserActivity(), CloudFlareCallback {\n\n\tprivate var pendingResult = RESULT_CANCELED\n\n\t@Inject\n\tlateinit var cookieJar: MutableCookieJar\n\n\t@Inject\n\tlateinit var captchaHandler: CaptchaHandler\n\n\tprivate lateinit var cfClient: CloudFlareClient\n\n\toverride fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) {\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tval url = intent?.dataString\n\t\tif (url.isNullOrEmpty()) {\n\t\t\tfinishAfterTransition()\n\t\t\treturn\n\t\t}\n\t\tcfClient = CloudFlareClient(cookieJar, this, adBlock, url)\n\t\tviewBinding.webView.webViewClient = cfClient\n\t\tlifecycleScope.launch {\n\t\t\ttry {\n\t\t\t\tproxyProvider.applyWebViewConfig()\n\t\t\t} catch (e: Exception) {\n\t\t\t\tSnackbar.make(viewBinding.webView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()\n\t\t\t}\n\t\t\tif (savedInstanceState == null) {\n\t\t\t\tonTitleChanged(getString(R.string.loading_), url)\n\t\t\t\tviewBinding.webView.loadUrl(url)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCreateOptionsMenu(menu: Menu?): Boolean {\n\t\tmenuInflater.inflate(R.menu.opt_captcha, menu)\n\t\treturn super.onCreateOptionsMenu(menu)\n\t}\n\n\toverride fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {\n\t\tandroid.R.id.home -> {\n\t\t\tviewBinding.webView.stopLoading()\n\t\t\tfinishAfterTransition()\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_retry -> {\n\t\t\trestartCheck()\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onOptionsItemSelected(item)\n\t}\n\n\toverride fun finish() {\n\t\tsetResult(pendingResult)\n\t\tsuper.finish()\n\t}\n\n\toverride fun onLoadingStateChanged(isLoading: Boolean) = Unit\n\n\toverride fun onPageLoaded() {\n\t\tviewBinding.progressBar.isInvisible = true\n\t}\n\n\toverride fun onLoopDetected() {\n\t\trestartCheck()\n\t}\n\n\toverride fun onCheckPassed() {\n\t\tpendingResult = RESULT_OK\n\t\tlifecycleScope.launch {\n\t\t\tval source = intent?.getStringExtra(AppRouter.KEY_SOURCE)\n\t\t\tif (source != null) {\n\t\t\t\trunCatchingCancellable {\n\t\t\t\t\tcaptchaHandler.discard(MangaSource(source))\n\t\t\t\t}.onFailure {\n\t\t\t\t\tit.printStackTraceDebug()\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinishAfterTransition()\n\t\t}\n\t}\n\n\toverride fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {\n\t\tsetTitle(title)\n\t\tsupportActionBar?.subtitle = subtitle?.toString()?.toHttpUrlOrNull()?.host.ifNullOrEmpty { subtitle }\n\t}\n\n\tprivate fun restartCheck() {\n\t\tlifecycleScope.launch {\n\t\t\tviewBinding.webView.stopLoading()\n\t\t\tyield()\n\t\t\tcfClient.reset()\n\t\t\tval targetUrl = intent?.dataString?.toHttpUrlOrNull()\n\t\t\tif (targetUrl != null) {\n\t\t\t\tclearCfCookies(targetUrl)\n\t\t\t\tviewBinding.webView.loadUrl(targetUrl.toString())\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun clearCfCookies(url: HttpUrl) = runInterruptible(Dispatchers.Default) {\n\t\tcookieJar.removeCookies(url) { cookie ->\n\t\t\tCloudFlareHelper.isCloudFlareCookie(cookie.name)\n\t\t}\n\t}\n\n\tclass Contract : ActivityResultContract<CloudFlareProtectedException, Boolean>() {\n\t\toverride fun createIntent(context: Context, input: CloudFlareProtectedException): Intent {\n\t\t\treturn AppRouter.cloudFlareResolveIntent(context, input)\n\t\t}\n\n\t\toverride fun parseResult(resultCode: Int, intent: Intent?): Boolean {\n\t\t\treturn resultCode == RESULT_OK\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tconst val TAG = \"CloudFlareActivity\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareCallback.kt",
    "content": "package org.koitharu.kotatsu.browser.cloudflare\n\nimport org.koitharu.kotatsu.browser.BrowserCallback\n\ninterface CloudFlareCallback : BrowserCallback {\n\n\toverride fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) = Unit\n\n\tfun onPageLoaded()\n\n\tfun onCheckPassed()\n\n\tfun onLoopDetected()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareClient.kt",
    "content": "package org.koitharu.kotatsu.browser.cloudflare\n\nimport android.graphics.Bitmap\nimport android.webkit.WebView\nimport org.koitharu.kotatsu.browser.BrowserClient\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.core.network.webview.adblock.AdBlock\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\n\nprivate const val LOOP_COUNTER = 3\n\nclass CloudFlareClient(\n\tprivate val cookieJar: MutableCookieJar,\n\tprivate val callback: CloudFlareCallback,\n\tadBlock: AdBlock,\n\tprivate val targetUrl: String,\n) : BrowserClient(callback, adBlock) {\n\n\tprivate val oldClearance = getClearance()\n\tprivate var counter = 0\n\n\toverride fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n\t\tsuper.onPageStarted(view, url, favicon)\n\t\tcheckClearance()\n\t}\n\n\toverride fun onPageCommitVisible(view: WebView, url: String) {\n\t\tsuper.onPageCommitVisible(view, url)\n\t\tcallback.onPageLoaded()\n\t}\n\n\toverride fun onPageFinished(webView: WebView, url: String) {\n\t\tsuper.onPageFinished(webView, url)\n\t\tcallback.onPageLoaded()\n\t}\n\n\tfun reset() {\n\t\tcounter = 0\n\t}\n\n\tprivate fun checkClearance() {\n\t\tval clearance = getClearance()\n\t\tif (clearance != null && clearance != oldClearance) {\n\t\t\tcallback.onCheckPassed()\n\t\t} else {\n\t\t\tcounter++\n\t\t\tif (counter >= LOOP_COUNTER) {\n\t\t\t\treset()\n\t\t\t\tcallback.onLoopDetected()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun getClearance() = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt",
    "content": "package org.koitharu.kotatsu.core\n\nimport android.app.Application\nimport android.content.Context\nimport android.os.Build\nimport android.provider.SearchRecentSuggestions\nimport android.text.Html\nimport androidx.collection.arraySetOf\nimport androidx.core.content.ContextCompat\nimport androidx.room.InvalidationTracker\nimport androidx.work.WorkManager\nimport coil3.ImageLoader\nimport coil3.disk.DiskCache\nimport coil3.disk.directory\nimport coil3.gif.AnimatedImageDecoder\nimport coil3.gif.GifDecoder\nimport coil3.network.okhttp.OkHttpNetworkFetcherFactory\nimport coil3.request.allowRgb565\nimport coil3.svg.SvgDecoder\nimport coil3.util.DebugLogger\nimport dagger.Binds\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport dagger.multibindings.ElementsIntoSet\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport okhttp3.OkHttpClient\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.backups.domain.BackupObserver\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler\nimport org.koitharu.kotatsu.core.image.AvifImageDecoder\nimport org.koitharu.kotatsu.core.image.CbzFetcher\nimport org.koitharu.kotatsu.core.image.MangaSourceHeaderInterceptor\nimport org.koitharu.kotatsu.core.network.MangaHttpClient\nimport org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl\nimport org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.image.CoilImageGetter\nimport org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle\nimport org.koitharu.kotatsu.core.util.AcraScreenLogger\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.ext.connectivityManager\nimport org.koitharu.kotatsu.core.util.ext.isLowRamDevice\nimport org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher\nimport org.koitharu.kotatsu.details.ui.pager.pages.MangaPageKeyer\nimport org.koitharu.kotatsu.local.data.CacheDir\nimport org.koitharu.kotatsu.local.data.FaviconCache\nimport org.koitharu.kotatsu.local.data.LocalStorageCache\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.data.PageCache\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor\nimport org.koitharu.kotatsu.main.ui.protect.AppProtectHelper\nimport org.koitharu.kotatsu.main.ui.protect.ScreenshotPolicyHelper\nimport org.koitharu.kotatsu.parsers.MangaLoaderContext\nimport org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider\nimport org.koitharu.kotatsu.sync.domain.SyncController\nimport org.koitharu.kotatsu.widget.WidgetUpdater\nimport javax.inject.Provider\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\ninterface AppModule {\n\n\t@Binds\n\tfun bindMangaLoaderContext(mangaLoaderContextImpl: MangaLoaderContextImpl): MangaLoaderContext\n\n\t@Binds\n\tfun bindImageGetter(coilImageGetter: CoilImageGetter): Html.ImageGetter\n\n\tcompanion object {\n\n\t\t@Provides\n\t\t@LocalizedAppContext\n\t\tfun provideLocalizedContext(\n\t\t\t@ApplicationContext context: Context,\n\t\t): Context = ContextCompat.getContextForLanguage(context)\n\n\t\t@Provides\n\t\t@Singleton\n\t\tfun provideNetworkState(\n\t\t\t@ApplicationContext context: Context,\n\t\t\tsettings: AppSettings,\n\t\t) = NetworkState(context.connectivityManager, settings)\n\n\t\t@Provides\n\t\t@Singleton\n\t\tfun provideMangaDatabase(\n\t\t\t@ApplicationContext context: Context,\n\t\t): MangaDatabase = MangaDatabase(context)\n\n\t\t@Provides\n\t\t@Singleton\n\t\tfun provideCoil(\n\t\t\t@LocalizedAppContext context: Context,\n\t\t\t@MangaHttpClient okHttpClientProvider: Provider<OkHttpClient>,\n\t\t\tfaviconFetcherFactory: FaviconFetcher.Factory,\n\t\t\timageProxyInterceptor: ImageProxyInterceptor,\n\t\t\tpageFetcherFactory: MangaPageFetcher.Factory,\n\t\t\tcoverRestoreInterceptor: CoverRestoreInterceptor,\n\t\t\tnetworkStateProvider: Provider<NetworkState>,\n\t\t\tcaptchaHandler: CaptchaHandler,\n\t\t): ImageLoader {\n\t\t\tval diskCacheFactory = {\n\t\t\t\tval rootDir = context.externalCacheDir ?: context.cacheDir\n\t\t\t\tDiskCache.Builder()\n\t\t\t\t\t.directory(rootDir.resolve(CacheDir.THUMBS.dir))\n\t\t\t\t\t.build()\n\t\t\t}\n\t\t\tval okHttpClientLazy = lazy {\n\t\t\t\tokHttpClientProvider.get().newBuilder().cache(null).build()\n\t\t\t}\n\t\t\treturn ImageLoader.Builder(context)\n\t\t\t\t.interceptorCoroutineContext(Dispatchers.Default)\n\t\t\t\t.diskCache(diskCacheFactory)\n\t\t\t\t.logger(if (BuildConfig.DEBUG) DebugLogger() else null)\n\t\t\t\t.allowRgb565(context.isLowRamDevice())\n\t\t\t\t.eventListener(captchaHandler)\n\t\t\t\t.components {\n\t\t\t\t\tadd(\n\t\t\t\t\t\tOkHttpNetworkFetcherFactory(\n\t\t\t\t\t\t\tcallFactory = okHttpClientLazy::value,\n\t\t\t\t\t\t\tconnectivityChecker = { networkStateProvider.get() },\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n\t\t\t\t\t\tadd(AnimatedImageDecoder.Factory())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tadd(GifDecoder.Factory())\n\t\t\t\t\t}\n\t\t\t\t\tadd(SvgDecoder.Factory())\n\t\t\t\t\tadd(CbzFetcher.Factory())\n\t\t\t\t\tadd(AvifImageDecoder.Factory())\n\t\t\t\t\tadd(faviconFetcherFactory)\n\t\t\t\t\tadd(MangaPageKeyer())\n\t\t\t\t\tadd(pageFetcherFactory)\n\t\t\t\t\tadd(imageProxyInterceptor)\n\t\t\t\t\tadd(coverRestoreInterceptor)\n\t\t\t\t\tadd(MangaSourceHeaderInterceptor())\n\t\t\t\t}.build()\n\t\t}\n\n\t\t@Provides\n\t\tfun provideSearchSuggestions(\n\t\t\t@ApplicationContext context: Context,\n\t\t): SearchRecentSuggestions = MangaSuggestionsProvider.createSuggestions(context)\n\n\t\t@Provides\n\t\t@ElementsIntoSet\n\t\tfun provideDatabaseObservers(\n\t\t\twidgetUpdater: WidgetUpdater,\n\t\t\tappShortcutManager: AppShortcutManager,\n\t\t\tbackupObserver: BackupObserver,\n\t\t\tsyncController: SyncController,\n\t\t): Set<@JvmSuppressWildcards InvalidationTracker.Observer> = arraySetOf(\n\t\t\twidgetUpdater,\n\t\t\tappShortcutManager,\n\t\t\tbackupObserver,\n\t\t\tsyncController,\n\t\t)\n\n\t\t@Provides\n\t\t@ElementsIntoSet\n\t\tfun provideActivityLifecycleCallbacks(\n\t\t\tappProtectHelper: AppProtectHelper,\n\t\t\tactivityRecreationHandle: ActivityRecreationHandle,\n\t\t\tacraScreenLogger: AcraScreenLogger,\n\t\t\tscreenshotPolicyHelper: ScreenshotPolicyHelper,\n\t\t): Set<@JvmSuppressWildcards Application.ActivityLifecycleCallbacks> = arraySetOf(\n\t\t\tappProtectHelper,\n\t\t\tactivityRecreationHandle,\n\t\t\tacraScreenLogger,\n\t\t\tscreenshotPolicyHelper,\n\t\t)\n\n\t\t@Provides\n\t\t@Singleton\n\t\t@LocalStorageChanges\n\t\tfun provideMutableLocalStorageChangesFlow(): MutableSharedFlow<LocalManga?> = MutableSharedFlow()\n\n\t\t@Provides\n\t\t@LocalStorageChanges\n\t\tfun provideLocalStorageChangesFlow(\n\t\t\t@LocalStorageChanges flow: MutableSharedFlow<LocalManga?>,\n\t\t): SharedFlow<LocalManga?> = flow.asSharedFlow()\n\n\t\t@Provides\n\t\tfun provideWorkManager(\n\t\t\t@ApplicationContext context: Context,\n\t\t): WorkManager = WorkManager.getInstance(context)\n\n\t\t@Provides\n\t\t@Singleton\n\t\t@PageCache\n\t\tfun providePageCache(\n\t\t\t@ApplicationContext context: Context,\n\t\t) = LocalStorageCache(\n\t\t\tcontext = context,\n\t\t\tdir = CacheDir.PAGES,\n\t\t\tdefaultSize = FileSize.MEGABYTES.convert(200, FileSize.BYTES),\n\t\t\tminSize = FileSize.MEGABYTES.convert(20, FileSize.BYTES),\n\t\t)\n\n\t\t@Provides\n\t\t@Singleton\n\t\t@FaviconCache\n\t\tfun provideFaviconCache(\n\t\t\t@ApplicationContext context: Context,\n\t\t) = LocalStorageCache(\n\t\t\tcontext = context,\n\t\t\tdir = CacheDir.FAVICONS,\n\t\t\tdefaultSize = FileSize.MEGABYTES.convert(8, FileSize.BYTES),\n\t\t\tminSize = FileSize.MEGABYTES.convert(2, FileSize.BYTES),\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/BaseApp.kt",
    "content": "package org.koitharu.kotatsu.core\n\nimport android.app.Application\nimport android.content.Context\nimport android.os.Build\nimport androidx.annotation.WorkerThread\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.hilt.work.HiltWorkerFactory\nimport androidx.room.InvalidationTracker\nimport androidx.work.Configuration\nimport dagger.hilt.android.HiltAndroidApp\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.launch\nimport okhttp3.internal.platform.PlatformRegistry\nimport org.acra.ACRA\nimport org.acra.ReportField\nimport org.acra.config.dialog\nimport org.acra.config.httpSender\nimport org.acra.data.StringFormat\nimport org.acra.ktx.initAcra\nimport org.acra.sender.HttpSender\nimport org.conscrypt.Conscrypt\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.os.AppValidator\nimport org.koitharu.kotatsu.core.os.RomCompat\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndex\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull\nimport org.koitharu.kotatsu.settings.work.WorkScheduleManager\nimport java.security.Security\nimport javax.inject.Inject\nimport javax.inject.Provider\n\n@HiltAndroidApp\nopen class BaseApp : Application(), Configuration.Provider {\n\n\t@Inject\n\tlateinit var databaseObserversProvider: Provider<Set<@JvmSuppressWildcards InvalidationTracker.Observer>>\n\n\t@Inject\n\tlateinit var activityLifecycleCallbacks: Set<@JvmSuppressWildcards ActivityLifecycleCallbacks>\n\n\t@Inject\n\tlateinit var database: Provider<MangaDatabase>\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\t@Inject\n\tlateinit var workerFactory: HiltWorkerFactory\n\n\t@Inject\n\tlateinit var appValidator: AppValidator\n\n\t@Inject\n\tlateinit var workScheduleManager: WorkScheduleManager\n\n\t@Inject\n\tlateinit var localMangaIndexProvider: Provider<LocalMangaIndex>\n\n\t@Inject\n\t@LocalStorageChanges\n\tlateinit var localStorageChanges: MutableSharedFlow<LocalManga?>\n\n\toverride val workManagerConfiguration: Configuration\n\t\tget() = Configuration.Builder()\n\t\t\t.setWorkerFactory(workerFactory)\n\t\t\t.build()\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tPlatformRegistry.applicationContext = this // TODO replace with OkHttp.initialize\n\t\tif (ACRA.isACRASenderServiceProcess()) {\n\t\t\treturn\n\t\t}\n\t\tAppCompatDelegate.setDefaultNightMode(settings.theme)\n\t\t// TLS 1.3 support for Android < 10\n\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {\n\t\t\tSecurity.insertProviderAt(Conscrypt.newProvider(), 1)\n\t\t}\n\t\tsetupActivityLifecycleCallbacks()\n\t\tprocessLifecycleScope.launch {\n\t\t\tACRA.errorReporter.putCustomData(\"isOriginalApp\", appValidator.isOriginalApp.getOrNull().toString())\n\t\t\tACRA.errorReporter.putCustomData(\"isMiui\", RomCompat.isMiui.getOrNull().toString())\n\t\t}\n\t\tprocessLifecycleScope.launch(Dispatchers.Default) {\n\t\t\tsetupDatabaseObservers()\n\t\t\tlocalStorageChanges.collect(localMangaIndexProvider.get())\n\t\t}\n\t\tworkScheduleManager.init()\n\t}\n\n\toverride fun attachBaseContext(base: Context) {\n\t\tsuper.attachBaseContext(base)\n\t\tif (ACRA.isACRASenderServiceProcess()) {\n\t\t\treturn\n\t\t}\n\t\tinitAcra {\n\t\t\tbuildConfigClass = BuildConfig::class.java\n\t\t\treportFormat = StringFormat.JSON\n\t\t\thttpSender {\n\t\t\t\turi = getString(R.string.url_error_report)\n\t\t\t\tbasicAuthLogin = getString(R.string.acra_login)\n\t\t\t\tbasicAuthPassword = getString(R.string.acra_password)\n\t\t\t\thttpMethod = HttpSender.Method.POST\n\t\t\t}\n\t\t\treportContent = listOf(\n\t\t\t\tReportField.PACKAGE_NAME,\n\t\t\t\tReportField.INSTALLATION_ID,\n\t\t\t\tReportField.APP_VERSION_CODE,\n\t\t\t\tReportField.APP_VERSION_NAME,\n\t\t\t\tReportField.ANDROID_VERSION,\n\t\t\t\tReportField.PHONE_MODEL,\n\t\t\t\tReportField.STACK_TRACE,\n\t\t\t\tReportField.CRASH_CONFIGURATION,\n\t\t\t\tReportField.CUSTOM_DATA,\n\t\t\t)\n\n\t\t\tdialog {\n\t\t\t\ttext = getString(R.string.crash_text)\n\t\t\t\ttitle = getString(R.string.error_occurred)\n\t\t\t\tpositiveButtonText = getString(R.string.send)\n\t\t\t\tresIcon = R.drawable.ic_alert_outline\n\t\t\t\tresTheme = android.R.style.Theme_Material_Light_Dialog_Alert\n\t\t\t}\n\t\t}\n\t}\n\n\t@WorkerThread\n\tprivate fun setupDatabaseObservers() {\n\t\tval tracker = database.get().invalidationTracker\n\t\tdatabaseObserversProvider.get().forEach {\n\t\t\ttracker.addObserver(it)\n\t\t}\n\t}\n\n\tprivate fun setupActivityLifecycleCallbacks() {\n\t\tactivityLifecycleCallbacks.forEach {\n\t\t\tregisterActivityLifecycleCallbacks(it)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ErrorReporterReceiver.kt",
    "content": "package org.koitharu.kotatsu.core\n\nimport android.app.PendingIntent\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.report\n\nclass ErrorReporterReceiver : BroadcastReceiver() {\n\n\toverride fun onReceive(context: Context?, intent: Intent?) {\n\t\tval e = intent?.getSerializableExtraCompat<Throwable>(AppRouter.KEY_ERROR) ?: return\n\t\tval notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 0)\n\t\tif (notificationId != 0 && context != null) {\n\t\t\tval notificationTag = intent.getStringExtra(EXTRA_NOTIFICATION_TAG)\n\t\t\tNotificationManagerCompat.from(context).cancel(notificationTag, notificationId)\n\t\t}\n\t\te.report()\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val ACTION_REPORT = \"${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR\"\n\t\tprivate const val EXTRA_NOTIFICATION_ID = \"notify.id\"\n\t\tprivate const val EXTRA_NOTIFICATION_TAG = \"notify.tag\"\n\n\t\tfun getPendingIntent(context: Context, e: Throwable): PendingIntent? = getPendingIntentInternal(\n\t\t\tcontext = context,\n\t\t\te = e,\n\t\t\tnotificationId = 0,\n\t\t\tnotificationTag = null,\n\t\t)\n\n\t\tfun getNotificationAction(\n\t\t\tcontext: Context,\n\t\t\te: Throwable,\n\t\t\tnotificationId: Int,\n\t\t\tnotificationTag: String?,\n\t\t): NotificationCompat.Action? {\n\t\t\tval intent = getPendingIntentInternal(\n\t\t\t\tcontext = context,\n\t\t\t\te = e,\n\t\t\t\tnotificationId = notificationId,\n\t\t\t\tnotificationTag = notificationTag,\n\t\t\t) ?: return null\n\t\t\treturn NotificationCompat.Action(\n\t\t\t\tR.drawable.ic_alert_outline,\n\t\t\t\tcontext.getString(R.string.report),\n\t\t\t\tintent,\n\t\t\t)\n\t\t}\n\n\t\tprivate fun getPendingIntentInternal(\n\t\t\tcontext: Context,\n\t\t\te: Throwable,\n\t\t\tnotificationId: Int,\n\t\t\tnotificationTag: String?,\n\t\t): PendingIntent? = runCatching {\n\t\t\tval intent = Intent(context, ErrorReporterReceiver::class.java)\n\t\t\tintent.setAction(ACTION_REPORT)\n\t\t\tintent.setData(\"err://${e.hashCode()}\".toUri())\n\t\t\tintent.putExtra(AppRouter.KEY_ERROR, e)\n\t\t\tintent.putExtra(EXTRA_NOTIFICATION_ID, notificationId)\n\t\t\tintent.putExtra(EXTRA_NOTIFICATION_TAG, notificationTag)\n\t\t\tPendingIntentCompat.getBroadcast(context, 0, intent, 0, false)\n\t\t}.onFailure { e ->\n\t\t\t// probably cannot write exception as serializable\n\t\t\te.printStackTraceDebug()\n\t\t}.getOrNull()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/LocalizedAppContext.kt",
    "content": "package org.koitharu.kotatsu.core\n\nimport javax.inject.Qualifier\n\n@Qualifier\n@Target(\n\tAnnotationTarget.FUNCTION,\n\tAnnotationTarget.PROPERTY_GETTER,\n\tAnnotationTarget.PROPERTY_SETTER,\n\tAnnotationTarget.VALUE_PARAMETER,\n\tAnnotationTarget.FIELD,\n)\nannotation class LocalizedAppContext\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringLruCache.kt",
    "content": "package org.koitharu.kotatsu.core.cache\n\nimport org.koitharu.kotatsu.core.util.SynchronizedSieveCache\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport java.util.concurrent.TimeUnit\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache.Key as CacheKey\n\nclass ExpiringLruCache<T>(\n\tval maxSize: Int,\n\tprivate val lifetime: Long,\n\tprivate val timeUnit: TimeUnit,\n) {\n\n\tprivate val cache = SynchronizedSieveCache<CacheKey, ExpiringValue<T>>(maxSize)\n\n\toperator fun get(key: CacheKey): T? {\n\t\tval value = cache[key] ?: return null\n\t\tif (value.isExpired) {\n\t\t\tcache.remove(key)\n\t\t}\n\t\treturn value.get()\n\t}\n\n\toperator fun set(key: CacheKey, value: T) {\n\t\tval value = ExpiringValue(value, lifetime, timeUnit)\n\t\tcache.put(key, value)\n\t}\n\n\tfun clear() {\n\t\tcache.evictAll()\n\t}\n\n\tfun trimToSize(size: Int) {\n\t\tcache.trimToSize(size)\n\t}\n\n\tfun remove(key: CacheKey) {\n\t\tcache.remove(key)\n\t}\n\n\tfun removeAll(source: MangaSource) {\n\t\tcache.removeIf { key, _ -> key.source == source }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/cache/ExpiringValue.kt",
    "content": "package org.koitharu.kotatsu.core.cache\n\nimport android.os.SystemClock\nimport java.util.concurrent.TimeUnit\n\nclass ExpiringValue<T>(\n\tprivate val value: T,\n\tlifetime: Long,\n\ttimeUnit: TimeUnit,\n) {\n\n\tprivate val expiresAt = SystemClock.elapsedRealtime() + timeUnit.toMillis(lifetime)\n\n\tval isExpired: Boolean\n\t\tget() = SystemClock.elapsedRealtime() >= expiresAt\n\n\tfun get(): T? = if (isExpired) null else value\n\n\toverride fun equals(other: Any?): Boolean {\n\t\tif (this === other) return true\n\t\tif (javaClass != other?.javaClass) return false\n\n\t\tother as ExpiringValue<*>\n\n\t\tif (value != other.value) return false\n\t\treturn expiresAt == other.expiresAt\n\t}\n\n\toverride fun hashCode(): Int {\n\t\tvar result = value?.hashCode() ?: 0\n\t\tresult = 31 * result + expiresAt.hashCode()\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/cache/MemoryContentCache.kt",
    "content": "package org.koitharu.kotatsu.core.cache\n\nimport android.app.Application\nimport android.content.ComponentCallbacks2\nimport android.content.res.Configuration\nimport org.koitharu.kotatsu.core.util.ext.isLowRamDevice\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MemoryContentCache @Inject constructor(application: Application) : ComponentCallbacks2 {\n\n\tprivate val isLowRam = application.isLowRamDevice()\n\n\tprivate val detailsCache = ExpiringLruCache<SafeDeferred<Manga>>(if (isLowRam) 1 else 4, 5, TimeUnit.MINUTES)\n\tprivate val pagesCache =\n\t\tExpiringLruCache<SafeDeferred<List<MangaPage>>>(if (isLowRam) 1 else 4, 10, TimeUnit.MINUTES)\n\tprivate val relatedMangaCache =\n\t\tExpiringLruCache<SafeDeferred<List<Manga>>>(if (isLowRam) 1 else 3, 10, TimeUnit.MINUTES)\n\n\tinit {\n\t\tapplication.registerComponentCallbacks(this)\n\t}\n\n\tsuspend fun getDetails(source: MangaSource, url: String): Manga? {\n\t\treturn detailsCache[Key(source, url)]?.awaitOrNull()\n\t}\n\n\tfun putDetails(source: MangaSource, url: String, details: SafeDeferred<Manga>) {\n\t\tdetailsCache[Key(source, url)] = details\n\t}\n\n\tsuspend fun getPages(source: MangaSource, url: String): List<MangaPage>? {\n\t\treturn pagesCache[Key(source, url)]?.awaitOrNull()\n\t}\n\n\tfun putPages(source: MangaSource, url: String, pages: SafeDeferred<List<MangaPage>>) {\n\t\tpagesCache[Key(source, url)] = pages\n\t}\n\n\tsuspend fun getRelatedManga(source: MangaSource, url: String): List<Manga>? {\n\t\treturn relatedMangaCache[Key(source, url)]?.awaitOrNull()\n\t}\n\n\tfun putRelatedManga(source: MangaSource, url: String, related: SafeDeferred<List<Manga>>) {\n\t\trelatedMangaCache[Key(source, url)] = related\n\t}\n\n\tfun clear(source: MangaSource) {\n\t\tclearCache(detailsCache, source)\n\t\tclearCache(pagesCache, source)\n\t\tclearCache(relatedMangaCache, source)\n\t}\n\n\toverride fun onConfigurationChanged(newConfig: Configuration) = Unit\n\n\toverride fun onLowMemory() = Unit\n\n\toverride fun onTrimMemory(level: Int) {\n\t\ttrimCache(detailsCache, level)\n\t\ttrimCache(pagesCache, level)\n\t\ttrimCache(relatedMangaCache, level)\n\t}\n\n\tprivate fun trimCache(cache: ExpiringLruCache<*>, level: Int) {\n\t\twhen (level) {\n\t\t\tComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL,\n\t\t\tComponentCallbacks2.TRIM_MEMORY_COMPLETE,\n\t\t\tComponentCallbacks2.TRIM_MEMORY_MODERATE -> cache.clear()\n\n\t\t\tComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,\n\t\t\tComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,\n\t\t\tComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> cache.trimToSize(1)\n\n\t\t\telse -> cache.trimToSize(cache.maxSize / 2)\n\t\t}\n\t}\n\n\tprivate fun clearCache(cache: ExpiringLruCache<*>, source: MangaSource) {\n\t\tcache.removeAll(source)\n\t}\n\n\tdata class Key(\n\t\tval source: MangaSource,\n\t\tval url: String,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/cache/SafeDeferred.kt",
    "content": "package org.koitharu.kotatsu.core.cache\n\nimport kotlinx.coroutines.Deferred\n\nclass SafeDeferred<T>(\n\tprivate val delegate: Deferred<Result<T>>,\n) {\n\n\tsuspend fun await(): T {\n\t\treturn delegate.await().getOrThrow()\n\t}\n\n\tsuspend fun awaitOrNull(): T? {\n\t\treturn delegate.await().getOrNull()\n\t}\n\n\tfun cancel() {\n\t\tdelegate.cancel()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/DatabasePrePopulateCallback.kt",
    "content": "package org.koitharu.kotatsu.core.db\n\nimport android.content.res.Resources\nimport androidx.room.RoomDatabase\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.model.SortOrder\n\nclass DatabasePrePopulateCallback(private val resources: Resources) : RoomDatabase.Callback() {\n\n\toverride fun onCreate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\n\t\t\t\"INSERT INTO favourite_categories (created_at, sort_key, title, `order`, track, show_in_lib, `deleted_at`) VALUES (?,?,?,?,?,?,?)\",\n\t\t\tarrayOf(\n\t\t\t\tSystem.currentTimeMillis(),\n\t\t\t\t1,\n\t\t\t\tresources.getString(R.string.read_later),\n\t\t\t\tSortOrder.NEWEST.name,\n\t\t\t\t1,\n\t\t\t\t1,\n\t\t\t\t0L,\n\t\t\t)\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt",
    "content": "package org.koitharu.kotatsu.core.db\n\nimport android.content.Context\nimport androidx.room.Database\nimport androidx.room.InvalidationTracker\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport androidx.room.migration.Migration\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.bookmarks.data.BookmarkEntity\nimport org.koitharu.kotatsu.bookmarks.data.BookmarksDao\nimport org.koitharu.kotatsu.core.db.dao.ChaptersDao\nimport org.koitharu.kotatsu.core.db.dao.MangaDao\nimport org.koitharu.kotatsu.core.db.dao.MangaSourcesDao\nimport org.koitharu.kotatsu.core.db.dao.PreferencesDao\nimport org.koitharu.kotatsu.core.db.dao.TagsDao\nimport org.koitharu.kotatsu.core.db.dao.TrackLogsDao\nimport org.koitharu.kotatsu.core.db.entity.ChapterEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaSourceEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaTagsEntity\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\nimport org.koitharu.kotatsu.core.db.migrations.Migration10To11\nimport org.koitharu.kotatsu.core.db.migrations.Migration11To12\nimport org.koitharu.kotatsu.core.db.migrations.Migration12To13\nimport org.koitharu.kotatsu.core.db.migrations.Migration13To14\nimport org.koitharu.kotatsu.core.db.migrations.Migration14To15\nimport org.koitharu.kotatsu.core.db.migrations.Migration15To16\nimport org.koitharu.kotatsu.core.db.migrations.Migration16To17\nimport org.koitharu.kotatsu.core.db.migrations.Migration17To18\nimport org.koitharu.kotatsu.core.db.migrations.Migration18To19\nimport org.koitharu.kotatsu.core.db.migrations.Migration19To20\nimport org.koitharu.kotatsu.core.db.migrations.Migration1To2\nimport org.koitharu.kotatsu.core.db.migrations.Migration20To21\nimport org.koitharu.kotatsu.core.db.migrations.Migration21To22\nimport org.koitharu.kotatsu.core.db.migrations.Migration22To23\nimport org.koitharu.kotatsu.core.db.migrations.Migration23To24\nimport org.koitharu.kotatsu.core.db.migrations.Migration24To23\nimport org.koitharu.kotatsu.core.db.migrations.Migration24To25\nimport org.koitharu.kotatsu.core.db.migrations.Migration25To26\nimport org.koitharu.kotatsu.core.db.migrations.Migration26To27\nimport org.koitharu.kotatsu.core.db.migrations.Migration2To3\nimport org.koitharu.kotatsu.core.db.migrations.Migration3To4\nimport org.koitharu.kotatsu.core.db.migrations.Migration4To5\nimport org.koitharu.kotatsu.core.db.migrations.Migration5To6\nimport org.koitharu.kotatsu.core.db.migrations.Migration6To7\nimport org.koitharu.kotatsu.core.db.migrations.Migration7To8\nimport org.koitharu.kotatsu.core.db.migrations.Migration8To9\nimport org.koitharu.kotatsu.core.db.migrations.Migration9To10\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.favourites.data.FavouriteCategoriesDao\nimport org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity\nimport org.koitharu.kotatsu.favourites.data.FavouriteEntity\nimport org.koitharu.kotatsu.favourites.data.FavouritesDao\nimport org.koitharu.kotatsu.history.data.HistoryDao\nimport org.koitharu.kotatsu.history.data.HistoryEntity\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndexDao\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndexEntity\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingDao\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity\nimport org.koitharu.kotatsu.stats.data.StatsDao\nimport org.koitharu.kotatsu.stats.data.StatsEntity\nimport org.koitharu.kotatsu.suggestions.data.SuggestionDao\nimport org.koitharu.kotatsu.suggestions.data.SuggestionEntity\nimport org.koitharu.kotatsu.tracker.data.TrackEntity\nimport org.koitharu.kotatsu.tracker.data.TrackLogEntity\nimport org.koitharu.kotatsu.tracker.data.TracksDao\n\nconst val DATABASE_VERSION = 27\n\n@Database(\n\tentities = [\n\t\tMangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class, ChapterEntity::class,\n\t\tFavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class, TrackEntity::class,\n\t\tTrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class, ScrobblingEntity::class,\n\t\tMangaSourceEntity::class, StatsEntity::class, LocalMangaIndexEntity::class,\n\t],\n\tversion = DATABASE_VERSION,\n)\nabstract class MangaDatabase : RoomDatabase() {\n\n\tabstract fun getHistoryDao(): HistoryDao\n\n\tabstract fun getTagsDao(): TagsDao\n\n\tabstract fun getMangaDao(): MangaDao\n\n\tabstract fun getFavouritesDao(): FavouritesDao\n\n\tabstract fun getPreferencesDao(): PreferencesDao\n\n\tabstract fun getFavouriteCategoriesDao(): FavouriteCategoriesDao\n\n\tabstract fun getTracksDao(): TracksDao\n\n\tabstract fun getTrackLogsDao(): TrackLogsDao\n\n\tabstract fun getSuggestionDao(): SuggestionDao\n\n\tabstract fun getBookmarksDao(): BookmarksDao\n\n\tabstract fun getScrobblingDao(): ScrobblingDao\n\n\tabstract fun getSourcesDao(): MangaSourcesDao\n\n\tabstract fun getStatsDao(): StatsDao\n\n\tabstract fun getLocalMangaIndexDao(): LocalMangaIndexDao\n\n\tabstract fun getChaptersDao(): ChaptersDao\n}\n\nfun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(\n\tMigration1To2(),\n\tMigration2To3(),\n\tMigration3To4(),\n\tMigration4To5(),\n\tMigration5To6(),\n\tMigration6To7(),\n\tMigration7To8(),\n\tMigration8To9(),\n\tMigration9To10(),\n\tMigration10To11(),\n\tMigration11To12(),\n\tMigration12To13(),\n\tMigration13To14(),\n\tMigration14To15(),\n\tMigration15To16(),\n\tMigration16To17(context),\n\tMigration17To18(),\n\tMigration18To19(),\n\tMigration19To20(),\n\tMigration20To21(),\n\tMigration21To22(),\n\tMigration22To23(),\n\tMigration23To24(),\n\tMigration24To23(),\n\tMigration24To25(),\n\tMigration25To26(),\n\tMigration26To27(),\n)\n\nfun MangaDatabase(context: Context): MangaDatabase = Room\n\t.databaseBuilder(context, MangaDatabase::class.java, \"kotatsu-db\")\n\t.addMigrations(*getDatabaseMigrations(context))\n\t.addCallback(DatabasePrePopulateCallback(context.resources))\n\t.build()\n\nfun InvalidationTracker.removeObserverAsync(observer: InvalidationTracker.Observer) {\n\tval scope = processLifecycleScope\n\tif (scope.isActive) {\n\t\tprocessLifecycleScope.launch(Dispatchers.Default, CoroutineStart.ATOMIC) {\n\t\t\tremoveObserver(observer)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaQueryBuilder.kt",
    "content": "package org.koitharu.kotatsu.core.db\n\nimport androidx.sqlite.db.SimpleSQLiteQuery\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport java.util.LinkedList\n\nclass MangaQueryBuilder(\n\tprivate val table: String,\n\tprivate val conditionCallback: ConditionCallback\n) {\n\n\tprivate var filterOptions: Collection<ListFilterOption> = emptyList()\n\tprivate var whereConditions = LinkedList<String>()\n\tprivate var orderBy: String? = null\n\tprivate var groupBy: String? = null\n\tprivate var extraJoins: String? = null\n\tprivate var limit: Int = 0\n\n\tfun filters(options: Collection<ListFilterOption>) = apply {\n\t\tfilterOptions = options\n\t}\n\n\tfun where(condition: String) = apply {\n\t\twhereConditions.add(condition)\n\t}\n\n\tfun orderBy(orderBy: String?) = apply {\n\t\tthis@MangaQueryBuilder.orderBy = orderBy\n\t}\n\n\tfun groupBy(groupBy: String?) = apply {\n\t\tthis@MangaQueryBuilder.groupBy = groupBy\n\t}\n\n\tfun limit(limit: Int) = apply {\n\t\tthis@MangaQueryBuilder.limit = limit\n\t}\n\n\tfun join(join: String?) = apply {\n\t\textraJoins = join\n\t}\n\n\tfun build() = buildString {\n\t\tappend(\"SELECT * FROM \")\n\t\tappend(table)\n\t\textraJoins?.let {\n\t\t\tappend(' ')\n\t\t\tappend(it)\n\t\t}\n\t\tif (whereConditions.isNotEmpty()) {\n\t\t\twhereConditions.joinTo(\n\t\t\t\tbuffer = this,\n\t\t\t\tprefix = \" WHERE \",\n\t\t\t\tseparator = \" AND \",\n\t\t\t)\n\t\t}\n\t\tif (filterOptions.isNotEmpty()) {\n\t\t\tif (whereConditions.isEmpty()) {\n\t\t\t\tappend(\" WHERE\")\n\t\t\t} else {\n\t\t\t\tappend(\" AND\")\n\t\t\t}\n\t\t\tvar isFirst = true\n\t\t\tval groupedOptions = filterOptions.groupBy { it.groupKey }\n\t\t\tfor ((_, group) in groupedOptions) {\n\t\t\t\tif (group.isEmpty()) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (isFirst) {\n\t\t\t\t\tisFirst = false\n\t\t\t\t\tappend(' ')\n\t\t\t\t} else {\n\t\t\t\t\tappend(\" AND \")\n\t\t\t\t}\n\t\t\t\tif (group.size > 1) {\n\t\t\t\t\tgroup.joinTo(\n\t\t\t\t\t\tbuffer = this,\n\t\t\t\t\t\tseparator = \" OR \",\n\t\t\t\t\t\tprefix = \"(\",\n\t\t\t\t\t\tpostfix = \")\",\n\t\t\t\t\t\ttransform = ::getConditionOrThrow,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tappend(getConditionOrThrow(group.single()))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tgroupBy?.let {\n\t\t\tappend(\" GROUP BY \")\n\t\t\tappend(it)\n\t\t}\n\t\torderBy?.let {\n\t\t\tappend(\" ORDER BY \")\n\t\t\tappend(it)\n\t\t}\n\t\tif (limit > 0) {\n\t\t\tappend(\" LIMIT \")\n\t\t\tappend(limit)\n\t\t}\n\t}.let { SimpleSQLiteQuery(it) }\n\n\tprivate fun getConditionOrThrow(option: ListFilterOption): String = when (option) {\n\t\tis ListFilterOption.Inverted -> \"NOT(${getConditionOrThrow(option.option)})\"\n\t\telse -> requireNotNull(conditionCallback.getCondition(option)) {\n\t\t\t\"Unsupported filter option $option\"\n\t\t}\n\t}\n\n\tfun interface ConditionCallback {\n\n\t\tfun getCondition(option: ListFilterOption): String?\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/Tables.kt",
    "content": "package org.koitharu.kotatsu.core.db\n\nconst val TABLE_FAVOURITES = \"favourites\"\nconst val TABLE_MANGA = \"manga\"\nconst val TABLE_TAGS = \"tags\"\nconst val TABLE_FAVOURITE_CATEGORIES = \"favourite_categories\"\nconst val TABLE_HISTORY = \"history\"\nconst val TABLE_MANGA_TAGS = \"manga_tags\"\nconst val TABLE_SOURCES = \"sources\"\nconst val TABLE_CHAPTERS = \"chapters\"\nconst val TABLE_PREFERENCES = \"preferences\"\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/ChaptersDao.kt",
    "content": "package org.koitharu.kotatsu.core.db.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport org.koitharu.kotatsu.core.db.entity.ChapterEntity\n\n@Dao\nabstract class ChaptersDao {\n\n\t@Query(\"SELECT * FROM chapters WHERE manga_id = :mangaId ORDER BY `index` ASC\")\n\tabstract suspend fun findAll(mangaId: Long): List<ChapterEntity>\n\n\t@Query(\"DELETE FROM chapters WHERE manga_id = :mangaId\")\n\tabstract suspend fun deleteAll(mangaId: Long)\n\n\t@Query(\"DELETE FROM chapters WHERE manga_id NOT IN (SELECT manga_id FROM history WHERE deleted_at = 0) AND manga_id NOT IN (SELECT manga_id FROM favourites WHERE deleted_at = 0)\")\n\tabstract suspend fun gc()\n\n\t@Transaction\n\topen suspend fun replaceAll(mangaId: Long, entities: Collection<ChapterEntity>) {\n\t\tdeleteAll(mangaId)\n\t\tinsert(entities)\n\t}\n\n\t@Insert(onConflict = OnConflictStrategy.REPLACE)\n\tprotected abstract suspend fun insert(entities: Collection<ChapterEntity>)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaDao.kt",
    "content": "package org.koitharu.kotatsu.core.db.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport androidx.room.Update\nimport androidx.room.Upsert\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaTagsEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\n@Dao\nabstract class MangaDao {\n\n\t@Transaction\n\t@Query(\"SELECT * FROM manga WHERE manga_id = :id\")\n\tabstract suspend fun find(id: Long): MangaWithTags?\n\n\t@Query(\"SELECT EXISTS(SELECT * FROM manga WHERE manga_id = :id)\")\n\tabstract suspend operator fun contains(id: Long): Boolean\n\n\t@Transaction\n\t@Query(\"SELECT * FROM manga WHERE public_url = :publicUrl\")\n\tabstract suspend fun findByPublicUrl(publicUrl: String): MangaWithTags?\n\n\t@Transaction\n\t@Query(\"SELECT * FROM manga WHERE source = :source\")\n\tabstract suspend fun findAllBySource(source: String): List<MangaWithTags>\n\n\t@Query(\"SELECT author FROM manga WHERE author LIKE :query GROUP BY author ORDER BY COUNT(author) DESC LIMIT :limit\")\n\tabstract suspend fun findAuthors(query: String, limit: Int): List<String>\n\n    @Query(\"SELECT author FROM manga WHERE manga.source = :source AND author IS NOT NULL AND author != '' GROUP BY author ORDER BY COUNT(author) DESC LIMIT :limit\")\n    abstract suspend fun findAuthorsBySource(source: String, limit: Int): List<String>\n\n\t@Transaction\n\t@Query(\"SELECT * FROM manga WHERE (title LIKE :query OR alt_title LIKE :query) AND manga_id IN (SELECT manga_id FROM favourites UNION SELECT manga_id FROM history) LIMIT :limit\")\n\tabstract suspend fun searchByTitle(query: String, limit: Int): List<MangaWithTags>\n\n\t@Transaction\n\t@Query(\"SELECT * FROM manga WHERE (title LIKE :query OR alt_title LIKE :query) AND source = :source AND manga_id IN (SELECT manga_id FROM favourites UNION SELECT manga_id FROM history) LIMIT :limit\")\n\tabstract suspend fun searchByTitle(query: String, source: String, limit: Int): List<MangaWithTags>\n\n\t@Upsert\n\tprotected abstract suspend fun upsert(manga: MangaEntity)\n\n\t@Update(onConflict = OnConflictStrategy.IGNORE)\n\tabstract suspend fun update(manga: MangaEntity): Int\n\n\t@Insert(onConflict = OnConflictStrategy.IGNORE)\n\tabstract suspend fun insertTagRelation(tag: MangaTagsEntity): Long\n\n\t@Query(\"DELETE FROM manga_tags WHERE manga_id = :mangaId\")\n\tabstract suspend fun clearTagRelation(mangaId: Long)\n\n\t@Transaction\n\t@Delete\n\tabstract suspend fun delete(subjects: Collection<MangaEntity>)\n\n\t@Query(\n\t\t\"\"\"\n\t\tDELETE FROM manga WHERE NOT EXISTS(SELECT * FROM history WHERE history.manga_id == manga.manga_id) \n\t\t\tAND NOT EXISTS(SELECT * FROM favourites WHERE favourites.manga_id == manga.manga_id)\n\t\t\tAND NOT EXISTS(SELECT * FROM bookmarks WHERE bookmarks.manga_id == manga.manga_id)\n\t\t\tAND NOT EXISTS(SELECT * FROM suggestions WHERE suggestions.manga_id == manga.manga_id)\n\t\t\tAND NOT EXISTS(SELECT * FROM scrobblings WHERE scrobblings.manga_id == manga.manga_id)\n\t\t\tAND NOT EXISTS(SELECT * FROM local_index WHERE local_index.manga_id == manga.manga_id)\n\t\t\tAND manga.manga_id NOT IN (:idsToKeep)\n\t\t\"\"\",\n\t)\n\tabstract suspend fun cleanup(idsToKeep: Set<Long>)\n\n\t@Transaction\n\topen suspend fun upsert(manga: MangaEntity, tags: Iterable<TagEntity>? = null) {\n\t\tupsert(manga)\n\t\tif (tags != null) {\n\t\t\tclearTagRelation(manga.id)\n\t\t\ttags.map {\n\t\t\t\tMangaTagsEntity(manga.id, it.id)\n\t\t\t}.forEach {\n\t\t\t\tinsertTagRelation(it)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt",
    "content": "package org.koitharu.kotatsu.core.db.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.RawQuery\nimport androidx.room.Transaction\nimport androidx.room.Upsert\nimport androidx.sqlite.db.SimpleSQLiteQuery\nimport androidx.sqlite.db.SupportSQLiteQuery\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.db.entity.MangaSourceEntity\nimport org.koitharu.kotatsu.explore.data.SourcesSortOrder\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper.PROTECTION_CAPTCHA\n\n@Dao\nabstract class MangaSourcesDao {\n\n\t@Query(\"SELECT * FROM sources ORDER BY pinned DESC, sort_key\")\n\tabstract suspend fun findAll(): List<MangaSourceEntity>\n\n\t@Query(\"SELECT source FROM sources WHERE enabled = 1\")\n\tabstract suspend fun findAllEnabledNames(): List<String>\n\n\t@Query(\"SELECT * FROM sources WHERE added_in >= :version\")\n\tabstract suspend fun findAllFromVersion(version: Int): List<MangaSourceEntity>\n\n\t@Query(\"SELECT * FROM sources ORDER BY used_at DESC LIMIT :limit\")\n\tabstract suspend fun findLastUsed(limit: Int): List<MangaSourceEntity>\n\n\t@Query(\"SELECT * FROM sources ORDER BY pinned DESC, sort_key\")\n\tabstract fun observeAll(): Flow<List<MangaSourceEntity>>\n\n\t@Query(\"SELECT enabled FROM sources WHERE source = :source\")\n\tabstract fun observeIsEnabled(source: String): Flow<Boolean>\n\n\t@Query(\"SELECT IFNULL(MAX(sort_key),0) FROM sources\")\n\tabstract suspend fun getMaxSortKey(): Int\n\n\t@Query(\"UPDATE sources SET enabled = 0\")\n\tabstract suspend fun disableAllSources()\n\n\t@Query(\"UPDATE sources SET sort_key = :sortKey WHERE source = :source\")\n\tabstract suspend fun setSortKey(source: String, sortKey: Int)\n\n\t@Query(\"UPDATE sources SET used_at = :value WHERE source = :source\")\n\tabstract suspend fun setLastUsed(source: String, value: Long)\n\n\t@Query(\"UPDATE sources SET pinned = :isPinned WHERE source = :source\")\n\tabstract suspend fun setPinned(source: String, isPinned: Boolean)\n\n\t@Query(\"UPDATE sources SET cf_state = :state WHERE source = :source\")\n\tabstract suspend fun setCfState(source: String, state: Int)\n\n\t@Insert(onConflict = OnConflictStrategy.IGNORE)\n\t@Transaction\n\tabstract suspend fun insertIfAbsent(entries: Collection<MangaSourceEntity>)\n\n\t@Upsert\n\tabstract suspend fun upsert(entry: MangaSourceEntity)\n\n\t@Query(\"SELECT * FROM sources WHERE pinned = 1\")\n\tabstract suspend fun findAllPinned(): List<MangaSourceEntity>\n\n\t@Query(\"SELECT * FROM sources WHERE cf_state = $PROTECTION_CAPTCHA\")\n\tabstract suspend fun findAllCaptchaRequired(): List<MangaSourceEntity>\n\n\tfun observeAll(enabledOnly: Boolean, order: SourcesSortOrder): Flow<List<MangaSourceEntity>> =\n\t\tobserveImpl(getQuery(enabledOnly, order))\n\n\tsuspend fun findAll(enabledOnly: Boolean, order: SourcesSortOrder): List<MangaSourceEntity> =\n\t\tfindAllImpl(getQuery(enabledOnly, order))\n\n\t@Transaction\n\topen suspend fun setEnabled(source: String, isEnabled: Boolean) {\n\t\tif (updateIsEnabled(source, isEnabled) == 0) {\n\t\t\tval entity = MangaSourceEntity(\n\t\t\t\tsource = source,\n\t\t\t\tisEnabled = isEnabled,\n\t\t\t\tsortKey = getMaxSortKey() + 1,\n\t\t\t\taddedIn = BuildConfig.VERSION_CODE,\n\t\t\t\tlastUsedAt = 0,\n\t\t\t\tisPinned = false,\n\t\t\t\tcfState = CloudFlareHelper.PROTECTION_NOT_DETECTED,\n\t\t\t)\n\t\t\tupsert(entity)\n\t\t}\n\t}\n\n\tfun dumpEnabled(): Flow<MangaSourceEntity> = flow {\n\t\tval window = 10\n\t\tvar offset = 0\n\t\twhile (currentCoroutineContext().isActive) {\n\t\t\tval list = findAllEnabled(offset, window)\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset += window\n\t\t\tlist.forEach { emit(it) }\n\t\t}\n\t}\n\n\t@Query(\"UPDATE sources SET enabled = :isEnabled WHERE source = :source\")\n\tprotected abstract suspend fun updateIsEnabled(source: String, isEnabled: Boolean): Int\n\n\t@RawQuery(observedEntities = [MangaSourceEntity::class])\n\tprotected abstract fun observeImpl(query: SupportSQLiteQuery): Flow<List<MangaSourceEntity>>\n\n\t@RawQuery\n\tprotected abstract suspend fun findAllImpl(query: SupportSQLiteQuery): List<MangaSourceEntity>\n\n\t@Query(\"SELECT * FROM sources WHERE enabled = 1 ORDER BY source LIMIT :limit OFFSET :offset\")\n\tprotected abstract suspend fun findAllEnabled(offset: Int, limit: Int): List<MangaSourceEntity>\n\n\tprivate fun getQuery(enabledOnly: Boolean, order: SourcesSortOrder) = SimpleSQLiteQuery(\n\t\tbuildString {\n\t\t\tappend(\"SELECT * FROM sources \")\n\t\t\tif (enabledOnly) {\n\t\t\t\tappend(\"WHERE enabled = 1 \")\n\t\t\t}\n\t\t\tappend(\"ORDER BY pinned DESC, \")\n\t\t\tappend(getOrderBy(order))\n\t\t},\n\t)\n\n\tprivate fun getOrderBy(order: SourcesSortOrder) = when (order) {\n\t\tSourcesSortOrder.ALPHABETIC -> \"source ASC\"\n\t\tSourcesSortOrder.POPULARITY -> \"(SELECT COUNT(*) FROM manga WHERE source = sources.source) DESC\"\n\t\tSourcesSortOrder.MANUAL -> \"sort_key ASC\"\n\t\tSourcesSortOrder.LAST_USED -> \"used_at DESC\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/PreferencesDao.kt",
    "content": "package org.koitharu.kotatsu.core.db.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Upsert\nimport kotlinx.coroutines.flow.Flow\nimport org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity\n\n@Dao\nabstract class PreferencesDao {\n\n\t@Query(\"SELECT * FROM preferences WHERE manga_id = :mangaId\")\n\tabstract suspend fun find(mangaId: Long): MangaPrefsEntity?\n\n\t@Query(\"SELECT * FROM preferences WHERE manga_id = :mangaId\")\n\tabstract fun observe(mangaId: Long): Flow<MangaPrefsEntity?>\n\n\t@Query(\"SELECT * FROM preferences WHERE title_override IS NOT NULL OR cover_override IS NOT NULL OR content_rating_override IS NOT NULL\")\n\tabstract suspend fun getOverrides(): List<MangaPrefsEntity>\n\n\t@Query(\"UPDATE preferences SET cf_brightness = 0, cf_contrast = 0, cf_invert = 0, cf_grayscale = 0\")\n\tabstract suspend fun resetColorFilters()\n\n\t@Upsert\n\tabstract suspend fun upsert(pref: MangaPrefsEntity)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TagsDao.kt",
    "content": "package org.koitharu.kotatsu.core.db.dao\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Upsert\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\n@Dao\nabstract class TagsDao {\n\n\t@Query(\"SELECT * FROM tags WHERE source = :source\")\n\tabstract suspend fun findTags(source: String): List<TagEntity>\n\n\t@Query(\n\t\t\"\"\"SELECT tags.* FROM tags\n\t\tLEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id\n\t\tWHERE manga_tags.manga_id IN (SELECT manga_id FROM history UNION SELECT manga_id FROM favourites)\n\t\tGROUP BY tags.title \n\t\tORDER BY COUNT(manga_id) DESC \n\t\tLIMIT :limit\"\"\",\n\t)\n\tabstract suspend fun findPopularTags(limit: Int): List<TagEntity>\n\n\t@Query(\n\t\t\"\"\"SELECT tags.* FROM tags\n\t\tLEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id \n\t\tWHERE tags.source = :source  \n\t\tGROUP BY tags.title\n\t\tORDER BY COUNT(manga_id) DESC \n\t\tLIMIT :limit\"\"\",\n\t)\n\tabstract suspend fun findPopularTags(source: String, limit: Int): List<TagEntity>\n\n\t@Query(\n\t\t\"\"\"SELECT tags.* FROM tags\n\t\tLEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id \n\t\tWHERE tags.source = :source  \n\t\tGROUP BY tags.title\n\t\tORDER BY COUNT(manga_id) ASC \n\t\tLIMIT :limit\"\"\",\n\t)\n\tabstract suspend fun findRareTags(source: String, limit: Int): List<TagEntity>\n\n\t@Query(\n\t\t\"\"\"SELECT tags.* FROM tags\n\t\tLEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id \n\t\tWHERE tags.source = :source AND title LIKE :query \n\t\tGROUP BY tags.title\n\t\tORDER BY COUNT(manga_id) DESC \n\t\tLIMIT :limit\"\"\",\n\t)\n\tabstract suspend fun findTags(source: String, query: String, limit: Int): List<TagEntity>\n\n\t@Query(\n\t\t\"\"\"SELECT tags.* FROM tags\n\t\tLEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id \n\t\tWHERE title LIKE :query AND manga_tags.manga_id IN (SELECT manga_id FROM history UNION SELECT manga_id FROM favourites)\n\t\tGROUP BY tags.title\n\t\tORDER BY COUNT(manga_id) DESC \n\t\tLIMIT :limit\"\"\",\n\t)\n\tabstract suspend fun findTags(query: String, limit: Int): List<TagEntity>\n\n\t@Query(\n\t\t\"\"\"\n\t\tSELECT tags.* FROM manga_tags \n\t\tLEFT JOIN tags ON tags.tag_id = manga_tags.tag_id \n\t\tWHERE manga_tags.manga_id IN (SELECT manga_id FROM manga_tags WHERE tag_id = :tagId)\n\t\tGROUP BY tags.tag_id \n\t\tORDER BY COUNT(manga_id) DESC;\n\t\"\"\",\n\t)\n\tabstract suspend fun findRelatedTags(tagId: Long): List<TagEntity>\n\n\t@Query(\n\t\t\"\"\"\n\t\tSELECT tags.* FROM manga_tags \n\t\tLEFT JOIN tags ON tags.tag_id = manga_tags.tag_id \n\t\tWHERE manga_tags.manga_id IN (SELECT manga_id FROM manga_tags WHERE tag_id IN (:ids))\n\t\tGROUP BY tags.tag_id \n\t\tORDER BY COUNT(manga_id) DESC;\n\t\"\"\",\n\t)\n\tabstract suspend fun findRelatedTags(ids: Set<Long>): List<TagEntity>\n\n\t@Upsert\n\tabstract suspend fun upsert(tags: Iterable<TagEntity>)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/TrackLogsDao.kt",
    "content": "package org.koitharu.kotatsu.core.db.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.RawQuery\nimport androidx.room.Transaction\nimport androidx.sqlite.db.SupportSQLiteQuery\nimport kotlinx.coroutines.flow.Flow\nimport org.koitharu.kotatsu.core.db.MangaQueryBuilder\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.tracker.data.TrackLogEntity\nimport org.koitharu.kotatsu.tracker.data.TrackLogWithManga\n\n@Dao\nabstract class TrackLogsDao : MangaQueryBuilder.ConditionCallback {\n\n\tfun observeAll(\n\t\tlimit: Int,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t): Flow<List<TrackLogWithManga>> = observeAllImpl(\n\t\tMangaQueryBuilder(\"track_logs\", this)\n\t\t\t.filters(filterOptions)\n\t\t\t.limit(limit)\n\t\t\t.orderBy(\"created_at DESC\")\n\t\t\t.build(),\n\t)\n\n\t@Query(\"SELECT COUNT(*) FROM track_logs WHERE unread = 1\")\n\tabstract fun observeUnreadCount(): Flow<Int>\n\n\t@Query(\"DELETE FROM track_logs\")\n\tabstract suspend fun clear()\n\n\t@Query(\"UPDATE track_logs SET unread = 0 WHERE id = :id\")\n\tabstract suspend fun markAsRead(id: Long)\n\n\t@Insert(onConflict = OnConflictStrategy.REPLACE)\n\tabstract suspend fun insert(entity: TrackLogEntity): Long\n\n\t@Query(\"DELETE FROM track_logs WHERE manga_id NOT IN (SELECT manga_id FROM tracks)\")\n\tabstract suspend fun gc()\n\n\t@Query(\"DELETE FROM track_logs WHERE id IN (SELECT id FROM track_logs ORDER BY created_at DESC LIMIT 0 OFFSET :size)\")\n\tabstract suspend fun trim(size: Int)\n\n\t@Query(\"SELECT COUNT(*) FROM track_logs\")\n\tabstract suspend fun count(): Int\n\n\t@Transaction\n\t@RawQuery(observedEntities = [TrackLogEntity::class])\n\tprotected abstract fun observeAllImpl(query: SupportSQLiteQuery): Flow<List<TrackLogWithManga>>\n\n\toverride fun getCondition(option: ListFilterOption): String? = when (option) {\n\t\tListFilterOption.Macro.FAVORITE -> \"EXISTS(SELECT * FROM favourites WHERE favourites.manga_id = track_logs.manga_id)\"\n\t\tis ListFilterOption.Favorite -> \"EXISTS(SELECT * FROM favourites WHERE favourites.manga_id = track_logs.manga_id AND favourites.category_id = ${option.category.id})\"\n\t\tis ListFilterOption.Tag -> \"EXISTS(SELECT * FROM manga_tags WHERE manga_tags.manga_id = track_logs.manga_id AND tag_id = ${option.tagId})\"\n\t\tListFilterOption.Macro.NSFW -> \"(SELECT nsfw FROM manga WHERE manga.manga_id = track_logs.manga_id) = 1\"\n\t\telse -> null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/ChapterEntity.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport org.koitharu.kotatsu.core.db.TABLE_CHAPTERS\n\n@Entity(\n\ttableName = TABLE_CHAPTERS,\n\tprimaryKeys = [\"manga_id\", \"chapter_id\"],\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t],\n)\ndata class ChapterEntity(\n\t@ColumnInfo(name = \"chapter_id\") val chapterId: Long,\n\t@ColumnInfo(name = \"manga_id\") val mangaId: Long,\n\t@ColumnInfo(name = \"name\") val title: String,\n\t@ColumnInfo(name = \"number\") val number: Float,\n\t@ColumnInfo(name = \"volume\") val volume: Int,\n\t@ColumnInfo(name = \"url\") val url: String,\n\t@ColumnInfo(name = \"scanlator\") val scanlator: String?,\n\t@ColumnInfo(name = \"upload_date\") val uploadDate: Long,\n\t@ColumnInfo(name = \"branch\") val branch: String?,\n\t@ColumnInfo(name = \"source\") val source: String,\n\t@ColumnInfo(name = \"index\") val index: Int,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.longHashCode\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.toArraySet\nimport org.koitharu.kotatsu.parsers.util.toTitleCase\n\nprivate const val VALUES_DIVIDER = '\\n'\n\n// Entity to model\n\nfun TagEntity.toMangaTag() = MangaTag(\n\tkey = this.key,\n\ttitle = this.title.toTitleCase(),\n\tsource = MangaSource(this.source),\n)\n\nfun Collection<TagEntity>.toMangaTags() = mapToSet(TagEntity::toMangaTag)\n\nfun Collection<TagEntity>.toMangaTagsList() = map(TagEntity::toMangaTag)\n\nfun MangaEntity.toManga(tags: Set<MangaTag>, chapters: List<ChapterEntity>?) = Manga(\n\tid = this.id,\n\ttitle = this.title,\n\taltTitles = this.altTitles?.split(VALUES_DIVIDER)?.toArraySet().orEmpty(),\n\tstate = this.state?.let { MangaState(it) },\n\trating = this.rating,\n\tcontentRating = ContentRating(this.contentRating)\n\t\t?: if (isNsfw) ContentRating.ADULT else null,\n\turl = this.url,\n\tpublicUrl = this.publicUrl,\n\tcoverUrl = this.coverUrl,\n\tlargeCoverUrl = this.largeCoverUrl,\n\tauthors = this.authors?.split(VALUES_DIVIDER)?.toArraySet().orEmpty(),\n\tsource = MangaSource(this.source),\n\ttags = tags,\n\tchapters = chapters?.toMangaChapters(),\n)\n\nfun MangaWithTags.toManga(chapters: List<ChapterEntity>? = null) = manga.toManga(tags.toMangaTags(), chapters)\n\nfun Collection<MangaWithTags>.toMangaList() = map { it.toManga() }\n\nfun ChapterEntity.toMangaChapter() = MangaChapter(\n\tid = chapterId,\n\ttitle = title.nullIfEmpty(),\n\tnumber = number,\n\tvolume = volume,\n\turl = url,\n\tscanlator = scanlator,\n\tuploadDate = uploadDate,\n\tbranch = branch,\n\tsource = MangaSource(source),\n)\n\nfun Collection<ChapterEntity>.toMangaChapters() = map { it.toMangaChapter() }\n\n// Model to entity\n\nfun Manga.toEntity() = MangaEntity(\n\tid = id,\n\turl = url,\n\tpublicUrl = publicUrl,\n\tsource = source.name,\n\tlargeCoverUrl = largeCoverUrl,\n\tcoverUrl = coverUrl.orEmpty(),\n\taltTitles = altTitles.joinToString(VALUES_DIVIDER.toString()),\n\trating = rating,\n\tisNsfw = isNsfw,\n\tcontentRating = contentRating?.name,\n\tstate = state?.name,\n\ttitle = title,\n\tauthors = authors.joinToString(VALUES_DIVIDER.toString()),\n)\n\nfun MangaTag.toEntity() = TagEntity(\n\ttitle = title,\n\tkey = key,\n\tsource = source.name,\n\tid = \"${key}_${source.name}\".longHashCode(),\n\tisPinned = false, // for future use\n)\n\nfun Collection<MangaTag>.toEntities() = map(MangaTag::toEntity)\n\nfun Iterable<IndexedValue<MangaChapter>>.toEntities(mangaId: Long) = map { (index, chapter) ->\n\tChapterEntity(\n\t\tchapterId = chapter.id,\n\t\tmangaId = mangaId,\n\t\ttitle = chapter.title.orEmpty(),\n\t\tnumber = chapter.number,\n\t\tvolume = chapter.volume,\n\t\turl = chapter.url,\n\t\tscanlator = chapter.scanlator,\n\t\tuploadDate = chapter.uploadDate,\n\t\tbranch = chapter.branch,\n\t\tsource = chapter.source.name,\n\t\tindex = index,\n\t)\n}\n\n// Other\n\nfun SortOrder(name: String, fallback: SortOrder): SortOrder = runCatching {\n\tSortOrder.valueOf(name)\n}.getOrDefault(fallback)\n\nfun MangaState(name: String): MangaState? = runCatching {\n\tMangaState.valueOf(name)\n}.getOrNull()\n\nfun ContentRating(name: String?): ContentRating? = runCatching {\n\tContentRating.valueOf(name ?: return@runCatching null)\n}.getOrNull()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.TABLE_MANGA\n\n@Entity(tableName = TABLE_MANGA)\ndata class MangaEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"manga_id\") val id: Long,\n\t@ColumnInfo(name = \"title\") val title: String,\n\t@ColumnInfo(name = \"alt_title\") val altTitles: String?,\n\t@ColumnInfo(name = \"url\") val url: String,\n\t@ColumnInfo(name = \"public_url\") val publicUrl: String,\n\t@ColumnInfo(name = \"rating\") val rating: Float, // normalized value [0..1] or -1\n\t@ColumnInfo(name = \"nsfw\") val isNsfw: Boolean,\n\t@ColumnInfo(name = \"content_rating\") val contentRating: String?,\n\t@ColumnInfo(name = \"cover_url\") val coverUrl: String,\n\t@ColumnInfo(name = \"large_cover_url\") val largeCoverUrl: String?,\n\t@ColumnInfo(name = \"state\") val state: String?,\n\t@ColumnInfo(name = \"author\") val authors: String?,\n\t@ColumnInfo(name = \"source\") val source: String,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.TABLE_PREFERENCES\n\n@Entity(\n\ttableName = TABLE_PREFERENCES,\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t],\n)\ndata class MangaPrefsEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"manga_id\")\n\tval mangaId: Long,\n\t@ColumnInfo(name = \"mode\") val mode: Int,\n\t@ColumnInfo(name = \"cf_brightness\") val cfBrightness: Float,\n\t@ColumnInfo(name = \"cf_contrast\") val cfContrast: Float,\n\t@ColumnInfo(name = \"cf_invert\") val cfInvert: Boolean,\n\t@ColumnInfo(name = \"cf_grayscale\") val cfGrayscale: Boolean,\n\t@ColumnInfo(name = \"cf_book\") val cfBookEffect: Boolean,\n\t@ColumnInfo(name = \"title_override\") val titleOverride: String?,\n\t@ColumnInfo(name = \"cover_override\") val coverUrlOverride: String?,\n\t@ColumnInfo(name = \"content_rating_override\") val contentRatingOverride: String?,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaSourceEntity.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.TABLE_SOURCES\n\n@Entity(\n\ttableName = TABLE_SOURCES,\n)\ndata class MangaSourceEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"source\")\n\tval source: String,\n\t@ColumnInfo(name = \"enabled\") val isEnabled: Boolean,\n\t@ColumnInfo(name = \"sort_key\", index = true) val sortKey: Int,\n\t@ColumnInfo(name = \"added_in\") val addedIn: Int,\n\t@ColumnInfo(name = \"used_at\") val lastUsedAt: Long,\n\t@ColumnInfo(name = \"pinned\") val isPinned: Boolean,\n\t@ColumnInfo(name = \"cf_state\") val cfState: Int,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS\n\n@Entity(\n\ttableName = TABLE_MANGA_TAGS,\n\tprimaryKeys = [\"manga_id\", \"tag_id\"],\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t\tForeignKey(\n\t\t\tentity = TagEntity::class,\n\t\t\tparentColumns = [\"tag_id\"],\n\t\t\tchildColumns = [\"tag_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t)\n\t]\n)\nclass MangaTagsEntity(\n\t@ColumnInfo(name = \"manga_id\", index = true) val mangaId: Long,\n\t@ColumnInfo(name = \"tag_id\", index = true) val tagId: Long,\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport androidx.room.Embedded\nimport androidx.room.Junction\nimport androidx.room.Relation\n\ndata class MangaWithTags(\n\t@Embedded val manga: MangaEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"tag_id\",\n\t\tassociateBy = Junction(MangaTagsEntity::class)\n\t)\n\tval tags: List<TagEntity>,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/TagEntity.kt",
    "content": "package org.koitharu.kotatsu.core.db.entity\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.TABLE_TAGS\n\n@Entity(tableName = TABLE_TAGS)\ndata class TagEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"tag_id\") val id: Long,\n\t@ColumnInfo(name = \"title\") val title: String,\n\t@ColumnInfo(name = \"key\") val key: String,\n\t@ColumnInfo(name = \"source\") val source: String,\n\t@ColumnInfo(name = \"pinned\") val isPinned: Boolean,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration10To11.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration10To11 : Migration(10, 11) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\n\t\t\t\"\"\"\n\t\t\tCREATE TABLE IF NOT EXISTS `bookmarks` (\n\t\t\t\t`manga_id` INTEGER NOT NULL,\n\t\t\t\t`page_id` INTEGER NOT NULL,\n\t\t\t\t`chapter_id` INTEGER NOT NULL, \n\t\t\t\t`page` INTEGER NOT NULL,\n\t\t\t\t`scroll` INTEGER NOT NULL,\n\t\t\t\t`image` TEXT NOT NULL,\n\t\t\t\t`created_at` INTEGER NOT NULL,\n\t\t\t\tPRIMARY KEY(`manga_id`, `page_id`),\n\t\t\t\tFOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )\n\t\t\t\"\"\".trimIndent()\n\t\t)\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS `index_bookmarks_manga_id` ON `bookmarks` (`manga_id`)\")\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS `index_bookmarks_page_id` ON `bookmarks` (`page_id`)\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration11To12.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration11To12 : Migration(11, 12) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\n\t\t\t\"\"\"\n\t\t\tCREATE TABLE IF NOT EXISTS `scrobblings` (\n\t\t\t\t`scrobbler` INTEGER NOT NULL,\n\t\t\t\t`id` INTEGER NOT NULL,\n\t\t\t\t`manga_id` INTEGER NOT NULL,\n\t\t\t\t`target_id` INTEGER NOT NULL, \n\t\t\t\t`status` TEXT,\n\t\t\t\t`chapter` INTEGER NOT NULL, \n\t\t\t\t`comment` TEXT,\n\t\t\t\t`rating` REAL NOT NULL,\n\t\t\t\tPRIMARY KEY(`scrobbler`, `id`, `manga_id`)\n\t\t\t)\n\t\t\t\"\"\".trimIndent()\n\t\t)\n\t\tdb.execSQL(\"ALTER TABLE history ADD COLUMN `percent` REAL NOT NULL DEFAULT -1\")\n\t\tdb.execSQL(\"ALTER TABLE bookmarks ADD COLUMN `percent` REAL NOT NULL DEFAULT -1\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration12To13.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration12To13 : Migration(12, 13) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE favourite_categories ADD COLUMN `show_in_lib` INTEGER NOT NULL DEFAULT 1\")\n\t\tdb.execSQL(\"ALTER TABLE favourites ADD COLUMN `sort_key` INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration13To14.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration13To14 : Migration(13, 14) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE favourite_categories ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"ALTER TABLE favourites ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"ALTER TABLE history ADD COLUMN `deleted_at` INTEGER NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN `cf_brightness` REAL NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN `cf_contrast` REAL NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration14To15.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration14To15 : Migration(14, 15) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration15To16.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration15To16 : Migration(15, 16) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN `cf_invert` INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration16To17.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport android.content.Context\nimport androidx.preference.PreferenceManager\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\n\nclass Migration16To17(context: Context) : Migration(16, 17) {\n\n\tprivate val prefs = PreferenceManager.getDefaultSharedPreferences(context)\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"CREATE TABLE `sources` (`source` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `sort_key` INTEGER NOT NULL, PRIMARY KEY(`source`))\")\n\t\tdb.execSQL(\"CREATE INDEX `index_sources_sort_key` ON `sources` (`sort_key`)\")\n\t\tval hiddenSources = prefs.getStringSet(\"sources_hidden\", null).orEmpty()\n\t\tval order = prefs.getString(\"sources_order_2\", null)?.split('|').orEmpty()\n\t\tval sources = MangaParserSource.entries\n\t\tfor (source in sources) {\n\t\t\tval name = source.name\n\t\t\tval isHidden = name in hiddenSources\n\t\t\tvar sortKey = order.indexOf(name)\n\t\t\tif (sortKey == -1) {\n\t\t\t\tif (isHidden) {\n\t\t\t\t\tsortKey = order.size + source.ordinal\n\t\t\t\t} else {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tdb.execSQL(\n\t\t\t\t\"INSERT INTO `sources` (`source`, `enabled`, `sort_key`) VALUES (?, ?, ?)\",\n\t\t\t\tarrayOf(name, (!isHidden).toInt(), sortKey),\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun Boolean.toInt() = if (this) 1 else 0\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration17To18.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration17To18 : Migration(17, 18) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN `cf_grayscale` INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration18To19.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration18To19 : Migration(18, 19) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE history ADD COLUMN `chapters` INTEGER NOT NULL DEFAULT -1\")\n\t\tdb.execSQL(\"CREATE TABLE IF NOT EXISTS `stats` (`manga_id` INTEGER NOT NULL, `started_at` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `pages` INTEGER NOT NULL, PRIMARY KEY(`manga_id`, `started_at`), FOREIGN KEY(`manga_id`) REFERENCES `history`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration19To20.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration19To20 : Migration(19, 20) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"CREATE TABLE tracks_bk (manga_id INTEGER NOT NULL, chapters_total INTEGER NOT NULL, last_chapter_id INTEGER NOT NULL, chapters_new INTEGER NOT NULL, last_check INTEGER NOT NULL, last_notified_id INTEGER NOT NULL, PRIMARY KEY(manga_id))\")\n\t\tdb.execSQL(\"INSERT INTO tracks_bk SELECT manga_id, chapters_total, last_chapter_id, chapters_new, last_check, last_notified_id FROM tracks\")\n\t\tdb.execSQL(\"DROP TABLE tracks\")\n\t\tdb.execSQL(\"CREATE TABLE tracks (`manga_id` INTEGER NOT NULL, `last_chapter_id` INTEGER NOT NULL, `chapters_new` INTEGER NOT NULL, `last_check_time` INTEGER NOT NULL, `last_chapter_date` INTEGER NOT NULL, `last_result` INTEGER NOT NULL, PRIMARY KEY(`manga_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )\")\n\t\tdb.execSQL(\"INSERT INTO tracks SELECT manga_id, last_chapter_id, chapters_new, last_check AS last_check_time, 0 AS last_chapter_date, 0 AS last_result FROM tracks_bk\")\n\t\tdb.execSQL(\"DROP TABLE tracks_bk\")\n\n\t\tdb.execSQL(\"ALTER TABLE track_logs ADD COLUMN `unread` INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration1To2.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration1To2 : Migration(1, 2) {\n\t/**\n\t * Adding foreign keys\n\t */\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\t/* manga_tags */\n\t\tdb.execSQL(\n\t\t\t\"CREATE TABLE IF NOT EXISTS manga_tags_tmp (manga_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, \" +\n\t\t\t\t\t\"PRIMARY KEY(manga_id, tag_id), \" +\n\t\t\t\t\t\"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE, \" +\n\t\t\t\t\t\"FOREIGN KEY(tag_id) REFERENCES tags(tag_id) ON UPDATE NO ACTION ON DELETE CASCADE )\"\n\t\t)\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS index_manga_tags_manga_id ON manga_tags_tmp (manga_id)\")\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS index_manga_tags_tag_id ON manga_tags_tmp (tag_id)\")\n\t\tdb.execSQL(\"INSERT INTO manga_tags_tmp (manga_id, tag_id) SELECT manga_id, tag_id FROM manga_tags\")\n\t\tdb.execSQL(\"DROP TABLE manga_tags\")\n\t\tdb.execSQL(\"ALTER TABLE manga_tags_tmp RENAME TO manga_tags\")\n\t\t/* favourites */\n\t\tdb.execSQL(\n\t\t\t\"CREATE TABLE IF NOT EXISTS favourites_tmp (manga_id INTEGER NOT NULL, category_id INTEGER NOT NULL, created_at INTEGER NOT NULL, \" +\n\t\t\t\t\t\"PRIMARY KEY(manga_id, category_id), \" +\n\t\t\t\t\t\"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE , \" +\n\t\t\t\t\t\"FOREIGN KEY(category_id) REFERENCES favourite_categories(category_id) ON UPDATE NO ACTION ON DELETE CASCADE )\"\n\t\t)\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS index_favourites_manga_id ON favourites_tmp (manga_id)\")\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS index_favourites_category_id ON favourites_tmp (category_id)\")\n\t\tdb.execSQL(\"INSERT INTO favourites_tmp (manga_id, category_id, created_at) SELECT manga_id, category_id, created_at FROM favourites\")\n\t\tdb.execSQL(\"DROP TABLE favourites\")\n\t\tdb.execSQL(\"ALTER TABLE favourites_tmp RENAME TO favourites\")\n\t\t/* history */\n\t\tdb.execSQL(\n\t\t\t\"CREATE TABLE IF NOT EXISTS history_tmp (manga_id INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, chapter_id INTEGER NOT NULL, page INTEGER NOT NULL, \" +\n\t\t\t\t\t\"PRIMARY KEY(manga_id), \" +\n\t\t\t\t\t\"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )\"\n\t\t)\n\t\tdb.execSQL(\"INSERT INTO history_tmp (manga_id, created_at, updated_at, chapter_id, page) SELECT manga_id, created_at, updated_at, chapter_id, page FROM history\")\n\t\tdb.execSQL(\"DROP TABLE history\")\n\t\tdb.execSQL(\"ALTER TABLE history_tmp RENAME TO history\")\n\t\t/* preferences */\n\t\tdb.execSQL(\n\t\t\t\"CREATE TABLE IF NOT EXISTS preferences_tmp (manga_id INTEGER NOT NULL, mode INTEGER NOT NULL,\" +\n\t\t\t\t\t\" PRIMARY KEY(manga_id), \" +\n\t\t\t\t\t\"FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )\"\n\t\t)\n\t\tdb.execSQL(\"INSERT INTO preferences_tmp (manga_id, mode) SELECT manga_id, mode FROM preferences\")\n\t\tdb.execSQL(\"DROP TABLE preferences\")\n\t\tdb.execSQL(\"ALTER TABLE preferences_tmp RENAME TO preferences\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration20To21.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration20To21 : Migration(20, 21) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE tracks ADD COLUMN `last_error` TEXT DEFAULT NULL\")\n\t\tdb.execSQL(\"ALTER TABLE sources ADD COLUMN `added_in` INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration21To22.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration21To22 : Migration(21, 22) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE sources ADD COLUMN `used_at` INTEGER NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"ALTER TABLE sources ADD COLUMN `pinned` INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration22To23.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration22To23 : Migration(22, 23) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"CREATE TABLE IF NOT EXISTS `local_index` (`manga_id` INTEGER NOT NULL, `path` TEXT NOT NULL, PRIMARY KEY(`manga_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration23To24.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration23To24 : Migration(23, 24) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"CREATE TABLE IF NOT EXISTS `chapters` (`chapter_id` INTEGER NOT NULL, `manga_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `number` REAL NOT NULL, `volume` INTEGER NOT NULL, `url` TEXT NOT NULL, `scanlator` TEXT, `upload_date` INTEGER NOT NULL, `branch` TEXT, `source` TEXT NOT NULL, `index` INTEGER NOT NULL, PRIMARY KEY(`manga_id`, `chapter_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration24To23.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration24To23 : Migration(24, 23) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"DROP TABLE IF EXISTS `chapters`\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration24To25.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration24To25 : Migration(24, 25) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE manga ADD COLUMN content_rating TEXT DEFAULT NULL\")\n\t\tdb.execSQL(\"UPDATE manga SET content_rating = 'ADULT' WHERE nsfw = 1\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration25To26.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration25To26 : Migration(25, 26) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE sources ADD COLUMN cf_state INTEGER NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN title_override TEXT DEFAULT NULL\")\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN cover_override TEXT DEFAULT NULL\")\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN content_rating_override TEXT DEFAULT NULL\")\n\t\tdb.execSQL(\"ALTER TABLE favourites ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"ALTER TABLE tags ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration26To27.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration26To27 : Migration(26, 27) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE preferences ADD COLUMN cf_book INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration2To3.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration2To3 : Migration(2, 3) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE history ADD COLUMN scroll REAL NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration3To4.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration3To4 : Migration(3, 4) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"CREATE TABLE IF NOT EXISTS tracks (manga_id INTEGER NOT NULL, chapters_total INTEGER NOT NULL, last_chapter_id INTEGER NOT NULL, chapters_new INTEGER NOT NULL, last_check INTEGER NOT NULL, last_notified_id INTEGER NOT NULL, PRIMARY KEY(manga_id), FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration4To5.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration4To5 : Migration(4, 5) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE favourite_categories ADD COLUMN sort_key INTEGER NOT NULL DEFAULT 0\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration5To6.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration5To6 : Migration(5, 6) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"CREATE TABLE IF NOT EXISTS track_logs (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, manga_id INTEGER NOT NULL, chapters TEXT NOT NULL, created_at INTEGER NOT NULL, FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE)\")\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS index_track_logs_manga_id ON track_logs (manga_id)\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration6To7.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration6To7 : Migration(6, 7) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE manga ADD COLUMN public_url TEXT NOT NULL DEFAULT ''\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration7To8.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration7To8 : Migration(7, 8) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE manga ADD COLUMN nsfw INTEGER NOT NULL DEFAULT 0\")\n\t\tdb.execSQL(\"CREATE TABLE IF NOT EXISTS suggestions (manga_id INTEGER NOT NULL, relevance REAL NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY(manga_id), FOREIGN KEY(manga_id) REFERENCES manga(manga_id) ON UPDATE NO ACTION ON DELETE CASCADE )\")\n\t\tdb.execSQL(\"CREATE INDEX IF NOT EXISTS index_suggestions_manga_id ON suggestions (manga_id)\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration8To9.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport org.koitharu.kotatsu.parsers.model.SortOrder\n\nclass Migration8To9 : Migration(8, 9) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE favourite_categories ADD COLUMN `order` TEXT NOT NULL DEFAULT ${SortOrder.NEWEST.name}\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration9To10.kt",
    "content": "package org.koitharu.kotatsu.core.db.migrations\n\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\n\nclass Migration9To10 : Migration(9, 10) {\n\n\toverride fun migrate(db: SupportSQLiteDatabase) {\n\t\tdb.execSQL(\"ALTER TABLE favourite_categories ADD COLUMN `track` INTEGER NOT NULL DEFAULT 1\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/BadBackupFormatException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport java.io.IOException\n\nclass BadBackupFormatException(cause: Throwable?) : IOException(cause)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CaughtException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nclass CaughtException(\n\toverride val cause: Throwable\n) : RuntimeException(\"${cause.javaClass.simpleName}(${cause.message})\", cause)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CloudFlareBlockedException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\n\nclass CloudFlareBlockedException(\n\toverride val url: String,\n\tsource: MangaSource?,\n) : CloudFlareException(\"Blocked by CloudFlare\", CloudFlareHelper.PROTECTION_BLOCKED) {\n\n\toverride val source: MangaSource = source ?: UnknownMangaSource\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CloudFlareException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport okio.IOException\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nabstract class CloudFlareException(\n\tmessage: String,\n\tval state: Int,\n) : IOException(message) {\n\n\tabstract val url: String\n\n\tabstract val source: MangaSource\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport okhttp3.Headers\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\n\nclass CloudFlareProtectedException(\n\toverride val url: String,\n\tsource: MangaSource?,\n\t@Transient val headers: Headers,\n) : CloudFlareException(\"Protected by CloudFlare\", CloudFlareHelper.PROTECTION_CAPTCHA) {\n\n\toverride val source: MangaSource = source ?: UnknownMangaSource\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/EmptyHistoryException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nclass EmptyHistoryException : RuntimeException()"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/EmptyMangaException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nclass EmptyMangaException(\n    val reason: EmptyMangaReason?,\n    val manga: Manga,\n    cause: Throwable?\n) : IllegalStateException(cause)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/IncompatiblePluginException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nclass IncompatiblePluginException(\n\tval name: String?,\n\tcause: Throwable?,\n) : RuntimeException(cause)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/InteractiveActionRequiredException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport okio.IOException\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nclass InteractiveActionRequiredException(\n\tval source: MangaSource,\n\tval url: String,\n) : IOException(\"Interactive action is required for ${source.name}\")\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/NoDataReceivedException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport okio.IOException\n\nclass NoDataReceivedException(\n\tval url: String,\n) : IOException(\"No data has been received from $url\")\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/NonFileUriException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport android.net.Uri\n\nclass NonFileUriException(\n\tval uri: Uri,\n) : IllegalArgumentException(\"Cannot resolve file name of \\\"$uri\\\"\")\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/ProxyConfigException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport java.net.ProtocolException\n\nclass ProxyConfigException : ProtocolException(\"Wrong proxy configuration\")\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/SyncApiException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nclass SyncApiException(\n\tmessage: String,\n\tval code: Int,\n) : RuntimeException(message)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/UnsupportedFileException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport java.io.IOException\n\nclass UnsupportedFileException(message: String? = null) : IOException(message)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/UnsupportedSourceException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nclass UnsupportedSourceException(\n\tmessage: String?,\n\tval manga: Manga?,\n) : IllegalArgumentException(message)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/WrapperIOException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nimport okio.IOException\n\nclass WrapperIOException(override val cause: Exception) : IOException(cause)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/WrongPasswordException.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions\n\nclass WrongPasswordException : IllegalArgumentException()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/CaptchaHandler.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions.resolve\n\nimport android.Manifest\nimport android.app.Notification\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Build\nimport android.provider.Settings\nimport androidx.annotation.CheckResult\nimport androidx.annotation.RequiresPermission\nimport androidx.collection.MutableScatterMap\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.net.toUri\nimport androidx.lifecycle.coroutineScope\nimport coil3.EventListener\nimport coil3.Extras\nimport coil3.ImageLoader\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.allowConversionToBitmap\nimport coil3.request.allowHardware\nimport coil3.request.lifecycle\nimport coil3.size.Scale\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareException\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.network.webview.WebViewExecutor\nimport org.koitharu.kotatsu.core.parser.favicon.faviconUri\nimport org.koitharu.kotatsu.core.prefs.SourceSettings\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.getNotificationIconSize\nimport org.koitharu.kotatsu.core.util.ext.goAsync\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.core.util.ext.toBitmapOrNull\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\nimport org.koitharu.kotatsu.parsers.util.mapToArray\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport javax.inject.Singleton\n\n@Singleton\nclass CaptchaHandler @Inject constructor(\n\t@LocalizedAppContext private val context: Context,\n\tprivate val databaseProvider: Provider<MangaDatabase>,\n\tprivate val coilProvider: Provider<ImageLoader>,\n\tprivate val webViewExecutor: WebViewExecutor,\n) : EventListener() {\n\n\tprivate val exceptionMap = MutableScatterMap<MangaSource, CloudFlareProtectedException>()\n\tprivate val mutex = Mutex()\n\n\t@CheckResult\n\tsuspend fun handle(exception: CloudFlareException): Boolean = handleException(exception.source, exception, true)\n\n\tsuspend fun discard(source: MangaSource) {\n\t\thandleException(source, null, true)\n\t}\n\n\toverride fun onError(request: ImageRequest, result: ErrorResult) {\n\t\tsuper.onError(request, result)\n\t\tval e = result.throwable\n\t\tif (e is CloudFlareException) {\n\t\t\tval scope = request.lifecycle?.coroutineScope ?: processLifecycleScope\n\t\t\tscope.launch {\n\t\t\t\tif (\n\t\t\t\t\thandleException(\n\t\t\t\t\t\tsource = e.source,\n\t\t\t\t\t\texception = e,\n\t\t\t\t\t\tnotify = request.extras[suppressCaptchaKey] != true,\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tcoilProvider.get().enqueue(request) // TODO check if ok\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun handleException(\n\t\tsource: MangaSource,\n\t\texception: CloudFlareException?,\n\t\tnotify: Boolean,\n\t): Boolean = withContext(Dispatchers.Default) {\n\t\tif (source == UnknownMangaSource) {\n\t\t\treturn@withContext false\n\t\t}\n\t\tif (exception != null && webViewExecutor.tryResolveCaptcha(exception, RESOLVE_TIMEOUT)) {\n\t\t\treturn@withContext true\n\t\t}\n\t\tmutex.withLock {\n\t\t\tvar removedException: CloudFlareProtectedException? = null\n\t\t\tif (exception is CloudFlareProtectedException) {\n\t\t\t\texceptionMap[source] = exception\n\t\t\t} else {\n\t\t\t\tremovedException = exceptionMap.remove(source)\n\t\t\t}\n\t\t\tval dao = databaseProvider.get().getSourcesDao()\n\t\t\tdao.setCfState(source.name, exception?.state ?: CloudFlareHelper.PROTECTION_NOT_DETECTED)\n\n\t\t\tif (notify && context.checkNotificationPermission(CHANNEL_ID)) {\n\t\t\t\tval exceptions = dao.findAllCaptchaRequired().mapNotNull {\n\t\t\t\t\tit.source.toMangaSourceOrNull()\n\t\t\t\t}.filterNot {\n\t\t\t\t\tSourceSettings(context, it).isCaptchaNotificationsDisabled\n\t\t\t\t}.mapNotNull {\n\t\t\t\t\texceptionMap[it]\n\t\t\t\t}\n\t\t\t\tif (removedException != null) {\n\t\t\t\t\tNotificationManagerCompat.from(context).cancel(TAG, removedException.source.hashCode())\n\t\t\t\t}\n\t\t\t\tnotify(exceptions)\n\t\t\t}\n\t\t}\n\t\tfalse\n\t}\n\n\t@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n\tprivate suspend fun notify(exceptions: List<CloudFlareProtectedException>) {\n\t\tval manager = NotificationManagerCompat.from(context)\n\t\tval channel = NotificationChannelCompat.Builder(\n\t\t\tCHANNEL_ID,\n\t\t\tNotificationManagerCompat.IMPORTANCE_LOW,\n\t\t)\n\t\t\t.setName(context.getString(R.string.captcha_required))\n\t\t\t.setShowBadge(true)\n\t\t\t.setVibrationEnabled(false)\n\t\t\t.setSound(null, null)\n\t\t\t.setLightsEnabled(false)\n\t\t\t.build()\n\t\tmanager.createNotificationChannel(channel)\n\n\t\tcoroutineScope {\n\t\t\texceptions.map {\n\t\t\t\tasync { it to buildNotification(it) }\n\t\t\t}.awaitAll()\n\t\t}.forEach { (exception, notification) ->\n\t\t\tmanager.notify(TAG, exception.source.hashCode(), notification)\n\t\t}\n\t\tif (exceptions.size > 1) {\n\t\t\tval groupNotification = NotificationCompat.Builder(context, CHANNEL_ID)\n\t\t\t\t.setGroupSummary(true)\n\t\t\t\t.setContentTitle(context.getString(R.string.captcha_required))\n\t\t\t\t.setPriority(NotificationCompat.PRIORITY_LOW)\n\t\t\t\t.setDefaults(0)\n\t\t\t\t.setOnlyAlertOnce(true)\n\t\t\t\t.setSmallIcon(R.drawable.ic_bot)\n\t\t\t\t.setGroup(GROUP_CAPTCHA)\n\t\t\t\t.setContentIntent(\n\t\t\t\t\tPendingIntentCompat.getActivities(\n\t\t\t\t\t\tcontext, GROUP_NOTIFICATION_ID,\n\t\t\t\t\t\texceptions.mapToArray { e ->\n\t\t\t\t\t\t\tAppRouter.cloudFlareResolveIntent(context, e)\n\t\t\t\t\t\t},\n\t\t\t\t\t\t0, false,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\t.setContentText(\n\t\t\t\t\tcontext.getString(\n\t\t\t\t\t\tR.string.captcha_required_summary, context.getString(R.string.app_name),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\t.setVisibility(\n\t\t\t\t\tif (exceptions.any { it.source.isNsfw() }) {\n\t\t\t\t\t\tNotificationCompat.VISIBILITY_SECRET\n\t\t\t\t\t} else {\n\t\t\t\t\t\tNotificationCompat.VISIBILITY_PUBLIC\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\tmanager.notify(TAG, GROUP_NOTIFICATION_ID, groupNotification.build())\n\t\t} else {\n\t\t\tmanager.cancel(TAG, GROUP_NOTIFICATION_ID)\n\t\t}\n\t}\n\n\tprivate suspend fun buildNotification(exception: CloudFlareProtectedException): Notification {\n\t\tval intent = AppRouter.cloudFlareResolveIntent(context, exception)\n\t\tval discardIntent = Intent(ACTION_DISCARD)\n\t\t\t.putExtra(AppRouter.KEY_SOURCE, exception.source.name)\n\t\t\t.setData(\"source://${exception.source.name}\".toUri())\n\t\tval notification = NotificationCompat.Builder(context, CHANNEL_ID)\n\t\t\t.setContentTitle(context.getString(R.string.captcha_required))\n\t\t\t.setPriority(NotificationCompat.PRIORITY_LOW)\n\t\t\t.setDefaults(0)\n\t\t\t.setSmallIcon(R.drawable.ic_bot)\n\t\t\t.setGroup(GROUP_CAPTCHA)\n\t\t\t.setOnlyAlertOnce(true)\n\t\t\t.setAutoCancel(true)\n\t\t\t.setDeleteIntent(PendingIntentCompat.getBroadcast(context, 0, discardIntent, 0, false))\n\t\t\t.setLargeIcon(getFavicon(exception.source))\n\t\t\t.setVisibility(\n\t\t\t\tif (exception.source.isNsfw()) {\n\t\t\t\t\tNotificationCompat.VISIBILITY_SECRET\n\t\t\t\t} else {\n\t\t\t\t\tNotificationCompat.VISIBILITY_PUBLIC\n\t\t\t\t},\n\t\t\t)\n\t\t\t.setContentText(\n\t\t\t\tcontext.getString(\n\t\t\t\t\tR.string.captcha_required_summary,\n\t\t\t\t\texception.source.getTitle(context),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.setContentIntent(PendingIntentCompat.getActivity(context, 0, intent, 0, false))\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\tval actionIntent = PendingIntentCompat.getActivity(\n\t\t\t\tcontext, SETTINGS_ACTION_CODE,\n\t\t\t\tIntent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)\n\t\t\t\t\t.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)\n\t\t\t\t\t.putExtra(Settings.EXTRA_CHANNEL_ID, CHANNEL_ID),\n\t\t\t\t0, false,\n\t\t\t)\n\t\t\tnotification.addAction(\n\t\t\t\tR.drawable.ic_settings,\n\t\t\t\tcontext.getString(R.string.notifications_settings),\n\t\t\t\tactionIntent,\n\t\t\t)\n\t\t}\n\t\treturn notification.build()\n\t}\n\n\tprivate fun String.toMangaSourceOrNull() = MangaSource(this).takeUnless { it == UnknownMangaSource }\n\n\tprivate suspend fun getFavicon(source: MangaSource) = runCatchingCancellable {\n\t\tcoilProvider.get().execute(\n\t\t\tImageRequest.Builder(context)\n\t\t\t\t.data(source.faviconUri())\n\t\t\t\t.allowHardware(false)\n\t\t\t\t.allowConversionToBitmap(true)\n\t\t\t\t.suppressCaptchaErrors()\n\t\t\t\t.mangaSourceExtra(source)\n\t\t\t\t.size(context.resources.getNotificationIconSize())\n\t\t\t\t.scale(Scale.FILL)\n\t\t\t\t.build(),\n\t\t).toBitmapOrNull()\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n\n\t@AndroidEntryPoint\n\tclass DiscardReceiver : BroadcastReceiver() {\n\n\t\t@Inject\n\t\tlateinit var captchaHandler: CaptchaHandler\n\n\t\toverride fun onReceive(context: Context?, intent: Intent?) {\n\t\t\tval sourceName = intent?.getStringExtra(AppRouter.KEY_SOURCE) ?: return\n\t\t\tgoAsync {\n\t\t\t\tcaptchaHandler.handleException(MangaSource(sourceName), exception = null, notify = false)\n\t\t\t}\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tfun ImageRequest.Builder.suppressCaptchaErrors() = apply {\n\t\t\textras[suppressCaptchaKey] = true\n\t\t}\n\n\t\tprivate val suppressCaptchaKey = Extras.Key(false)\n\n\t\tprivate const val CHANNEL_ID = \"captcha\"\n\t\tprivate const val TAG = CHANNEL_ID\n\t\tprivate const val GROUP_CAPTCHA = \"org.koitharu.kotatsu.CAPTCHA\"\n\t\tprivate const val GROUP_NOTIFICATION_ID = 34\n\t\tprivate const val SETTINGS_ACTION_CODE = 3\n\t\tprivate const val ACTION_DISCARD = \"org.koitharu.kotatsu.CAPTCHA_DISCARD\"\n\t\tprivate const val RESOLVE_TIMEOUT = 20_000L\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions.resolve\n\nimport android.content.DialogInterface\nimport android.view.View\nimport androidx.core.util.Consumer\nimport androidx.fragment.app.Fragment\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.isSerializable\nimport org.koitharu.kotatsu.parsers.exception.ParseException\n\nclass DialogErrorObserver(\n\thost: View,\n\tfragment: Fragment?,\n\tresolver: ExceptionResolver?,\n\tprivate val onResolved: Consumer<Boolean>?,\n) : ErrorObserver(host, fragment, resolver, onResolved) {\n\n\tconstructor(\n\t\thost: View,\n\t\tfragment: Fragment?,\n\t) : this(host, fragment, null, null)\n\n\toverride suspend fun emit(value: Throwable) {\n\t\tval listener = DialogListener(value)\n\t\tval dialogBuilder = MaterialAlertDialogBuilder(activity ?: host.context)\n\t\t\t.setMessage(value.getDisplayMessage(host.context.resources))\n\t\t\t.setNegativeButton(R.string.close, listener)\n\t\t\t.setOnCancelListener(listener)\n\t\tif (canResolve(value)) {\n\t\t\tdialogBuilder.setPositiveButton(ExceptionResolver.getResolveStringId(value), listener)\n\t\t} else if (value is ParseException) {\n\t\t\tval router = router()\n\t\t\tif (router != null && value.isSerializable()) {\n\t\t\t\tdialogBuilder.setPositiveButton(R.string.details) { _, _ ->\n\t\t\t\t\trouter.showErrorDialog(value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tval dialog = dialogBuilder.create()\n\t\tif (activity != null) {\n\t\t\tdialog.setOwnerActivity(activity)\n\t\t}\n\t\tdialog.show()\n\t}\n\n\tprivate inner class DialogListener(\n\t\tprivate val error: Throwable,\n\t) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener {\n\n\t\toverride fun onClick(dialog: DialogInterface?, which: Int) {\n\t\t\twhen (which) {\n\t\t\t\tDialogInterface.BUTTON_NEGATIVE -> onResolved?.accept(false)\n\t\t\t\tDialogInterface.BUTTON_POSITIVE -> resolve(error)\n\t\t\t}\n\t\t}\n\n\t\toverride fun onCancel(dialog: DialogInterface?) {\n\t\t\tonResolved?.accept(false)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ErrorObserver.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions.resolve\n\nimport android.view.View\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.util.Consumer\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.LifecycleCoroutineScope\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.coroutineScope\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.util.ext.findActivity\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\n\nabstract class ErrorObserver(\n\tprotected val host: View,\n\tprotected val fragment: Fragment?,\n\tprivate val resolver: ExceptionResolver?,\n\tprivate val onResolved: Consumer<Boolean>?,\n) : FlowCollector<Throwable> {\n\n\tprotected open val activity = host.context.findActivity()\n\n\tprivate val lifecycleScope: LifecycleCoroutineScope\n\t\tget() = checkNotNull(fragment?.viewLifecycleScope ?: (activity as? LifecycleOwner)?.lifecycle?.coroutineScope)\n\n\tprotected val fragmentManager: FragmentManager?\n\t\tget() = fragment?.childFragmentManager ?: (activity as? AppCompatActivity)?.supportFragmentManager\n\n\tprotected fun canResolve(error: Throwable): Boolean {\n\t\treturn resolver != null && ExceptionResolver.canResolve(error)\n\t}\n\n\tprotected fun router() = fragment?.router ?: (activity as? FragmentActivity)?.router\n\n\tprivate fun isAlive(): Boolean {\n\t\treturn when {\n\t\t\tfragment != null -> fragment.view != null\n\t\t\tactivity != null -> activity?.isDestroyed == false\n\t\t\telse -> true\n\t\t}\n\t}\n\n\tprotected fun resolve(error: Throwable) {\n\t\tif (isAlive()) {\n\t\t\tlifecycleScope.launch {\n\t\t\t\tval isResolved = resolver?.resolve(error) == true\n\t\t\t\tif (isActive) {\n\t\t\t\t\tonResolved?.accept(isResolved)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions.resolve\n\nimport android.content.Context\nimport android.widget.Toast\nimport androidx.activity.result.ActivityResultCaller\nimport androidx.annotation.StringRes\nimport androidx.collection.MutableScatterMap\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport kotlinx.coroutines.async\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.browser.BrowserActivity\nimport org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.exceptions.EmptyMangaException\nimport org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException\nimport org.koitharu.kotatsu.core.exceptions.ProxyConfigException\nimport org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.util.ext.isHttpUrl\nimport org.koitharu.kotatsu.core.util.ext.restartApplication\nimport org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason\nimport org.koitharu.kotatsu.parsers.exception.AuthRequiredException\nimport org.koitharu.kotatsu.parsers.exception.NotFoundException\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException\nimport org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper\nimport org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity\nimport java.security.cert.CertPathValidatorException\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport javax.net.ssl.SSLException\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\nclass ExceptionResolver private constructor(\n    private val host: Host,\n    private val settings: AppSettings,\n    private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>,\n) {\n    private val continuations = MutableScatterMap<String, Continuation<Boolean>>(1)\n\n    private val browserActionContract = host.registerForActivityResult(BrowserActivity.Contract()) {\n        handleActivityResult(BrowserActivity.TAG, true)\n    }\n    private val sourceAuthContract = host.registerForActivityResult(SourceAuthActivity.Contract()) {\n        handleActivityResult(SourceAuthActivity.TAG, it)\n    }\n    private val cloudflareContract = host.registerForActivityResult(CloudFlareActivity.Contract()) {\n        handleActivityResult(CloudFlareActivity.TAG, it)\n    }\n\n    fun showErrorDetails(e: Throwable, url: String? = null) {\n        host.router.showErrorDialog(e, url)\n    }\n\n    suspend fun resolve(e: Throwable): Boolean = host.lifecycleScope.async {\n        when (e) {\n            is CloudFlareProtectedException -> resolveCF(e)\n            is AuthRequiredException -> resolveAuthException(e.source)\n            is SSLException,\n            is CertPathValidatorException -> {\n                showSslErrorDialog()\n                false\n            }\n\n            is InteractiveActionRequiredException -> resolveBrowserAction(e)\n\n            is ProxyConfigException -> {\n                host.router.openProxySettings()\n                false\n            }\n\n            is NotFoundException -> {\n                openInBrowser(e.url)\n                false\n            }\n\n            is EmptyMangaException -> {\n                when (e.reason) {\n                    EmptyMangaReason.NO_CHAPTERS -> openAlternatives(e.manga)\n                    EmptyMangaReason.LOADING_ERROR -> Unit\n                    EmptyMangaReason.RESTRICTED -> host.router.openBrowser(e.manga)\n                    else -> Unit\n                }\n                false\n            }\n\n            is UnsupportedSourceException -> {\n                e.manga?.let { openAlternatives(it) }\n                false\n            }\n\n            is ScrobblerAuthRequiredException -> {\n                val authHelper = scrobblerAuthHelperProvider.get()\n                if (authHelper.isAuthorized(e.scrobbler)) {\n                    true\n                } else {\n                    host.withContext {\n                        authHelper.startAuth(this, e.scrobbler).onFailure(::showErrorDetails)\n                    }\n                    false\n                }\n            }\n\n            else -> false\n        }\n    }.await()\n\n    private suspend fun resolveBrowserAction(\n        e: InteractiveActionRequiredException\n    ): Boolean = suspendCoroutine { cont ->\n        continuations[BrowserActivity.TAG] = cont\n        browserActionContract.launch(e)\n    }\n\n    private suspend fun resolveCF(e: CloudFlareProtectedException): Boolean = suspendCoroutine { cont ->\n        continuations[CloudFlareActivity.TAG] = cont\n        cloudflareContract.launch(e)\n    }\n\n    private suspend fun resolveAuthException(source: MangaSource): Boolean = suspendCoroutine { cont ->\n        continuations[SourceAuthActivity.TAG] = cont\n        sourceAuthContract.launch(source)\n    }\n\n    private fun openInBrowser(url: String) {\n        host.router.openBrowser(url, null, null)\n    }\n\n    private fun openAlternatives(manga: Manga) {\n        host.router.openAlternatives(manga)\n    }\n\n    private fun handleActivityResult(tag: String, result: Boolean) {\n        continuations.remove(tag)?.resume(result)\n    }\n\n    private fun showSslErrorDialog() {\n        val ctx = host.context ?: return\n        if (settings.isSSLBypassEnabled) {\n            Toast.makeText(ctx, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()\n            return\n        }\n        buildAlertDialog(ctx) {\n            setTitle(R.string.ignore_ssl_errors)\n            setMessage(R.string.ignore_ssl_errors_summary)\n            setPositiveButton(R.string.apply) { _, _ ->\n                settings.isSSLBypassEnabled = true\n                Toast.makeText(ctx, R.string.settings_apply_restart_required, Toast.LENGTH_LONG).show()\n                ctx.restartApplication()\n            }\n            setNegativeButton(android.R.string.cancel, null)\n        }.show()\n    }\n\n    class Factory @Inject constructor(\n        private val settings: AppSettings,\n        private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>,\n    ) {\n\n        fun create(fragment: Fragment) = ExceptionResolver(\n            host = Host.FragmentHost(fragment),\n            settings = settings,\n            scrobblerAuthHelperProvider = scrobblerAuthHelperProvider,\n        )\n\n        fun create(activity: FragmentActivity) = ExceptionResolver(\n            host = Host.ActivityHost(activity),\n            settings = settings,\n            scrobblerAuthHelperProvider = scrobblerAuthHelperProvider,\n        )\n    }\n\n    private sealed interface Host : ActivityResultCaller, LifecycleOwner {\n\n        val context: Context?\n\n        val router: AppRouter\n\n        val fragmentManager: FragmentManager\n\n        inline fun withContext(block: Context.() -> Unit) {\n            context?.apply(block)\n        }\n\n        class ActivityHost(val activity: FragmentActivity) : Host,\n            ActivityResultCaller by activity,\n            LifecycleOwner by activity {\n\n            override val context: Context\n                get() = activity\n\n            override val router: AppRouter\n                get() = activity.router\n\n            override val fragmentManager: FragmentManager\n                get() = activity.supportFragmentManager\n        }\n\n        class FragmentHost(val fragment: Fragment) : Host,\n            ActivityResultCaller by fragment {\n\n            override val context: Context?\n                get() = fragment.context\n\n            override val router: AppRouter\n                get() = fragment.router\n\n            override val fragmentManager: FragmentManager\n                get() = fragment.childFragmentManager\n\n            override val lifecycle: Lifecycle\n                get() = fragment.viewLifecycleOwner.lifecycle\n        }\n    }\n\n    companion object {\n\n        @StringRes\n        fun getResolveStringId(e: Throwable) = when (e) {\n            is CloudFlareProtectedException -> R.string.captcha_solve\n            is ScrobblerAuthRequiredException,\n            is AuthRequiredException -> R.string.sign_in\n\n            is NotFoundException -> if (e.url.isHttpUrl()) R.string.open_in_browser else 0\n            is UnsupportedSourceException -> if (e.manga != null) R.string.alternatives else 0\n            is SSLException,\n            is CertPathValidatorException -> R.string.fix\n\n            is ProxyConfigException -> R.string.settings\n\n            is InteractiveActionRequiredException -> R.string._continue\n\n            is EmptyMangaException -> when (e.reason) {\n                EmptyMangaReason.RESTRICTED -> if (e.manga.publicUrl.isHttpUrl()) R.string.open_in_browser else 0\n                EmptyMangaReason.NO_CHAPTERS -> R.string.alternatives\n                else -> 0\n            }\n\n            else -> 0\n        }\n\n        fun canResolve(e: Throwable) = getResolveStringId(e) != 0\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions.resolve\n\nimport android.view.View\nimport androidx.core.util.Consumer\nimport androidx.fragment.app.Fragment\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.isSerializable\nimport org.koitharu.kotatsu.main.ui.owners.BottomNavOwner\nimport org.koitharu.kotatsu.main.ui.owners.BottomSheetOwner\nimport org.koitharu.kotatsu.parsers.exception.ParseException\n\nclass SnackbarErrorObserver(\n\thost: View,\n\tfragment: Fragment?,\n\tresolver: ExceptionResolver?,\n\tonResolved: Consumer<Boolean>?,\n) : ErrorObserver(host, fragment, resolver, onResolved) {\n\n\tconstructor(\n\t\thost: View,\n\t\tfragment: Fragment?,\n\t) : this(host, fragment, null, null)\n\n\toverride suspend fun emit(value: Throwable) {\n\t\tval snackbar = Snackbar.make(host, value.getDisplayMessage(host.context.resources), Snackbar.LENGTH_SHORT)\n\t\twhen (activity) {\n\t\t\tis BottomNavOwner -> snackbar.anchorView = activity.bottomNav\n\t\t\tis BottomSheetOwner -> snackbar.anchorView = activity.bottomSheet\n\t\t}\n\t\tif (canResolve(value)) {\n\t\t\tsnackbar.setAction(ExceptionResolver.getResolveStringId(value)) {\n\t\t\t\tresolve(value)\n\t\t\t}\n\t\t} else if (value is ParseException) {\n\t\t\tval router = router()\n\t\t\tif (router != null && value.isSerializable()) {\n\t\t\t\tsnackbar.setAction(R.string.details) {\n\t\t\t\t\trouter.showErrorDialog(value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsnackbar.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ToastErrorObserver.kt",
    "content": "package org.koitharu.kotatsu.core.exceptions.resolve\n\nimport android.view.View\nimport android.widget.Toast\nimport androidx.fragment.app.Fragment\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\n\nclass ToastErrorObserver(\n\thost: View,\n\tfragment: Fragment?,\n) : ErrorObserver(host, fragment, null, null) {\n\n\toverride suspend fun emit(value: Throwable) {\n\t\tval toast = Toast.makeText(host.context, value.getDisplayMessage(host.context.resources), Toast.LENGTH_SHORT)\n\t\ttoast.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/fs/FileSequence.kt",
    "content": "package org.koitharu.kotatsu.core.fs\n\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport org.koitharu.kotatsu.core.util.CloseableSequence\nimport org.koitharu.kotatsu.core.util.iterator.MappingIterator\nimport java.io.File\nimport java.nio.file.Files\nimport java.nio.file.Path\n\nsealed interface FileSequence : CloseableSequence<File> {\n\n\t@RequiresApi(Build.VERSION_CODES.O)\n\tclass StreamImpl(dir: File) : FileSequence {\n\n\t\tprivate val stream = Files.newDirectoryStream(dir.toPath())\n\n\t\toverride fun iterator(): Iterator<File> = MappingIterator(stream.iterator(), Path::toFile)\n\n\t\toverride fun close() = stream.close()\n\t}\n\n\tclass ListImpl(dir: File) : FileSequence {\n\n\t\tprivate val list = dir.listFiles().orEmpty()\n\n\t\toverride fun iterator(): Iterator<File> = list.iterator()\n\n\t\toverride fun close() = Unit\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/github/AppUpdateRepository.kt",
    "content": "package org.koitharu.kotatsu.core.github\n\nimport android.content.Context\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.withContext\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.os.AppValidator\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.asArrayList\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull\nimport org.koitharu.kotatsu.parsers.util.parseJsonArray\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\nprivate const val CONTENT_TYPE_APK = \"application/vnd.android.package-archive\"\nprivate const val BUILD_TYPE_RELEASE = \"release\"\n\n@Singleton\nclass AppUpdateRepository @Inject constructor(\n\tprivate val appValidator: AppValidator,\n\tprivate val settings: AppSettings,\n\t@BaseHttpClient private val okHttp: OkHttpClient,\n\t@ApplicationContext context: Context,\n) {\n\n\tprivate val availableUpdate = MutableStateFlow<AppVersion?>(null)\n\tprivate val releasesUrl = buildString {\n\t\tappend(\"https://api.github.com/repos/\")\n\t\tappend(context.getString(R.string.github_updates_repo))\n\t\tappend(\"/releases?page=1&per_page=10\")\n\t}\n\n\tval isUpdateAvailable: Boolean\n\t\tget() = availableUpdate.value != null\n\n\tfun observeAvailableUpdate() = availableUpdate.asStateFlow()\n\n\tsuspend fun getAvailableVersions(): List<AppVersion> {\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(releasesUrl)\n\t\tval jsonArray = okHttp.newCall(request.build()).await().parseJsonArray()\n\t\treturn jsonArray.mapJSONNotNull { json ->\n\t\t\tval asset = json.optJSONArray(\"assets\")?.find { jo ->\n\t\t\t\tjo.optString(\"content_type\") == CONTENT_TYPE_APK\n\t\t\t} ?: return@mapJSONNotNull null\n\t\t\tAppVersion(\n\t\t\t\tid = json.getLong(\"id\"),\n\t\t\t\turl = json.getString(\"html_url\"),\n\t\t\t\tname = json.getString(\"name\").removePrefix(\"v\"),\n\t\t\t\tapkSize = asset.getLong(\"size\"),\n\t\t\t\tapkUrl = asset.getString(\"browser_download_url\"),\n\t\t\t\tdescription = json.getString(\"body\"),\n\t\t\t)\n\t\t}\n\t}\n\n\tsuspend fun fetchUpdate(): AppVersion? = withContext(Dispatchers.Default) {\n\t\tif (!isUpdateSupported()) {\n\t\t\treturn@withContext null\n\t\t}\n\t\trunCatchingCancellable {\n\t\t\tval currentVersion = VersionId(BuildConfig.VERSION_NAME)\n\t\t\tval available = getAvailableVersions().asArrayList()\n\t\t\tavailable.sortBy { it.versionId }\n\t\t\tif (currentVersion.isStable && !settings.isUnstableUpdatesAllowed) {\n\t\t\t\tavailable.retainAll { it.versionId.isStable }\n\t\t\t}\n\t\t\tavailable.maxByOrNull { it.versionId }\n\t\t\t\t?.takeIf { it.versionId > currentVersion }\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}.onSuccess {\n\t\t\tavailableUpdate.value = it\n\t\t}.getOrNull()\n\t}\n\n\t@Suppress(\"KotlinConstantConditions\")\n\tsuspend fun isUpdateSupported(): Boolean {\n\t\treturn BuildConfig.BUILD_TYPE != BUILD_TYPE_RELEASE || appValidator.isOriginalApp.getOrNull() == true\n\t}\n\n\tprivate inline fun JSONArray.find(predicate: (JSONObject) -> Boolean): JSONObject? {\n\t\tval size = length()\n\t\tfor (i in 0 until size) {\n\t\t\tval jo = getJSONObject(i)\n\t\t\tif (predicate(jo)) {\n\t\t\t\treturn jo\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/github/AppVersion.kt",
    "content": "package org.koitharu.kotatsu.core.github\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.IgnoredOnParcel\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class AppVersion(\n\tval id: Long,\n\tval name: String,\n\tval url: String,\n\tval apkSize: Long,\n\tval apkUrl: String,\n\tval description: String,\n) : Parcelable {\n\n\t@IgnoredOnParcel\n\tval versionId = VersionId(name)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/github/VersionId.kt",
    "content": "package org.koitharu.kotatsu.core.github\n\nimport org.koitharu.kotatsu.parsers.util.digits\nimport java.util.Locale\n\ndata class VersionId(\n\tval major: Int,\n\tval minor: Int,\n\tval build: Int,\n\tval variantType: String,\n\tval variantNumber: Int,\n) : Comparable<VersionId> {\n\n\toverride fun compareTo(other: VersionId): Int {\n\t\tvar diff = major.compareTo(other.major)\n\t\tif (diff != 0) {\n\t\t\treturn diff\n\t\t}\n\t\tdiff = minor.compareTo(other.minor)\n\t\tif (diff != 0) {\n\t\t\treturn diff\n\t\t}\n\t\tdiff = build.compareTo(other.build)\n\t\tif (diff != 0) {\n\t\t\treturn diff\n\t\t}\n\t\tdiff = variantWeight(variantType).compareTo(variantWeight(other.variantType))\n\t\tif (diff != 0) {\n\t\t\treturn diff\n\t\t}\n\t\treturn variantNumber.compareTo(other.variantNumber)\n\t}\n\n\tprivate fun variantWeight(variantType: String) = when (variantType.lowercase(Locale.ROOT)) {\n\t\t\"a\", \"alpha\" -> 1\n\t\t\"b\", \"beta\" -> 2\n\t\t\"rc\" -> 4\n\t\t\"\" -> 8\n\t\telse -> 0\n\t}\n}\n\nval VersionId.isStable: Boolean\n\tget() = variantType.isEmpty()\n\nfun VersionId(versionName: String): VersionId {\n\tif (versionName.startsWith('n', ignoreCase = true)) {\n\t\t// Nightly build\n\t\treturn VersionId(\n\t\t\tmajor = 0,\n\t\t\tminor = 0,\n\t\t\tbuild = versionName.digits().toIntOrNull() ?: 0,\n\t\t\tvariantType = \"n\",\n\t\t\tvariantNumber = 0,\n\t\t)\n\t}\n\tval parts = versionName.substringBeforeLast('-').split('.')\n\tval variant = versionName.substringAfterLast('-', \"\")\n\treturn VersionId(\n\t\tmajor = parts.getOrNull(0)?.toIntOrNull() ?: 0,\n\t\tminor = parts.getOrNull(1)?.toIntOrNull() ?: 0,\n\t\tbuild = parts.getOrNull(2)?.toIntOrNull() ?: 0,\n\t\tvariantType = variant.filter(Char::isLetter),\n\t\tvariantNumber = variant.filter(Char::isDigit).toIntOrNull() ?: 0,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/image/AvifImageDecoder.kt",
    "content": "package org.koitharu.kotatsu.core.image\n\nimport android.graphics.Bitmap\nimport androidx.core.graphics.createBitmap\nimport androidx.core.graphics.scale\nimport coil3.ImageLoader\nimport coil3.asImage\nimport coil3.decode.DecodeResult\nimport coil3.decode.DecodeUtils\nimport coil3.decode.Decoder\nimport coil3.decode.ImageSource\nimport coil3.fetch.SourceFetchResult\nimport coil3.request.Options\nimport coil3.request.maxBitmapSize\nimport coil3.util.component1\nimport coil3.util.component2\nimport com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException\nimport kotlinx.coroutines.runInterruptible\nimport org.aomedia.avif.android.AvifDecoder\nimport org.koitharu.kotatsu.core.util.ext.readByteBuffer\n\nclass AvifImageDecoder(\n\tprivate val source: ImageSource,\n\tprivate val options: Options,\n) : Decoder {\n\n\toverride suspend fun decode(): DecodeResult = runInterruptible {\n\t\tval bytes = source.source().readByteBuffer()\n\t\tval decoder = AvifDecoder.create(bytes) ?: throw ImageDecodeException(\n\t\t\turi = source.fileOrNull()?.toString(),\n\t\t\tformat = \"avif\",\n\t\t\tmessage = \"Requested to decode byte buffer which cannot be handled by AvifDecoder\",\n\t\t)\n\t\ttry {\n\t\t\tval config = if (decoder.depth == 8 || decoder.alphaPresent) {\n\t\t\t\tBitmap.Config.ARGB_8888\n\t\t\t} else {\n\t\t\t\tBitmap.Config.RGB_565\n\t\t\t}\n\t\t\tval bitmap = createBitmap(decoder.width, decoder.height, config)\n\t\t\tval result = decoder.nextFrame(bitmap)\n\t\t\tif (result != 0) {\n\t\t\t\tbitmap.recycle()\n\t\t\t\tthrow ImageDecodeException(\n\t\t\t\t\turi = source.fileOrNull()?.toString(),\n\t\t\t\t\tformat = \"avif\",\n\t\t\t\t\tmessage = AvifDecoder.resultToString(result),\n\t\t\t\t)\n\t\t\t}\n\t\t\t// downscaling\n\t\t\tval (dstWidth, dstHeight) = DecodeUtils.computeDstSize(\n\t\t\t\tsrcWidth = bitmap.width,\n\t\t\t\tsrcHeight = bitmap.height,\n\t\t\t\ttargetSize = options.size,\n\t\t\t\tscale = options.scale,\n\t\t\t\tmaxSize = options.maxBitmapSize,\n\t\t\t)\n\t\t\tif (dstWidth < bitmap.width || dstHeight < bitmap.height) {\n\t\t\t\tval scaled = bitmap.scale(dstWidth, dstHeight)\n\t\t\t\tbitmap.recycle()\n\t\t\t\tDecodeResult(\n\t\t\t\t\timage = scaled.asImage(),\n\t\t\t\t\tisSampled = true,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tDecodeResult(\n\t\t\t\t\timage = bitmap.asImage(),\n\t\t\t\t\tisSampled = false,\n\t\t\t\t)\n\t\t\t}\n\t\t} finally {\n\t\t\tdecoder.release()\n\t\t}\n\t}\n\n\tclass Factory : Decoder.Factory {\n\n\t\toverride fun create(\n\t\t\tresult: SourceFetchResult,\n\t\t\toptions: Options,\n\t\t\timageLoader: ImageLoader\n\t\t): Decoder? = if (isApplicable(result)) {\n\t\t\tAvifImageDecoder(result.source, options)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\n\t\toverride fun equals(other: Any?) = other is Factory\n\n\t\toverride fun hashCode() = javaClass.hashCode()\n\n\t\tprivate fun isApplicable(result: SourceFetchResult): Boolean {\n\t\t\treturn result.mimeType == \"image/avif\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt",
    "content": "package org.koitharu.kotatsu.core.image\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.graphics.BitmapRegionDecoder\nimport android.graphics.ImageDecoder\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport androidx.core.graphics.createBitmap\nimport com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException\nimport okio.IOException\nimport okio.buffer\nimport okio.source\nimport org.aomedia.avif.android.AvifDecoder\nimport org.aomedia.avif.android.AvifDecoder.Info\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.MimeType\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.readByteBuffer\nimport org.koitharu.kotatsu.core.util.ext.toByteBuffer\nimport org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\nimport java.io.InputStream\nimport java.nio.ByteBuffer\n\nobject BitmapDecoderCompat {\n\n\tprivate const val FORMAT_AVIF = \"avif\"\n\n\t@Blocking\n\tfun decode(file: File): Bitmap = when (val format = probeMimeType(file)?.subtype) {\n\t\tFORMAT_AVIF -> file.source().buffer().use { decodeAvif(it.readByteBuffer()) }\n\t\telse -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n\t\t\tImageDecoder.decodeBitmap(ImageDecoder.createSource(file))\n\t\t} else {\n\t\t\tcheckBitmapNotNull(BitmapFactory.decodeFile(file.absolutePath), format)\n\t\t}\n\t}\n\n\t@Blocking\n\tfun decode(stream: InputStream, type: MimeType?, isMutable: Boolean = false): Bitmap {\n\t\tval format = type?.subtype\n\t\tif (format == FORMAT_AVIF) {\n\t\t\treturn decodeAvif(stream.toByteBuffer())\n\t\t}\n\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n\t\t\tval opts = BitmapFactory.Options()\n\t\t\topts.inMutable = isMutable\n\t\t\treturn checkBitmapNotNull(BitmapFactory.decodeStream(stream, null, opts), format)\n\t\t}\n\t\tval byteBuffer = stream.toByteBuffer()\n\t\treturn if (AvifDecoder.isAvifImage(byteBuffer)) {\n\t\t\tdecodeAvif(byteBuffer)\n\t\t} else {\n\t\t\tImageDecoder.decodeBitmap(ImageDecoder.createSource(byteBuffer), DecoderConfigListener(isMutable))\n\t\t}\n\t}\n\n\t@Blocking\n\tfun createRegionDecoder(inoutStream: InputStream): BitmapRegionDecoder? = try {\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n\t\t\tBitmapRegionDecoder.newInstance(inoutStream)\n\t\t} else {\n\t\t\t@Suppress(\"DEPRECATION\")\n\t\t\tBitmapRegionDecoder.newInstance(inoutStream, false)\n\t\t}\n\t} catch (e: IOException) {\n\t\te.printStackTraceDebug()\n\t\tnull\n\t}\n\n\t@Blocking\n\tfun probeMimeType(file: File): MimeType? {\n\t\treturn MimeTypes.probeMimeType(file) ?: detectBitmapType(file)\n\t}\n\n\t@Blocking\n\tprivate fun detectBitmapType(file: File): MimeType? = runCatchingCancellable {\n\t\tval options = BitmapFactory.Options().apply {\n\t\t\tinJustDecodeBounds = true\n\t\t}\n\t\tBitmapFactory.decodeFile(file.path, options)?.recycle()\n\t\toptions.outMimeType?.toMimeTypeOrNull()\n\t}.getOrNull()\n\n\tprivate fun checkBitmapNotNull(bitmap: Bitmap?, format: String?): Bitmap =\n\t\tbitmap ?: throw ImageDecodeException(null, format)\n\n\tprivate fun decodeAvif(bytes: ByteBuffer): Bitmap {\n\t\tval info = Info()\n\t\tif (!AvifDecoder.getInfo(bytes, bytes.remaining(), info)) {\n\t\t\tthrow ImageDecodeException(\n\t\t\t\tnull,\n\t\t\t\tFORMAT_AVIF,\n\t\t\t\t\"Requested to decode byte buffer which cannot be handled by AvifDecoder\",\n\t\t\t)\n\t\t}\n\t\tval config = if (info.depth == 8 || info.alphaPresent) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565\n\t\tval bitmap = createBitmap(info.width, info.height, config)\n\t\tif (!AvifDecoder.decode(bytes, bytes.remaining(), bitmap)) {\n\t\t\tbitmap.recycle()\n\t\t\tthrow ImageDecodeException(null, FORMAT_AVIF)\n\t\t}\n\t\treturn bitmap\n\t}\n\n\t@RequiresApi(Build.VERSION_CODES.P)\n\tprivate class DecoderConfigListener(\n\t\tprivate val isMutable: Boolean,\n\t) : ImageDecoder.OnHeaderDecodedListener {\n\n\t\toverride fun onHeaderDecoded(\n\t\t\tdecoder: ImageDecoder,\n\t\t\tinfo: ImageDecoder.ImageInfo,\n\t\t\tsource: ImageDecoder.Source\n\t\t) {\n\t\t\tdecoder.isMutableRequired = isMutable\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/image/CbzFetcher.kt",
    "content": "package org.koitharu.kotatsu.core.image\n\nimport android.net.Uri\nimport coil3.ImageLoader\nimport coil3.decode.DataSource\nimport coil3.decode.ImageSource\nimport coil3.fetch.Fetcher\nimport coil3.fetch.SourceFetchResult\nimport coil3.request.Options\nimport coil3.toAndroidUri\nimport kotlinx.coroutines.runInterruptible\nimport okio.Path.Companion.toPath\nimport okio.openZip\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.isZipUri\nimport coil3.Uri as CoilUri\n\nclass CbzFetcher(\n\tprivate val uri: Uri,\n\tprivate val options: Options,\n) : Fetcher {\n\n\toverride suspend fun fetch() = runInterruptible {\n\t\tval filePath = uri.schemeSpecificPart.toPath()\n\t\tval entryName = requireNotNull(uri.fragment)\n\t\tval fs = options.fileSystem.openZip(filePath)\n\t\tSourceFetchResult(\n\t\t\tsource = ImageSource(entryName.toPath(), fs),\n\t\t\tmimeType = MimeTypes.getMimeTypeFromExtension(entryName)?.toString(),\n\t\t\tdataSource = DataSource.DISK,\n\t\t)\n\t}\n\n\tclass Factory : Fetcher.Factory<CoilUri> {\n\n\t\toverride fun create(\n\t\t\tdata: CoilUri,\n\t\t\toptions: Options,\n\t\t\timageLoader: ImageLoader\n\t\t): Fetcher? {\n\t\t\tval androidUri = data.toAndroidUri()\n\t\t\treturn if (androidUri.isZipUri()) {\n\t\t\t\tCbzFetcher(androidUri, options)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/image/CoilImageView.kt",
    "content": "package org.koitharu.kotatsu.core.image\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport androidx.annotation.AttrRes\nimport androidx.annotation.DrawableRes\nimport androidx.core.content.withStyledAttributes\nimport androidx.lifecycle.findViewTreeLifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport coil3.ImageLoader\nimport coil3.asImage\nimport coil3.request.Disposable\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.NullRequestData\nimport coil3.request.SuccessResult\nimport coil3.request.allowRgb565\nimport coil3.request.crossfade\nimport coil3.request.lifecycle\nimport coil3.request.target\nimport coil3.size.Scale\nimport coil3.size.Size\nimport coil3.size.SizeResolver\nimport coil3.size.ViewSizeResolver\nimport coil3.util.CoilUtils\nimport com.google.android.material.imageview.ShapeableImageView\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.util.ext.decodeRegion\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.isNetworkError\nimport java.util.LinkedList\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nopen class CoilImageView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : ShapeableImageView(context, attrs, defStyleAttr), ImageRequest.Listener {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\t@Inject\n\tlateinit var networkState: NetworkState\n\n\tvar allowRgb565: Boolean = false\n\tvar useExistingDrawable: Boolean = false\n\tvar decodeRegion: Boolean = false\n\tvar exactImageSize: Size? = null\n\tvar crossfadeDurationFactor: Float = 1f\n\n\tvar placeholderDrawable: Drawable? = null\n\tvar errorDrawable: Drawable? = null\n\tvar fallbackDrawable: Drawable? = null\n\n\tprivate var currentRequest: Disposable? = null\n\tprivate var currentImageData: Any = NullRequestData\n\tprivate var networkWaitingJob: Job? = null\n\n\tprivate var listeners: MutableList<ImageRequest.Listener>? = null\n\n\tval isFailed: Boolean\n\t\tget() = CoilUtils.result(this) is ErrorResult\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.CoilImageView, defStyleAttr) {\n\t\t\tallowRgb565 = getBoolean(R.styleable.CoilImageView_allowRgb565, allowRgb565)\n\t\t\tuseExistingDrawable = getBoolean(R.styleable.CoilImageView_useExistingDrawable, useExistingDrawable)\n\t\t\tdecodeRegion = getBoolean(R.styleable.CoilImageView_decodeRegion, decodeRegion)\n\t\t\tplaceholderDrawable = getDrawable(R.styleable.CoilImageView_placeholderDrawable)\n\t\t\terrorDrawable = getDrawable(R.styleable.CoilImageView_errorDrawable)\n\t\t\tfallbackDrawable = getDrawable(R.styleable.CoilImageView_fallbackDrawable)\n\t\t\tcrossfadeDurationFactor = if (getBoolean(R.styleable.CoilImageView_crossfadeEnabled, true)) {\n\t\t\t\tcrossfadeDurationFactor\n\t\t\t} else {\n\t\t\t\t0f\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCancel(request: ImageRequest) {\n\t\tsuper.onCancel(request)\n\t\tlisteners?.forEach { it.onCancel(request) }\n\t}\n\n\toverride fun onError(request: ImageRequest, result: ErrorResult) {\n\t\tsuper.onError(request, result)\n\t\tlisteners?.forEach { it.onError(request, result) }\n\t\tif (result.throwable.isNetworkError()) {\n\t\t\twaitForNetwork()\n\t\t}\n\t}\n\n\toverride fun onStart(request: ImageRequest) {\n\t\tsuper.onStart(request)\n\t\tlisteners?.forEach { it.onStart(request) }\n\t}\n\n\toverride fun onSuccess(request: ImageRequest, result: SuccessResult) {\n\t\tsuper.onSuccess(request, result)\n\t\tlisteners?.forEach { it.onSuccess(request, result) }\n\t}\n\n\tfun addImageRequestListener(listener: ImageRequest.Listener) {\n\t\tval list = listeners ?: LinkedList<ImageRequest.Listener>().also { listeners = it }\n\t\tlist.add(listener)\n\t}\n\n\tfun removeImageRequestListener(listener: ImageRequest.Listener) {\n\t\tlisteners?.remove(listener)\n\t}\n\n\tfun setImageAsync(@DrawableRes resourceId: Int) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(resourceId)\n\t\t\t.build(),\n\t)\n\n\tfun setImageAsync(url: String?) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(url)\n\t\t\t.build(),\n\t)\n\n\tfun disposeImage() {\n\t\tnetworkWaitingJob?.cancel()\n\t\tnetworkWaitingJob = null\n\t\tCoilUtils.dispose(this)\n\t\tcurrentRequest = null\n\t\tcurrentImageData = NullRequestData\n\t\tsetImageDrawable(null)\n\t}\n\n\tfun reload() {\n\t\tCoilUtils.result(this)?.let { result ->\n\t\t\tenqueueRequest(result.request, force = true)\n\t\t}\n\t}\n\n\tprotected fun enqueueRequest(request: ImageRequest, force: Boolean = false): Disposable {\n\t\tval previous = currentRequest\n\t\tif (!force && currentImageData == request.data && previous?.job?.isCancelled == false && !isFailed) {\n\t\t\treturn previous\n\t\t}\n\t\tnetworkWaitingJob?.cancel()\n\t\tnetworkWaitingJob = null\n\t\tcurrentImageData = request.data\n\t\treturn coil.enqueue(request).also { currentRequest = it }\n\t}\n\n\tprotected open fun newRequestBuilder() = ImageRequest.Builder(context).apply {\n\t\tlifecycle(findViewTreeLifecycleOwner())\n\t\tval crossfadeDuration = if (context.isAnimationsEnabled) {\n\t\t\t(context.getAnimationDuration(R.integer.config_defaultAnimTime) * crossfadeDurationFactor).toInt()\n\t\t} else {\n\t\t\t0\n\t\t}\n\t\tcrossfade(crossfadeDuration)\n\t\tif (useExistingDrawable) {\n\t\t\tval previousDrawable = this@CoilImageView.drawable?.asImage()\n\t\t\tif (previousDrawable != null) {\n\t\t\t\tfallback(previousDrawable)\n\t\t\t\tplaceholder(previousDrawable)\n\t\t\t\terror(previousDrawable)\n\t\t\t} else {\n\t\t\t\tsetupPlaceholders()\n\t\t\t}\n\t\t} else {\n\t\t\tsetupPlaceholders()\n\t\t}\n\t\tif (decodeRegion) {\n\t\t\tdecodeRegion(0)\n\t\t}\n\t\tsize(\n\t\t\texactImageSize?.let {\n\t\t\t\tSizeResolver(it)\n\t\t\t} ?: ViewSizeResolver(this@CoilImageView),\n\t\t)\n\t\tscale(scaleType.toCoilScale())\n\t\tlistener(this@CoilImageView)\n\t\tallowRgb565(allowRgb565)\n\t\ttarget(this@CoilImageView)\n\t}\n\n\tprivate fun ImageRequest.Builder.setupPlaceholders() {\n\t\tplaceholder(placeholderDrawable?.asImage())\n\t\terror(errorDrawable?.asImage())\n\t\tfallback(fallbackDrawable?.asImage())\n\t}\n\n\tprivate fun ScaleType.toCoilScale(): Scale = if (this == ScaleType.CENTER_CROP) {\n\t\tScale.FILL\n\t} else {\n\t\tScale.FIT\n\t}\n\n\tprivate fun waitForNetwork() {\n\t\tif (networkWaitingJob?.isActive == true || networkState.isOnline()) {\n\t\t\treturn\n\t\t}\n\t\tnetworkWaitingJob?.cancel()\n\t\tnetworkWaitingJob = findViewTreeLifecycleOwner()?.lifecycleScope?.launch {\n\t\t\tnetworkState.awaitForConnection()\n\t\t\tif (isFailed) {\n\t\t\t\treload()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/image/CoilMemoryCacheKey.kt",
    "content": "package org.koitharu.kotatsu.core.image\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.view.View\nimport androidx.collection.ArrayMap\nimport coil3.memory.MemoryCache\nimport coil3.request.SuccessResult\nimport coil3.util.CoilUtils\nimport kotlinx.parcelize.Parceler\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\nclass CoilMemoryCacheKey(\n\tval data: MemoryCache.Key\n) : Parcelable {\n\n\tcompanion object : Parceler<CoilMemoryCacheKey> {\n\t\toverride fun CoilMemoryCacheKey.write(parcel: Parcel, flags: Int) = with(data) {\n\t\t\tparcel.writeString(key)\n\t\t\tparcel.writeInt(extras.size)\n\t\t\tfor (entry in extras.entries) {\n\t\t\t\tparcel.writeString(entry.key)\n\t\t\t\tparcel.writeString(entry.value)\n\t\t\t}\n\t\t}\n\n\t\toverride fun create(parcel: Parcel): CoilMemoryCacheKey = CoilMemoryCacheKey(\n\t\t\tMemoryCache.Key(\n\t\t\t\tkey = parcel.readString().orEmpty(),\n\t\t\t\textras = run {\n\t\t\t\t\tval size = parcel.readInt()\n\t\t\t\t\tval map = ArrayMap<String, String>(size)\n\t\t\t\t\trepeat(size) {\n\t\t\t\t\t\tmap.put(parcel.readString(), parcel.readString())\n\t\t\t\t\t}\n\t\t\t\t\tmap\n\t\t\t\t},\n\t\t\t),\n\t\t)\n\n\t\tfun from(view: View): CoilMemoryCacheKey? {\n\t\t\treturn (CoilUtils.result(view) as? SuccessResult)?.memoryCacheKey?.let {\n\t\t\t\tCoilMemoryCacheKey(it)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/image/MangaSourceHeaderInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.image\n\nimport coil3.intercept.Interceptor\nimport coil3.network.httpHeaders\nimport coil3.request.ImageResult\nimport org.koitharu.kotatsu.core.model.unwrap\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceKey\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\n\nclass MangaSourceHeaderInterceptor : Interceptor {\n\n\toverride suspend fun intercept(chain: Interceptor.Chain): ImageResult {\n\t\tval mangaSource = chain.request.extras[mangaSourceKey]?.unwrap() as? MangaParserSource ?: return chain.proceed()\n\t\tval request = chain.request\n\t\tval newHeaders = request.httpHeaders.newBuilder()\n\t\t\t.set(CommonHeaders.MANGA_SOURCE, mangaSource.name)\n\t\t\t.build()\n\t\tval newRequest = request.newBuilder()\n\t\t\t.httpHeaders(newHeaders)\n\t\t\t.build()\n\t\treturn chain.withRequest(newRequest).proceed()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/image/RegionBitmapDecoder.kt",
    "content": "package org.koitharu.kotatsu.core.image\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.graphics.Rect\nimport android.os.Build\nimport coil3.Extras\nimport coil3.ImageLoader\nimport coil3.asImage\nimport coil3.decode.DecodeResult\nimport coil3.decode.DecodeUtils\nimport coil3.decode.Decoder\nimport coil3.fetch.SourceFetchResult\nimport coil3.getExtra\nimport coil3.request.Options\nimport coil3.request.allowRgb565\nimport coil3.request.bitmapConfig\nimport coil3.request.colorSpace\nimport coil3.request.premultipliedAlpha\nimport coil3.size.Dimension\nimport coil3.size.Precision\nimport coil3.size.Scale\nimport coil3.size.Size\nimport coil3.size.isOriginal\nimport coil3.size.pxOrElse\nimport org.koitharu.kotatsu.core.util.ext.copyWithNewSource\nimport kotlin.math.roundToInt\n\nclass RegionBitmapDecoder(\n\tprivate val fetchResult: SourceFetchResult,\n\tprivate val options: Options,\n\tprivate val imageLoader: ImageLoader,\n) : Decoder {\n\n\toverride suspend fun decode(): DecodeResult? {\n\t\tval regionDecoder = BitmapDecoderCompat.createRegionDecoder(fetchResult.source.source().inputStream())\n\t\tif (regionDecoder == null) {\n\t\t\tval revivedFetchResult = fetchResult.copyWithNewSource()\n\t\t\treturn try {\n\t\t\t\tval fallbackDecoder = imageLoader.components.newDecoder(\n\t\t\t\t\tresult = revivedFetchResult,\n\t\t\t\t\toptions = options,\n\t\t\t\t\timageLoader = imageLoader,\n\t\t\t\t\tstartIndex = 0,\n\t\t\t\t)?.first\n\t\t\t\tif (fallbackDecoder == null || fallbackDecoder is RegionBitmapDecoder) {\n\t\t\t\t\tnull\n\t\t\t\t} else {\n\t\t\t\t\tfallbackDecoder.decode()\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\trevivedFetchResult.source.close()\n\t\t\t}\n\t\t}\n\t\tval bitmapOptions = BitmapFactory.Options()\n\t\treturn try {\n\t\t\tval rect = bitmapOptions.configureScale(regionDecoder.width, regionDecoder.height)\n\t\t\tbitmapOptions.configureConfig()\n\t\t\tval bitmap = regionDecoder.decodeRegion(rect, bitmapOptions)\n\t\t\tbitmap.density = options.context.resources.displayMetrics.densityDpi\n\t\t\tDecodeResult(\n\t\t\t\timage = bitmap.asImage(),\n\t\t\t\tisSampled = true,\n\t\t\t)\n\t\t} finally {\n\t\t\tregionDecoder.recycle()\n\t\t}\n\t}\n\n\t/** Compute and set the scaling properties for [BitmapFactory.Options]. */\n\tprivate fun BitmapFactory.Options.configureScale(srcWidth: Int, srcHeight: Int): Rect {\n\t\tval dstWidth = options.size.widthPx(options.scale) { srcWidth }\n\t\tval dstHeight = options.size.heightPx(options.scale) { srcHeight }\n\n\t\tval srcRatio = srcWidth / srcHeight.toDouble()\n\t\tval dstRatio = dstWidth / dstHeight.toDouble()\n\t\tval rect = if (srcRatio < dstRatio) {\n\t\t\t// probably manga\n\t\t\tRect(0, 0, srcWidth, (srcWidth / dstRatio).toInt().coerceAtLeast(1))\n\t\t} else {\n\t\t\tRect(0, 0, (srcHeight / dstRatio).toInt().coerceAtLeast(1), srcHeight)\n\t\t}\n\t\tval scroll = options.getExtra(regionScrollKey)\n\t\tif (scroll == SCROLL_UNDEFINED) {\n\t\t\trect.offsetTo(\n\t\t\t\t(srcWidth - rect.width()) / 2,\n\t\t\t\t(srcHeight - rect.height()) / 2,\n\t\t\t)\n\t\t} else {\n\t\t\trect.offsetTo(\n\t\t\t\t(srcWidth - rect.width()) / 2,\n\t\t\t\t(scroll * dstRatio).toInt().coerceAtMost(srcHeight - rect.height()),\n\t\t\t)\n\t\t}\n\n\t\t// Calculate the image's sample size.\n\t\tinSampleSize = DecodeUtils.calculateInSampleSize(\n\t\t\tsrcWidth = rect.width(),\n\t\t\tsrcHeight = rect.height(),\n\t\t\tdstWidth = dstWidth,\n\t\t\tdstHeight = dstHeight,\n\t\t\tscale = options.scale,\n\t\t)\n\n\t\t// Calculate the image's density scaling multiple.\n\t\tvar scale = DecodeUtils.computeSizeMultiplier(\n\t\t\tsrcWidth = rect.width() / inSampleSize.toDouble(),\n\t\t\tsrcHeight = rect.height() / inSampleSize.toDouble(),\n\t\t\tdstWidth = dstWidth.toDouble(),\n\t\t\tdstHeight = dstHeight.toDouble(),\n\t\t\tscale = options.scale,\n\t\t)\n\n\t\t// Only upscale the image if the options require an exact size.\n\t\tif (options.precision == Precision.INEXACT) {\n\t\t\tscale = scale.coerceAtMost(1.0)\n\t\t}\n\n\t\tinScaled = scale != 1.0\n\t\tif (inScaled) {\n\t\t\tif (scale > 1) {\n\t\t\t\t// Upscale\n\t\t\t\tinDensity = (Int.MAX_VALUE / scale).roundToInt()\n\t\t\t\tinTargetDensity = Int.MAX_VALUE\n\t\t\t} else {\n\t\t\t\t// Downscale\n\t\t\t\tinDensity = Int.MAX_VALUE\n\t\t\t\tinTargetDensity = (Int.MAX_VALUE * scale).roundToInt()\n\t\t\t}\n\t\t}\n\t\treturn rect\n\t}\n\n\tprivate fun BitmapFactory.Options.configureConfig() {\n\t\tvar config = options.bitmapConfig\n\n\t\tinMutable = false\n\n\t\tif (Build.VERSION.SDK_INT >= 26 && options.colorSpace != null) {\n\t\t\tinPreferredColorSpace = options.colorSpace\n\t\t}\n\t\tinPremultiplied = options.premultipliedAlpha\n\n\t\t// Decode the image as RGB_565 as an optimization if allowed.\n\t\tif (options.allowRgb565 && config == Bitmap.Config.ARGB_8888 && outMimeType == \"image/jpeg\") {\n\t\t\tconfig = Bitmap.Config.RGB_565\n\t\t}\n\n\t\t// High color depth images must be decoded as either RGBA_F16 or HARDWARE.\n\t\tif (Build.VERSION.SDK_INT >= 26 && outConfig == Bitmap.Config.RGBA_F16 && config != Bitmap.Config.HARDWARE) {\n\t\t\tconfig = Bitmap.Config.RGBA_F16\n\t\t}\n\n\t\tinPreferredConfig = config\n\t}\n\n\tobject Factory : Decoder.Factory {\n\n\t\toverride fun create(\n\t\t\tresult: SourceFetchResult,\n\t\t\toptions: Options,\n\t\t\timageLoader: ImageLoader\n\t\t): Decoder = RegionBitmapDecoder(result, options, imageLoader)\n\n\t\toverride fun equals(other: Any?) = other is Factory\n\n\t\toverride fun hashCode() = javaClass.hashCode()\n\t}\n\n\tcompanion object {\n\n\t\tconst val SCROLL_UNDEFINED = -1\n\t\tval regionScrollKey = Extras.Key(SCROLL_UNDEFINED)\n\n\t\tprivate inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {\n\t\t\treturn if (isOriginal) original() else width.toPx(scale)\n\t\t}\n\n\t\tprivate inline fun Size.heightPx(scale: Scale, original: () -> Int): Int {\n\t\t\treturn if (isOriginal) original() else height.toPx(scale)\n\t\t}\n\n\t\tprivate fun Dimension.toPx(scale: Scale) = pxOrElse {\n\t\t\twhen (scale) {\n\t\t\t\tScale.FILL -> Int.MIN_VALUE\n\t\t\t\tScale.FIT -> Int.MAX_VALUE\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt",
    "content": "package org.koitharu.kotatsu.core.io\n\nimport java.io.OutputStream\nimport java.util.Objects\n\nclass NullOutputStream : OutputStream() {\n\n\toverride fun write(b: Int) = Unit\n\n\toverride fun write(b: ByteArray, off: Int, len: Int) {\n\t\tObjects.checkFromIndexSize(off, len, b.size)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/FavouriteCategory.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport java.time.Instant\n\n@Parcelize\ndata class FavouriteCategory(\n\tval id: Long,\n\tval title: String,\n\tval sortKey: Int,\n\tval order: ListSortOrder,\n\tval createdAt: Instant,\n\tval isTrackingEnabled: Boolean,\n\tval isVisibleInLibrary: Boolean,\n) : Parcelable, ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is FavouriteCategory && id == other.id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\tif (previousState !is FavouriteCategory) {\n\t\t\treturn null\n\t\t}\n\t\treturn if (isTrackingEnabled != previousState.isTrackingEnabled || isVisibleInLibrary != previousState.isVisibleInLibrary) {\n\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/GenericSortOrder.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.model.SortOrder\n\n@Deprecated(\"\")\nenum class GenericSortOrder(\n\t@StringRes val titleResId: Int,\n\tval ascending: SortOrder,\n\tval descending: SortOrder,\n) {\n\n\tUPDATED(R.string.updated, SortOrder.UPDATED_ASC, SortOrder.UPDATED),\n\tRATING(R.string.by_rating, SortOrder.RATING_ASC, SortOrder.RATING),\n\tPOPULARITY(R.string.popularity, SortOrder.POPULARITY_ASC, SortOrder.POPULARITY),\n\tDATE(R.string.by_date, SortOrder.NEWEST_ASC, SortOrder.NEWEST),\n\tNAME(R.string.by_name, SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC),\n\t;\n\n\toperator fun get(direction: SortDirection): SortOrder = when (direction) {\n\t\tSortDirection.ASC -> ascending\n\t\tSortDirection.DESC -> descending\n\t}\n\n\tcompanion object {\n\n\t\tfun of(order: SortOrder): GenericSortOrder = entries.first { e ->\n\t\t\te.ascending == order || e.descending == order\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport android.content.res.Resources\nimport android.net.Uri\nimport android.text.SpannableStringBuilder\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport androidx.collection.MutableObjectIntMap\nimport androidx.core.net.toUri\nimport androidx.core.os.LocaleListCompat\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.strikeThrough\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.core.util.ext.iterator\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.Demographic\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport com.google.android.material.R as materialR\n\n@JvmName(\"mangaIds\")\nfun Collection<Manga>.ids() = mapToSet { it.id }\n\nfun Collection<Manga>.distinctById() = distinctBy { it.id }\n\n@JvmName(\"chaptersIds\")\nfun Collection<MangaChapter>.ids() = mapToSet { it.id }\n\nfun Collection<ChapterListItem>.countChaptersByBranch(): Int {\n\tif (size <= 1) {\n\t\treturn size\n\t}\n\tval acc = MutableObjectIntMap<String?>()\n\tfor (item in this) {\n\t\tval branch = item.chapter.branch\n\t\tacc[branch] = acc.getOrDefault(branch, 0) + 1\n\t}\n\tvar max = 0\n\tacc.forEachValue { x -> if (x > max) max = x }\n\treturn max\n}\n\n@get:StringRes\nval MangaState.titleResId: Int\n\tget() = when (this) {\n\t\tMangaState.ONGOING -> R.string.state_ongoing\n\t\tMangaState.FINISHED -> R.string.state_finished\n\t\tMangaState.ABANDONED -> R.string.state_abandoned\n\t\tMangaState.PAUSED -> R.string.state_paused\n\t\tMangaState.UPCOMING -> R.string.state_upcoming\n\t\tMangaState.RESTRICTED -> R.string.unavailable\n\t}\n\n@get:DrawableRes\nval MangaState.iconResId: Int\n\tget() = when (this) {\n\t\tMangaState.ONGOING -> R.drawable.ic_play\n\t\tMangaState.FINISHED -> R.drawable.ic_state_finished\n\t\tMangaState.ABANDONED -> R.drawable.ic_state_abandoned\n\t\tMangaState.PAUSED -> R.drawable.ic_action_pause\n\t\tMangaState.UPCOMING -> materialR.drawable.ic_clock_black_24dp\n\t\tMangaState.RESTRICTED -> R.drawable.ic_disable\n\t}\n\n@get:StringRes\nval ContentRating.titleResId: Int\n\tget() = when (this) {\n\t\tContentRating.SAFE -> R.string.rating_safe\n\t\tContentRating.SUGGESTIVE -> R.string.rating_suggestive\n\t\tContentRating.ADULT -> R.string.rating_adult\n\t}\n\n@get:StringRes\nval Demographic.titleResId: Int\n\tget() = when (this) {\n\t\tDemographic.SHOUNEN -> R.string.demographic_shounen\n\t\tDemographic.SHOUJO -> R.string.demographic_shoujo\n\t\tDemographic.SEINEN -> R.string.demographic_seinen\n\t\tDemographic.JOSEI -> R.string.demographic_josei\n\t\tDemographic.KODOMO -> R.string.demographic_kodomo\n\t\tDemographic.NONE -> R.string.none\n\t}\n\nfun Manga.getPreferredBranch(history: MangaHistory?): String? {\n\tval ch = chapters\n\tif (ch.isNullOrEmpty()) {\n\t\treturn null\n\t}\n\tif (history != null) {\n\t\tval currentChapter = ch.findById(history.chapterId)\n\t\tif (currentChapter != null) {\n\t\t\treturn currentChapter.branch\n\t\t}\n\t}\n\tval groups = ch.groupBy { it.branch }\n\tif (groups.size == 1) {\n\t\treturn groups.keys.first()\n\t}\n\tfor (locale in LocaleListCompat.getAdjustedDefault()) {\n\t\tval displayLanguage = locale.getDisplayLanguage(locale)\n\t\tval displayName = locale.getDisplayName(locale)\n\t\tval candidates = HashMap<String?, List<MangaChapter>>(3)\n\t\tfor (branch in groups.keys) {\n\t\t\tif (branch != null && (\n\t\t\t\t\tbranch.contains(displayLanguage, ignoreCase = true) ||\n\t\t\t\t\t\tbranch.contains(displayName, ignoreCase = true)\n\t\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcandidates[branch] = groups[branch] ?: continue\n\t\t\t}\n\t\t}\n\t\tif (candidates.isNotEmpty()) {\n\t\t\treturn candidates.maxBy { it.value.size }.key\n\t\t}\n\t}\n\treturn groups.maxByOrNull { it.value.size }?.key\n}\n\nval Manga.isLocal: Boolean\n\tget() = source == LocalMangaSource\n\nval Manga.isBroken: Boolean\n\tget() = source == UnknownMangaSource\n\nval Manga.appUrl: Uri\n\tget() = \"https://kotatsu.app/manga\".toUri()\n\t\t.buildUpon()\n\t\t.appendQueryParameter(\"source\", source.name)\n\t\t.appendQueryParameter(\"name\", title)\n\t\t.appendQueryParameter(\"url\", url)\n\t\t.build()\n\nfun Manga.chaptersCount(): Int {\n\tif (chapters.isNullOrEmpty()) {\n\t\treturn 0\n\t}\n\tval counters = MutableObjectIntMap<String?>()\n\tvar max = 0\n\tchapters?.forEach { x ->\n\t\tval c = counters.getOrDefault(x.branch, 0) + 1\n\t\tcounters[x.branch] = c\n\t\tif (max < c) {\n\t\t\tmax = c\n\t\t}\n\t}\n\treturn max\n}\n\nfun Manga.isNsfw(): Boolean = contentRating == ContentRating.ADULT || source.isNsfw()\n\nfun MangaListFilter.getSummary() = buildSpannedString {\n\tif (!query.isNullOrEmpty()) {\n\t\tappend(query)\n\t\tif (tags.isNotEmpty() || tagsExclude.isNotEmpty()) {\n\t\t\tappend(' ')\n\t\t\tappend('(')\n\t\t\tappendTagsSummary(this@getSummary)\n\t\t\tappend(')')\n\t\t}\n\t} else {\n\t\tappendTagsSummary(this@getSummary)\n\t}\n}\n\nprivate fun SpannableStringBuilder.appendTagsSummary(filter: MangaListFilter) {\n\tvar isFirst = true\n\tval separator = \", \"\n\tfor (tag in filter.tags) {\n\t\tif (isFirst) {\n\t\t\tisFirst = false\n\t\t} else {\n\t\t\tappend(separator)\n\t\t}\n\t\tappend(tag.title)\n\t}\n\tfor (tag in filter.tagsExclude) {\n\t\tif (isFirst) {\n\t\t\tisFirst = false\n\t\t} else {\n\t\t\tappend(separator)\n\t\t}\n\t\tstrikeThrough {\n\t\t\tappend(tag.title)\n\t\t}\n\t}\n}\n\nfun MangaChapter.getLocalizedTitle(resources: Resources, index: Int = -1): String {\n\ttitle?.let {\n\t\tif (it.isNotBlank()) {\n\t\t\treturn it\n\t\t}\n\t}\n\tval num = numberString()\n\tval vol = volumeString()\n\treturn when {\n\t\tnum != null && vol != null -> resources.getString(R.string.chapter_volume_number, vol, num)\n\t\tnum != null -> resources.getString(R.string.chapter_number, num)\n\t\tindex > 0 -> resources.getString(\n\t\t\tR.string.chapters_time_pattern,\n\t\t\tresources.getString(R.string.unnamed_chapter),\n\t\t\tindex.toString(),\n\t\t)\n\n\t\telse -> resources.getString(R.string.unnamed_chapter)\n\t}\n}\n\nfun Manga.withOverride(override: MangaOverride?) = if (override != null) {\n\tcopy(\n\t\ttitle = override.title.ifNullOrEmpty { title },\n\t\tcoverUrl = override.coverUrl.ifNullOrEmpty { coverUrl },\n\t\tlargeCoverUrl = override.coverUrl.ifNullOrEmpty { largeCoverUrl },\n\t\tcontentRating = override.contentRating ?: contentRating,\n\t)\n} else {\n\tthis\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaHistory.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport java.time.Instant\n\n@Parcelize\ndata class MangaHistory(\n\tval createdAt: Instant,\n\tval updatedAt: Instant,\n\tval chapterId: Long,\n\tval page: Int,\n\tval scroll: Int,\n\tval percent: Float,\n\tval chaptersCount: Int,\n) : Parcelable\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSource.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport android.content.Context\nimport android.os.Build\nimport android.text.SpannableStringBuilder\nimport android.text.style.ImageSpan\nimport android.widget.TextView\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.text.inSpans\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaSource\nimport org.koitharu.kotatsu.core.util.ext.getDisplayName\nimport org.koitharu.kotatsu.core.util.ext.toLocale\nimport org.koitharu.kotatsu.core.util.ext.toLocaleOrNull\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.splitTwoParts\nimport java.util.Locale\n\ndata object LocalMangaSource : MangaSource {\n\toverride val name = \"LOCAL\"\n}\n\ndata object UnknownMangaSource : MangaSource {\n\toverride val name = \"UNKNOWN\"\n}\n\ndata object TestMangaSource : MangaSource {\n\toverride val name = \"TEST\"\n}\n\nfun MangaSource(name: String?): MangaSource {\n\twhen (name ?: return UnknownMangaSource) {\n\t\tUnknownMangaSource.name -> return UnknownMangaSource\n\t\tLocalMangaSource.name -> return LocalMangaSource\n\t\tTestMangaSource.name -> return TestMangaSource\n\t}\n\tif (name.startsWith(\"content:\")) {\n\t\tval parts = name.substringAfter(':').splitTwoParts('/') ?: return UnknownMangaSource\n\t\treturn ExternalMangaSource(packageName = parts.first, authority = parts.second)\n\t}\n\tMangaParserSource.entries.forEach {\n\t\tif (it.name == name) return it\n\t}\n\treturn UnknownMangaSource\n}\n\nfun Collection<String>.toMangaSources() = map(::MangaSource)\n\nfun MangaSource.isNsfw(): Boolean = when (this) {\n\tis MangaSourceInfo -> mangaSource.isNsfw()\n\tis MangaParserSource -> contentType == ContentType.HENTAI\n\telse -> false\n}\n\n@get:StringRes\nval ContentType.titleResId\n\tget() = when (this) {\n\t\tContentType.MANGA -> R.string.content_type_manga\n\t\tContentType.HENTAI -> R.string.content_type_hentai\n\t\tContentType.COMICS -> R.string.content_type_comics\n\t\tContentType.OTHER -> R.string.content_type_other\n\t\tContentType.MANHWA -> R.string.content_type_manhwa\n\t\tContentType.MANHUA -> R.string.content_type_manhua\n\t\tContentType.NOVEL -> R.string.content_type_novel\n\t\tContentType.ONE_SHOT -> R.string.content_type_one_shot\n\t\tContentType.DOUJINSHI -> R.string.content_type_doujinshi\n\t\tContentType.IMAGE_SET -> R.string.content_type_image_set\n\t\tContentType.ARTIST_CG -> R.string.content_type_artist_cg\n\t\tContentType.GAME_CG -> R.string.content_type_game_cg\n\t}\n\ntailrec fun MangaSource.unwrap(): MangaSource = if (this is MangaSourceInfo) {\n\tmangaSource.unwrap()\n} else {\n\tthis\n}\n\nfun MangaSource.getLocale(): Locale? = (unwrap() as? MangaParserSource)?.locale?.toLocaleOrNull()\n\nfun MangaSource.getSummary(context: Context): String? = when (val source = unwrap()) {\n\tis MangaParserSource -> {\n\t\tval type = context.getString(source.contentType.titleResId)\n\t\tval locale = source.locale.toLocale().getDisplayName(context)\n\t\tcontext.getString(R.string.source_summary_pattern, type, locale)\n\t}\n\n\tis ExternalMangaSource -> context.getString(R.string.external_source)\n\n\telse -> null\n}\n\nfun MangaSource.getTitle(context: Context): String = when (val source = unwrap()) {\n\tis MangaParserSource -> source.title\n\tLocalMangaSource -> context.getString(R.string.local_storage)\n\tTestMangaSource -> context.getString(R.string.test_parser)\n\tis ExternalMangaSource -> source.resolveName(context)\n\telse -> context.getString(R.string.unknown)\n}\n\nfun SpannableStringBuilder.appendIcon(textView: TextView, @DrawableRes resId: Int): SpannableStringBuilder {\n\tval icon = ContextCompat.getDrawable(textView.context, resId) ?: return this\n\ticon.setTintList(textView.textColors)\n\tval size = textView.lineHeight\n\ticon.setBounds(0, 0, size, size)\n\tval alignment = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\tImageSpan.ALIGN_CENTER\n\t} else {\n\t\tImageSpan.ALIGN_BOTTOM\n\t}\n\treturn inSpans(ImageSpan(icon, alignment)) { append(' ') }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSourceInfo.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\ndata class MangaSourceInfo(\n\tval mangaSource: MangaSource,\n\tval isEnabled: Boolean,\n\tval isPinned: Boolean,\n) : MangaSource by mangaSource\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/MangaSourceSerializer.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport kotlinx.serialization.KSerializer\nimport kotlinx.serialization.descriptors.SerialDescriptor\nimport kotlinx.serialization.descriptors.serialDescriptor\nimport kotlinx.serialization.encoding.Decoder\nimport kotlinx.serialization.encoding.Encoder\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nobject MangaSourceSerializer : KSerializer<MangaSource> {\n\n    override val descriptor: SerialDescriptor = serialDescriptor<String>()\n\n    override fun serialize(\n        encoder: Encoder,\n        value: MangaSource\n    ) = encoder.encodeString(value.name)\n\n    override fun deserialize(decoder: Decoder): MangaSource = MangaSource(decoder.decodeString())\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/QuickFilter.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\n\nfun ListFilterOption.toChipModel(isChecked: Boolean) = ChipsView.ChipModel(\n\ttitle = titleText,\n\ttitleResId = titleResId,\n\ticon = iconResId,\n\ticonData = getIconData(),\n\tisChecked = isChecked,\n\tcounter = if (this is ListFilterOption.Branch) chaptersCount else 0,\n\tdata = this,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/SortDirection.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nenum class SortDirection {\n\n\tASC, DESC;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/ZoomMode.kt",
    "content": "package org.koitharu.kotatsu.core.model\n\nenum class ZoomMode {\n\n\tFIT_CENTER, FIT_HEIGHT, FIT_WIDTH, KEEP_START\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/MangaSourceParceler.kt",
    "content": "package org.koitharu.kotatsu.core.model.parcelable\n\nimport android.os.Parcel\nimport kotlinx.parcelize.Parceler\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nclass MangaSourceParceler : Parceler<MangaSource> {\n\n\toverride fun create(parcel: Parcel): MangaSource = MangaSource(parcel.readString())\n\n\toverride fun MangaSource.write(parcel: Parcel, flags: Int) {\n\t\tparcel.writeString(name)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableChapter.kt",
    "content": "package org.koitharu.kotatsu.core.model.parcelable\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parceler\nimport kotlinx.parcelize.Parcelize\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\n\n@Parcelize\ndata class ParcelableChapter(\n\tval chapter: MangaChapter,\n) : Parcelable {\n\n\tcompanion object : Parceler<ParcelableChapter> {\n\n\t\toverride fun create(parcel: Parcel) = ParcelableChapter(\n\t\t\tMangaChapter(\n\t\t\t\tid = parcel.readLong(),\n\t\t\t\ttitle = parcel.readString(),\n\t\t\t\tnumber = parcel.readFloat(),\n\t\t\t\tvolume = parcel.readInt(),\n\t\t\t\turl = parcel.readString().orEmpty(),\n\t\t\t\tscanlator = parcel.readString(),\n\t\t\t\tuploadDate = parcel.readLong(),\n\t\t\t\tbranch = parcel.readString(),\n\t\t\t\tsource = MangaSource(parcel.readString()),\n\t\t\t),\n\t\t)\n\n\t\toverride fun ParcelableChapter.write(parcel: Parcel, flags: Int) = with(chapter) {\n\t\t\tparcel.writeLong(id)\n\t\t\tparcel.writeString(title)\n\t\t\tparcel.writeFloat(number)\n\t\t\tparcel.writeInt(volume)\n\t\t\tparcel.writeString(url)\n\t\t\tparcel.writeString(scanlator)\n\t\t\tparcel.writeLong(uploadDate)\n\t\t\tparcel.writeString(branch)\n\t\t\tparcel.writeString(source.name)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt",
    "content": "package org.koitharu.kotatsu.core.model.parcelable\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parceler\nimport kotlinx.parcelize.Parcelize\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.util.ext.readParcelableCompat\nimport org.koitharu.kotatsu.core.util.ext.readSerializableCompat\nimport org.koitharu.kotatsu.core.util.ext.readStringSet\nimport org.koitharu.kotatsu.core.util.ext.writeStringSet\nimport org.koitharu.kotatsu.parsers.model.Manga\n\n@Parcelize\ndata class ParcelableManga(\n\tval manga: Manga,\n\tprivate val withDescription: Boolean = true,\n) : Parcelable {\n\n\tcompanion object : Parceler<ParcelableManga> {\n\n\t\toverride fun ParcelableManga.write(parcel: Parcel, flags: Int) = with(manga) {\n\t\t\tparcel.writeLong(id)\n\t\t\tparcel.writeString(title)\n\t\t\tparcel.writeStringSet(altTitles)\n\t\t\tparcel.writeString(url)\n\t\t\tparcel.writeString(publicUrl)\n\t\t\tparcel.writeFloat(rating)\n\t\t\tparcel.writeSerializable(contentRating)\n\t\t\tparcel.writeString(coverUrl)\n\t\t\tparcel.writeString(largeCoverUrl)\n\t\t\tparcel.writeString(description.takeIf { withDescription })\n\t\t\tparcel.writeParcelable(ParcelableMangaTags(tags), flags)\n\t\t\tparcel.writeSerializable(state)\n\t\t\tparcel.writeStringSet(authors)\n\t\t\tparcel.writeString(source.name)\n\t\t}\n\n\t\toverride fun create(parcel: Parcel) = ParcelableManga(\n\t\t\tManga(\n\t\t\t\tid = parcel.readLong(),\n\t\t\t\ttitle = requireNotNull(parcel.readString()),\n\t\t\t\taltTitles = parcel.readStringSet(),\n\t\t\t\turl = requireNotNull(parcel.readString()),\n\t\t\t\tpublicUrl = requireNotNull(parcel.readString()),\n\t\t\t\trating = parcel.readFloat(),\n\t\t\t\tcontentRating = parcel.readSerializableCompat(),\n\t\t\t\tcoverUrl = parcel.readString(),\n\t\t\t\tlargeCoverUrl = parcel.readString(),\n\t\t\t\tdescription = parcel.readString(),\n\t\t\t\ttags = requireNotNull(parcel.readParcelableCompat<ParcelableMangaTags>()).tags,\n\t\t\t\tstate = parcel.readSerializableCompat(),\n\t\t\t\tauthors = parcel.readStringSet(),\n\t\t\t\tchapters = null,\n\t\t\t\tsource = MangaSource(parcel.readString()),\n\t\t\t),\n\t\t\twithDescription = true,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaListFilter.kt",
    "content": "package org.koitharu.kotatsu.core.model.parcelable\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parceler\nimport kotlinx.parcelize.Parcelize\nimport kotlinx.parcelize.TypeParceler\nimport org.koitharu.kotatsu.core.util.ext.readEnumSet\nimport org.koitharu.kotatsu.core.util.ext.readParcelableCompat\nimport org.koitharu.kotatsu.core.util.ext.readSerializableCompat\nimport org.koitharu.kotatsu.core.util.ext.writeEnumSet\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.Demographic\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaState\n\nobject MangaListFilterParceler : Parceler<MangaListFilter> {\n\n\toverride fun MangaListFilter.write(parcel: Parcel, flags: Int) {\n\t\tparcel.writeString(query)\n\t\tparcel.writeParcelable(ParcelableMangaTags(tags), 0)\n\t\tparcel.writeParcelable(ParcelableMangaTags(tagsExclude), 0)\n\t\tparcel.writeSerializable(locale)\n\t\tparcel.writeSerializable(originalLocale)\n\t\tparcel.writeEnumSet(states)\n\t\tparcel.writeEnumSet(contentRating)\n\t\tparcel.writeEnumSet(types)\n\t\tparcel.writeEnumSet(demographics)\n\t\tparcel.writeInt(year)\n\t\tparcel.writeInt(yearFrom)\n\t\tparcel.writeInt(yearTo)\n\t\tparcel.writeString(author)\n\t}\n\n\toverride fun create(parcel: Parcel) = MangaListFilter(\n\t\tquery = parcel.readString(),\n\t\ttags = parcel.readParcelableCompat<ParcelableMangaTags>()?.tags.orEmpty(),\n\t\ttagsExclude = parcel.readParcelableCompat<ParcelableMangaTags>()?.tags.orEmpty(),\n\t\tlocale = parcel.readSerializableCompat(),\n\t\toriginalLocale = parcel.readSerializableCompat(),\n\t\tstates = parcel.readEnumSet<MangaState>().orEmpty(),\n\t\tcontentRating = parcel.readEnumSet<ContentRating>().orEmpty(),\n\t\ttypes = parcel.readEnumSet<ContentType>().orEmpty(),\n\t\tdemographics = parcel.readEnumSet<Demographic>().orEmpty(),\n\t\tyear = parcel.readInt(),\n\t\tyearFrom = parcel.readInt(),\n\t\tyearTo = parcel.readInt(),\n\t\tauthor = parcel.readString(),\n\t)\n}\n\n@Parcelize\n@TypeParceler<MangaListFilter, MangaListFilterParceler>\ndata class ParcelableMangaListFilter(val filter: MangaListFilter) : Parcelable\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaPage.kt",
    "content": "package org.koitharu.kotatsu.core.model.parcelable\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parceler\nimport kotlinx.parcelize.Parcelize\nimport kotlinx.parcelize.TypeParceler\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaPage\n\nobject MangaPageParceler : Parceler<MangaPage> {\n\toverride fun create(parcel: Parcel) = MangaPage(\n\t\tid = parcel.readLong(),\n\t\turl = requireNotNull(parcel.readString()),\n\t\tpreview = parcel.readString(),\n\t\tsource = MangaSource(parcel.readString()),\n\t)\n\n\toverride fun MangaPage.write(parcel: Parcel, flags: Int) {\n\t\tparcel.writeLong(id)\n\t\tparcel.writeString(url)\n\t\tparcel.writeString(preview)\n\t\tparcel.writeString(source.name)\n\t}\n}\n\n@Parcelize\n@TypeParceler<MangaPage, MangaPageParceler>\nclass ParcelableMangaPage(val page: MangaPage) : Parcelable\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableMangaTags.kt",
    "content": "package org.koitharu.kotatsu.core.model.parcelable\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parceler\nimport kotlinx.parcelize.Parcelize\nimport kotlinx.parcelize.TypeParceler\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\n\nobject MangaTagParceler : Parceler<MangaTag> {\n\toverride fun create(parcel: Parcel) = MangaTag(\n\t\ttitle = requireNotNull(parcel.readString()),\n\t\tkey = requireNotNull(parcel.readString()),\n\t\tsource = MangaSource(parcel.readString()),\n\t)\n\n\toverride fun MangaTag.write(parcel: Parcel, flags: Int) {\n\t\tparcel.writeString(title)\n\t\tparcel.writeString(key)\n\t\tparcel.writeString(source.name)\n\t}\n}\n\n@Parcelize\n@TypeParceler<MangaTag, MangaTagParceler>\ndata class ParcelableMangaTags(val tags: Set<MangaTag>) : Parcelable\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt",
    "content": "package org.koitharu.kotatsu.core.nav\n\nimport android.accounts.Account\nimport android.app.Activity\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.provider.Settings\nimport android.view.View\nimport androidx.annotation.CheckResult\nimport androidx.annotation.UiContext\nimport androidx.core.app.ShareCompat\nimport androidx.core.content.FileProvider\nimport androidx.core.net.toUri\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.findFragment\nimport androidx.lifecycle.LifecycleOwner\nimport dagger.hilt.android.EntryPointAccessors\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.alternatives.ui.AlternativesActivity\nimport org.koitharu.kotatsu.backups.ui.backup.BackupDialogFragment\nimport org.koitharu.kotatsu.backups.ui.restore.RestoreDialogFragment\nimport org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity\nimport org.koitharu.kotatsu.browser.BrowserActivity\nimport org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.image.CoilMemoryCacheKey\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.model.MangaSourceInfo\nimport org.koitharu.kotatsu.core.model.appUrl\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.isBroken\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaListFilter\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaSource\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.ui.dialog.BigButtonsAlertDialog\nimport org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.util.ext.connectivityManager\nimport org.koitharu.kotatsu.core.util.ext.findActivity\nimport org.koitharu.kotatsu.core.util.ext.getThemeDrawable\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toFileOrNull\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.core.util.ext.withArgs\nimport org.koitharu.kotatsu.details.ui.DetailsActivity\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet\nimport org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity\nimport org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingInfoSheet\nimport org.koitharu.kotatsu.download.ui.dialog.DownloadDialogFragment\nimport org.koitharu.kotatsu.download.ui.list.DownloadsActivity\nimport org.koitharu.kotatsu.favourites.ui.FavouritesActivity\nimport org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity\nimport org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity\nimport org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteDialog\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment\nimport org.koitharu.kotatsu.filter.ui.tags.TagsCatalogSheet\nimport org.koitharu.kotatsu.history.ui.HistoryActivity\nimport org.koitharu.kotatsu.image.ui.ImageActivity\nimport org.koitharu.kotatsu.list.ui.config.ListConfigBottomSheet\nimport org.koitharu.kotatsu.list.ui.config.ListConfigSection\nimport org.koitharu.kotatsu.local.ui.ImportDialogFragment\nimport org.koitharu.kotatsu.local.ui.info.LocalInfoDialog\nimport org.koitharu.kotatsu.main.ui.MainActivity\nimport org.koitharu.kotatsu.main.ui.welcome.WelcomeSheet\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.ellipsize\nimport org.koitharu.kotatsu.parsers.util.isNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.mapToArray\nimport org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity\nimport org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.ui.config.ScrobblerConfigActivity\nimport org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.search.ui.MangaListActivity\nimport org.koitharu.kotatsu.search.ui.multi.SearchActivity\nimport org.koitharu.kotatsu.settings.SettingsActivity\nimport org.koitharu.kotatsu.settings.about.AppUpdateActivity\nimport org.koitharu.kotatsu.settings.override.OverrideConfigActivity\nimport org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity\nimport org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity\nimport org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity\nimport org.koitharu.kotatsu.settings.storage.MangaDirectorySelectDialog\nimport org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity\nimport org.koitharu.kotatsu.settings.tracker.categories.TrackerCategoriesConfigSheet\nimport org.koitharu.kotatsu.stats.ui.StatsActivity\nimport org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet\nimport org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity\nimport org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity\nimport java.io.File\nimport androidx.appcompat.R as appcompatR\n\nclass AppRouter private constructor(\n    private val activity: FragmentActivity?,\n    private val fragment: Fragment?,\n) {\n\n    constructor(activity: FragmentActivity) : this(activity, null)\n\n    constructor(fragment: Fragment) : this(null, fragment)\n\n    private val settings: AppSettings by lazy {\n        EntryPointAccessors.fromApplication<AppRouterEntryPoint>(checkNotNull(contextOrNull())).settings\n    }\n\n    /** Activities **/\n\n    fun openList(source: MangaSource, filter: MangaListFilter?, sortOrder: SortOrder?) {\n        startActivity(listIntent(contextOrNull() ?: return, source, filter, sortOrder))\n    }\n\n    fun openList(tag: MangaTag) = openList(tag.source, MangaListFilter(tags = setOf(tag)), null)\n\n    fun openSearch(query: String, kind: SearchKind = SearchKind.SIMPLE) {\n        startActivity(\n            Intent(contextOrNull() ?: return, SearchActivity::class.java)\n                .putExtra(KEY_QUERY, query)\n                .putExtra(KEY_KIND, kind),\n        )\n    }\n\n    fun openSearch(source: MangaSource, query: String) = openList(source, MangaListFilter(query = query), null)\n\n    fun openDetails(manga: Manga) {\n        startActivity(detailsIntent(contextOrNull() ?: return, manga))\n    }\n\n    fun openDetails(mangaId: Long) {\n        startActivity(detailsIntent(contextOrNull() ?: return, mangaId))\n    }\n\n    fun openDetails(link: Uri) {\n        startActivity(\n            Intent(contextOrNull() ?: return, DetailsActivity::class.java)\n                .setData(link),\n        )\n    }\n\n    fun openReader(manga: Manga, anchor: View? = null) {\n        openReader(\n            ReaderIntent.Builder(contextOrNull() ?: return)\n                .manga(manga)\n                .build(),\n            anchor,\n        )\n    }\n\n    fun openReader(intent: ReaderIntent, anchor: View? = null) {\n        val activityIntent = intent.intent\n        if (settings.isReaderMultiTaskEnabled && activityIntent.data != null) {\n            activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)\n        }\n        startActivity(activityIntent, anchor?.let { view -> scaleUpActivityOptionsOf(view) })\n    }\n\n    fun openAlternatives(manga: Manga) {\n        startActivity(\n            Intent(contextOrNull() ?: return, AlternativesActivity::class.java)\n                .putExtra(KEY_MANGA, ParcelableManga(manga)),\n        )\n    }\n\n    fun openRelated(manga: Manga) {\n        startActivity(\n            Intent(contextOrNull(), RelatedMangaActivity::class.java)\n                .putExtra(KEY_MANGA, ParcelableManga(manga)),\n        )\n    }\n\n    fun openImage(url: String, source: MangaSource?, anchor: View? = null, preview: CoilMemoryCacheKey? = null) {\n        startActivity(\n            Intent(contextOrNull(), ImageActivity::class.java)\n                .setData(url.toUri())\n                .putExtra(KEY_SOURCE, source?.name)\n                .putExtra(KEY_PREVIEW, preview),\n            anchor?.let { scaleUpActivityOptionsOf(it) },\n        )\n    }\n\n    fun openBookmarks() = startActivity(AllBookmarksActivity::class.java)\n\n    fun openAppUpdate() = startActivity(AppUpdateActivity::class.java)\n\n    fun openSuggestions() {\n        startActivity(suggestionsIntent(contextOrNull() ?: return))\n    }\n\n    fun openSourcesCatalog() = startActivity(SourcesCatalogActivity::class.java)\n\n    fun openDownloads() = startActivity(DownloadsActivity::class.java)\n\n    fun openDirectoriesSettings() = startActivity(MangaDirectoriesActivity::class.java)\n\n    fun openBrowser(url: String, source: MangaSource?, title: String?) {\n        startActivity(browserIntent(contextOrNull() ?: return, url, source, title))\n    }\n\n    fun openBrowser(manga: Manga) = openBrowser(\n        url = manga.publicUrl,\n        source = manga.source,\n        title = manga.title,\n    )\n\n    fun openColorFilterConfig(manga: Manga, page: MangaPage) {\n        startActivity(\n            Intent(contextOrNull(), ColorFilterConfigActivity::class.java)\n                .putExtra(KEY_MANGA, ParcelableManga(manga))\n                .putExtra(KEY_PAGES, ParcelableMangaPage(page)),\n        )\n    }\n\n    fun openHistory() = startActivity(HistoryActivity::class.java)\n\n    fun openFavorites() = startActivity(FavouritesActivity::class.java)\n\n    fun openFavorites(category: FavouriteCategory) {\n        startActivity(\n            Intent(contextOrNull() ?: return, FavouritesActivity::class.java)\n                .putExtra(KEY_ID, category.id)\n                .putExtra(KEY_TITLE, category.title),\n        )\n    }\n\n    fun openFavoriteCategories() = startActivity(FavouriteCategoriesActivity::class.java)\n\n    fun openFavoriteCategoryEdit(categoryId: Long) {\n        startActivity(\n            Intent(contextOrNull() ?: return, FavouritesCategoryEditActivity::class.java)\n                .putExtra(KEY_ID, categoryId),\n        )\n    }\n\n    fun openFavoriteCategoryCreate() = openFavoriteCategoryEdit(FavouritesCategoryEditActivity.NO_ID)\n\n    fun openMangaUpdates() {\n        startActivity(mangaUpdatesIntent(contextOrNull() ?: return))\n    }\n\n    fun openMangaOverrideConfig(manga: Manga) {\n        val intent = overrideEditIntent(contextOrNull() ?: return, manga)\n        startActivity(intent)\n    }\n\n    fun openSettings() = startActivity(SettingsActivity::class.java)\n\n    fun openReaderSettings() {\n        startActivity(readerSettingsIntent(contextOrNull() ?: return))\n    }\n\n    fun openProxySettings() {\n        startActivity(proxySettingsIntent(contextOrNull() ?: return))\n    }\n\n    fun openDownloadsSetting() {\n        startActivity(downloadsSettingsIntent(contextOrNull() ?: return))\n    }\n\n    fun openSourceSettings(source: MangaSource) {\n        startActivity(sourceSettingsIntent(contextOrNull() ?: return, source))\n    }\n\n    fun openSuggestionsSettings() {\n        startActivity(suggestionsSettingsIntent(contextOrNull() ?: return))\n    }\n\n    fun openSourcesSettings() {\n        startActivity(sourcesSettingsIntent(contextOrNull() ?: return))\n    }\n\n    fun openDiscordSettings() {\n        startActivity(discordSettingsIntent(contextOrNull() ?: return))\n    }\n\n    fun openReaderTapGridSettings() = startActivity(ReaderTapGridConfigActivity::class.java)\n\n    fun openScrobblerSettings(scrobbler: ScrobblerService) {\n        startActivity(\n            Intent(contextOrNull() ?: return, ScrobblerConfigActivity::class.java)\n                .putExtra(KEY_ID, scrobbler.id),\n        )\n    }\n\n    fun openSourceAuth(source: MangaSource) {\n        startActivity(sourceAuthIntent(contextOrNull() ?: return, source))\n    }\n\n    fun openManageSources() {\n        startActivity(\n            manageSourcesIntent(contextOrNull() ?: return),\n        )\n    }\n\n    fun openStatistic() = startActivity(StatsActivity::class.java)\n\n    @CheckResult\n    fun openExternalBrowser(url: String, chooserTitle: CharSequence? = null): Boolean {\n        val intent = Intent(Intent.ACTION_VIEW)\n        intent.data = url.toUriOrNull() ?: return false\n        return startActivitySafe(\n            if (!chooserTitle.isNullOrEmpty()) {\n                Intent.createChooser(intent, chooserTitle)\n            } else {\n                intent\n            },\n        )\n    }\n\n    @CheckResult\n    fun openSystemSyncSettings(account: Account): Boolean {\n        val args = Bundle(1)\n        args.putParcelable(ACCOUNT_KEY, account)\n        val intent = Intent(ACTION_ACCOUNT_SYNC_SETTINGS)\n        intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args)\n        return startActivitySafe(intent)\n    }\n\n    /** Dialogs **/\n\n    fun showDownloadDialog(manga: Manga, snackbarHost: View?) = showDownloadDialog(setOf(manga), snackbarHost)\n\n    fun showDownloadDialog(manga: Collection<Manga>, snackbarHost: View?) {\n        if (manga.isEmpty()) {\n            return\n        }\n        val fm = getFragmentManager() ?: return\n        if (snackbarHost != null) {\n            getLifecycleOwner()?.let { lifecycleOwner ->\n                DownloadDialogFragment.registerCallback(fm, lifecycleOwner, snackbarHost)\n            }\n        } else {\n            DownloadDialogFragment.unregisterCallback(fm)\n        }\n        DownloadDialogFragment().withArgs(1) {\n            putParcelableArray(KEY_MANGA, manga.mapToArray { ParcelableManga(it, withDescription = false) })\n        }.showDistinct()\n    }\n\n    fun showLocalInfoDialog(manga: Manga) {\n        LocalInfoDialog().withArgs(1) {\n            putParcelable(KEY_MANGA, ParcelableManga(manga))\n        }.showDistinct()\n    }\n\n    fun showDirectorySelectDialog() {\n        MangaDirectorySelectDialog().showDistinct()\n    }\n\n    fun showFavoriteDialog(manga: Manga) = showFavoriteDialog(setOf(manga))\n\n    fun showFavoriteDialog(manga: Collection<Manga>) {\n        if (manga.isEmpty()) {\n            return\n        }\n        FavoriteDialog().withArgs(1) {\n            putParcelableArrayList(\n                KEY_MANGA_LIST,\n                manga.mapTo(ArrayList(manga.size)) { ParcelableManga(it, withDescription = false) },\n            )\n        }.showDistinct()\n    }\n\n    fun showTagDialog(tag: MangaTag) {\n        buildAlertDialog(contextOrNull() ?: return) {\n            setIcon(R.drawable.ic_tag)\n            setTitle(tag.title)\n            setItems(\n                arrayOf(\n                    context.getString(R.string.search_on_s, tag.source.getTitle(context)),\n                    context.getString(R.string.search_everywhere),\n                ),\n            ) { _, which ->\n                when (which) {\n                    0 -> openList(tag)\n                    1 -> openSearch(tag.title, SearchKind.TAG)\n                }\n            }\n            setNegativeButton(R.string.close, null)\n            setCancelable(true)\n        }.show()\n    }\n\n    fun showAuthorDialog(author: String, source: MangaSource) {\n        buildAlertDialog(contextOrNull() ?: return) {\n            setIcon(R.drawable.ic_user)\n            setTitle(author)\n            setItems(\n                arrayOf(\n                    context.getString(R.string.search_on_s, source.getTitle(context)),\n                    context.getString(R.string.search_everywhere),\n                ),\n            ) { _, which ->\n                when (which) {\n                    0 -> openList(source, MangaListFilter(author = author), null)\n                    1 -> openSearch(author, SearchKind.AUTHOR)\n                }\n            }\n            setNegativeButton(R.string.close, null)\n            setCancelable(true)\n        }.show()\n    }\n\n    fun showShareDialog(manga: Manga) {\n        if (manga.isBroken) {\n            return\n        }\n        if (manga.isLocal) {\n            manga.url.toUri().toFileOrNull()?.let {\n                shareFile(it)\n            }\n            return\n        }\n        buildAlertDialog(contextOrNull() ?: return) {\n            setIcon(context.getThemeDrawable(appcompatR.attr.actionModeShareDrawable))\n            setTitle(R.string.share)\n            setItems(\n                arrayOf(\n                    context.getString(R.string.link_to_manga_in_app),\n                    context.getString(R.string.link_to_manga_on_s, manga.source.getTitle(context)),\n                ),\n            ) { _, which ->\n                val link = when (which) {\n                    0 -> manga.appUrl.toString()\n                    1 -> manga.publicUrl\n                    else -> return@setItems\n                }\n                shareLink(link, manga.title)\n            }\n            setNegativeButton(android.R.string.cancel, null)\n            setCancelable(true)\n        }.show()\n    }\n\n    fun showErrorDialog(error: Throwable, url: String? = null) {\n        ErrorDetailsDialog().withArgs(2) {\n            putSerializable(KEY_ERROR, error)\n            putString(KEY_URL, url)\n        }.show()\n    }\n\n    fun showBackupRestoreDialog(fileUri: Uri) {\n        RestoreDialogFragment().withArgs(1) {\n            putString(KEY_FILE, fileUri.toString())\n        }.show()\n    }\n\n    fun createBackup(destination: Uri) {\n        BackupDialogFragment().withArgs(1) {\n            putParcelable(KEY_DATA, destination)\n        }.showDistinct()\n    }\n\n    fun showImportDialog() {\n        ImportDialogFragment().showDistinct()\n    }\n\n    fun showFilterSheet(): Boolean = if (isFilterSupported()) {\n        FilterSheetFragment().showDistinct()\n    } else {\n        false\n    }\n\n    fun showTagsCatalogSheet(excludeMode: Boolean) {\n        if (!isFilterSupported()) {\n            return\n        }\n        TagsCatalogSheet().withArgs(1) {\n            putBoolean(KEY_EXCLUDE, excludeMode)\n        }.showDistinct()\n    }\n\n    fun showListConfigSheet(section: ListConfigSection) {\n        ListConfigBottomSheet().withArgs(1) {\n            putParcelable(KEY_LIST_SECTION, section)\n        }.showDistinct()\n    }\n\n    fun showStatisticSheet(manga: Manga) {\n        MangaStatsSheet().withArgs(1) {\n            putParcelable(KEY_MANGA, ParcelableManga(manga))\n        }.showDistinct()\n    }\n\n    fun showReaderConfigSheet(mode: ReaderMode) {\n        ReaderConfigSheet().withArgs(1) {\n            putInt(KEY_READER_MODE, mode.id)\n        }.showDistinct()\n    }\n\n    fun showWelcomeSheet() {\n        WelcomeSheet().showDistinct()\n    }\n\n    fun showChapterPagesSheet() {\n        ChaptersPagesSheet().showDistinct()\n    }\n\n    fun showChapterPagesSheet(defaultTab: Int) {\n        ChaptersPagesSheet().withArgs(1) {\n            putInt(KEY_TAB, defaultTab)\n        }.showDistinct()\n    }\n\n    fun showScrobblingSelectorSheet(manga: Manga, scrobblerService: ScrobblerService?) {\n        ScrobblingSelectorSheet().withArgs(2) {\n            putParcelable(KEY_MANGA, ParcelableManga(manga))\n            if (scrobblerService != null) {\n                putInt(KEY_ID, scrobblerService.id)\n            }\n        }.show()\n    }\n\n    fun showScrobblingInfoSheet(index: Int) {\n        ScrobblingInfoSheet().withArgs(1) {\n            putInt(KEY_INDEX, index)\n        }.showDistinct()\n    }\n\n    fun showTrackerCategoriesConfigSheet() {\n        TrackerCategoriesConfigSheet().showDistinct()\n    }\n\n    fun askForDownloadOverMeteredNetwork(onConfirmed: (allow: Boolean) -> Unit) {\n        val context = contextOrNull() ?: return\n        when (settings.allowDownloadOnMeteredNetwork) {\n            TriStateOption.ENABLED -> onConfirmed(true)\n            TriStateOption.DISABLED -> onConfirmed(false)\n            TriStateOption.ASK -> {\n                if (!context.connectivityManager.isActiveNetworkMetered) {\n                    onConfirmed(true)\n                    return\n                }\n                val listener = DialogInterface.OnClickListener { _, which ->\n                    when (which) {\n                        DialogInterface.BUTTON_POSITIVE -> {\n                            settings.allowDownloadOnMeteredNetwork = TriStateOption.ENABLED\n                            onConfirmed(true)\n                        }\n\n                        DialogInterface.BUTTON_NEUTRAL -> {\n                            onConfirmed(true)\n                        }\n\n                        DialogInterface.BUTTON_NEGATIVE -> {\n                            settings.allowDownloadOnMeteredNetwork = TriStateOption.DISABLED\n                            onConfirmed(false)\n                        }\n                    }\n                }\n                BigButtonsAlertDialog.Builder(context)\n                    .setIcon(R.drawable.ic_network_cellular)\n                    .setTitle(R.string.download_cellular_confirm)\n                    .setPositiveButton(R.string.allow_always, listener)\n                    .setNeutralButton(R.string.allow_once, listener)\n                    .setNegativeButton(R.string.dont_allow, listener)\n                    .create()\n                    .show()\n            }\n        }\n    }\n\n    /** Public utils **/\n\n    fun isFilterSupported(): Boolean = when {\n        fragment != null -> FilterCoordinator.find(fragment) != null\n        activity != null -> activity is FilterCoordinator.Owner\n        else -> false\n    }\n\n    fun isChapterPagesSheetShown(): Boolean {\n        val sheet = getFragmentManager()?.findFragmentByTag(fragmentTag<ChaptersPagesSheet>()) as? ChaptersPagesSheet\n        return sheet?.dialog?.isShowing == true\n    }\n\n    fun closeWelcomeSheet(): Boolean {\n        val tag = fragmentTag<WelcomeSheet>()\n        val sheet = fragment?.findFragmentByTagRecursive(tag)\n            ?: activity?.supportFragmentManager?.findFragmentByTag(tag)\n            ?: return false\n        return if (sheet is WelcomeSheet) {\n            sheet.dismissAllowingStateLoss()\n            true\n        } else {\n            false\n        }\n    }\n\n    /** Private utils **/\n\n    private fun startActivity(intent: Intent, options: Bundle? = null) {\n        fragment?.also {\n            if (it.host != null) {\n                it.startActivity(intent, options)\n            }\n        } ?: activity?.startActivity(intent, options)\n    }\n\n    private fun startActivitySafe(intent: Intent): Boolean = try {\n        startActivity(intent)\n        true\n    } catch (_: ActivityNotFoundException) {\n        false\n    }\n\n    private fun startActivity(activityClass: Class<out Activity>) {\n        startActivity(Intent(contextOrNull() ?: return, activityClass))\n    }\n\n    private fun getFragmentManager(): FragmentManager? = runCatching {\n        fragment?.childFragmentManager ?: activity?.supportFragmentManager\n    }.onFailure { exception ->\n        exception.printStackTraceDebug()\n    }.getOrNull()\n\n    private fun shareLink(link: String, title: String) {\n        val context = contextOrNull() ?: return\n        ShareCompat.IntentBuilder(context)\n            .setText(link)\n            .setType(TYPE_TEXT)\n            .setChooserTitle(context.getString(R.string.share_s, title.ellipsize(12)))\n            .startChooser()\n    }\n\n    private fun shareFile(file: File) { // TODO directory sharing support\n        val context = contextOrNull() ?: return\n        val intentBuilder = ShareCompat.IntentBuilder(context)\n            .setType(TYPE_CBZ)\n        val uri = FileProvider.getUriForFile(context, \"${BuildConfig.APPLICATION_ID}.files\", file)\n        intentBuilder.addStream(uri)\n        intentBuilder.setChooserTitle(context.getString(R.string.share_s, file.name))\n        intentBuilder.startChooser()\n    }\n\n    @UiContext\n    private fun contextOrNull(): Context? = activity ?: fragment?.context\n\n    private fun getLifecycleOwner(): LifecycleOwner? = activity ?: fragment?.viewLifecycleOwner\n\n    private fun DialogFragment.showDistinct(): Boolean {\n        val fm = this@AppRouter.getFragmentManager() ?: return false\n        val tag = javaClass.fragmentTag()\n        val existing = fm.findFragmentByTag(tag) as? DialogFragment?\n        if (existing != null && existing.isVisible && existing.arguments == this.arguments) {\n            return false\n        }\n        show(fm, tag)\n        return true\n    }\n\n    private fun DialogFragment.show() {\n        show(\n            this@AppRouter.getFragmentManager() ?: return,\n            javaClass.fragmentTag(),\n        )\n    }\n\n    private fun Fragment.findFragmentByTagRecursive(fragmentTag: String): Fragment? {\n        childFragmentManager.findFragmentByTag(fragmentTag)?.let {\n            return it\n        }\n        val parent = parentFragment\n        return if (parent != null) {\n            parent.findFragmentByTagRecursive(fragmentTag)\n        } else {\n            parentFragmentManager.findFragmentByTag(fragmentTag)\n        }\n    }\n\n    companion object {\n\n        fun from(view: View): AppRouter? = runCatching {\n            AppRouter(view.findFragment())\n        }.getOrElse {\n            (view.context.findActivity() as? FragmentActivity)?.let(::AppRouter)\n        }\n\n        fun detailsIntent(context: Context, manga: Manga) = Intent(context, DetailsActivity::class.java)\n            .putExtra(KEY_MANGA, ParcelableManga(manga))\n            .setData(shortMangaUrl(manga.id))\n\n        fun detailsIntent(context: Context, mangaId: Long) = Intent(context, DetailsActivity::class.java)\n            .putExtra(KEY_ID, mangaId)\n            .setData(shortMangaUrl(mangaId))\n\n        fun listIntent(context: Context, source: MangaSource, filter: MangaListFilter?, sortOrder: SortOrder?): Intent =\n            Intent(context, MangaListActivity::class.java)\n                .setAction(ACTION_MANGA_EXPLORE)\n                .putExtra(KEY_SOURCE, source.name)\n                .apply {\n                    if (!filter.isNullOrEmpty()) {\n                        putExtra(KEY_FILTER, ParcelableMangaListFilter(filter))\n                    }\n                    if (sortOrder != null) {\n                        putExtra(KEY_SORT_ORDER, sortOrder)\n                    }\n                }\n\n        fun cloudFlareResolveIntent(context: Context, exception: CloudFlareProtectedException): Intent =\n            Intent(context, CloudFlareActivity::class.java).apply {\n                data = exception.url.toUri()\n                putExtra(KEY_SOURCE, exception.source.name)\n                exception.headers[CommonHeaders.USER_AGENT]?.let {\n                    putExtra(KEY_USER_AGENT, it)\n                }\n            }\n\n        fun browserIntent(\n            context: Context,\n            url: String,\n            source: MangaSource?,\n            title: String?\n        ): Intent = Intent(context, BrowserActivity::class.java)\n            .setData(url.toUri())\n            .putExtra(KEY_TITLE, title)\n            .putExtra(KEY_SOURCE, source?.name)\n\n        fun suggestionsIntent(context: Context) = Intent(context, SuggestionsActivity::class.java)\n\n        fun homeIntent(context: Context) = Intent(context, MainActivity::class.java)\n\n        fun mangaUpdatesIntent(context: Context) = Intent(context, UpdatesActivity::class.java)\n\n        fun readerSettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_READER)\n\n        fun suggestionsSettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_SUGGESTIONS)\n\n        fun trackerSettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_TRACKER)\n\n        fun periodicBackupSettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_PERIODIC_BACKUP)\n\n        fun discordSettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_MANAGE_DISCORD)\n\n        fun proxySettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_PROXY)\n\n        fun historySettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_HISTORY)\n\n        fun sourcesSettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_SOURCES)\n\n        fun manageSourcesIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_MANAGE_SOURCES)\n\n        fun downloadsSettingsIntent(context: Context) =\n            Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_MANAGE_DOWNLOADS)\n\n        fun sourceSettingsIntent(context: Context, source: MangaSource): Intent = when (source) {\n            is MangaSourceInfo -> sourceSettingsIntent(context, source.mangaSource)\n            is ExternalMangaSource -> Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)\n                .setData(Uri.fromParts(\"package\", source.packageName, null))\n\n            else -> Intent(context, SettingsActivity::class.java)\n                .setAction(ACTION_SOURCE)\n                .putExtra(KEY_SOURCE, source.name)\n        }\n\n        fun sourceAuthIntent(context: Context, source: MangaSource): Intent {\n            return Intent(context, SourceAuthActivity::class.java)\n                .putExtra(KEY_SOURCE, source.name)\n        }\n\n        fun overrideEditIntent(context: Context, manga: Manga): Intent =\n            Intent(context, OverrideConfigActivity::class.java)\n                .putExtra(KEY_MANGA, ParcelableManga(manga, withDescription = false))\n\n        fun isShareSupported(manga: Manga): Boolean = when {\n            manga.isBroken -> false\n            manga.isLocal -> manga.url.toUri().toFileOrNull() != null\n            else -> true\n        }\n\n        fun shortMangaUrl(mangaId: Long): Uri = Uri.Builder()\n            .scheme(\"kotatsu\")\n            .path(\"manga\")\n            .appendQueryParameter(\"id\", mangaId.toString())\n            .build()\n\n        const val KEY_DATA = \"data\"\n        const val KEY_ENTRIES = \"entries\"\n        const val KEY_ERROR = \"error\"\n        const val KEY_EXCLUDE = \"exclude\"\n        const val KEY_FILE = \"file\"\n        const val KEY_FILTER = \"filter\"\n        const val KEY_ID = \"id\"\n        const val KEY_INDEX = \"index\"\n        const val KEY_IS_BOTTOMTAB = \"is_btab\"\n        const val KEY_KIND = \"kind\"\n        const val KEY_LIST_SECTION = \"list_section\"\n        const val KEY_MANGA = \"manga\"\n        const val KEY_MANGA_LIST = \"manga_list\"\n        const val KEY_PAGES = \"pages\"\n        const val KEY_PREVIEW = \"preview\"\n        const val KEY_QUERY = \"query\"\n        const val KEY_READER_MODE = \"reader_mode\"\n        const val KEY_SORT_ORDER = \"sort_order\"\n        const val KEY_SOURCE = \"source\"\n        const val KEY_TAB = \"tab\"\n        const val KEY_TITLE = \"title\"\n        const val KEY_URL = \"url\"\n        const val KEY_USER_AGENT = \"user_agent\"\n\n        const val ACTION_HISTORY = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_HISTORY\"\n        const val ACTION_MANAGE_DOWNLOADS = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_DOWNLOADS\"\n        const val ACTION_MANAGE_SOURCES = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES_LIST\"\n        const val ACTION_MANGA_EXPLORE = \"${BuildConfig.APPLICATION_ID}.action.EXPLORE_MANGA\"\n        const val ACTION_PROXY = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_PROXY\"\n        const val ACTION_READER = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS\"\n        const val ACTION_SOURCE = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS\"\n        const val ACTION_SOURCES = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES\"\n        const val ACTION_MANAGE_DISCORD = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_DISCORD\"\n        const val ACTION_SUGGESTIONS = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS\"\n        const val ACTION_TRACKER = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER\"\n        const val ACTION_PERIODIC_BACKUP = \"${BuildConfig.APPLICATION_ID}.action.MANAGE_PERIODIC_BACKUP\"\n\n        private const val ACCOUNT_KEY = \"account\"\n        private const val ACTION_ACCOUNT_SYNC_SETTINGS = \"android.settings.ACCOUNT_SYNC_SETTINGS\"\n        private const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = \":settings:show_fragment_args\"\n\n        private const val TYPE_TEXT = \"text/plain\"\n        private const val TYPE_IMAGE = \"image/*\"\n        private const val TYPE_CBZ = \"application/x-cbz\"\n\n        private fun Class<out Fragment>.fragmentTag() = name // TODO\n\n        private inline fun <reified F : Fragment> fragmentTag() = F::class.java.fragmentTag()\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouterEntryPoint.kt",
    "content": "package org.koitharu.kotatsu.core.nav\n\nimport dagger.hilt.EntryPoint\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport org.koitharu.kotatsu.core.prefs.AppSettings\n\n@EntryPoint\n@InstallIn(SingletonComponent::class)\ninterface AppRouterEntryPoint {\n\n\tval settings: AppSettings\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/nav/MangaIntent.kt",
    "content": "package org.koitharu.kotatsu.core.nav\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport androidx.lifecycle.SavedStateHandle\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter.Companion.KEY_ID\nimport org.koitharu.kotatsu.core.nav.AppRouter.Companion.KEY_MANGA\nimport org.koitharu.kotatsu.core.util.ext.getParcelableCompat\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nclass MangaIntent private constructor(\n\t@JvmField val manga: Manga?,\n\t@JvmField val id: Long,\n\t@JvmField val uri: Uri?,\n) {\n\n\tconstructor(intent: Intent?) : this(\n\t\tmanga = intent?.getParcelableExtraCompat<ParcelableManga>(KEY_MANGA)?.manga,\n\t\tid = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE,\n\t\turi = intent?.data,\n\t)\n\n\tconstructor(savedStateHandle: SavedStateHandle) : this(\n\t\tmanga = savedStateHandle.get<ParcelableManga>(KEY_MANGA)?.manga,\n\t\tid = savedStateHandle[KEY_ID] ?: ID_NONE,\n\t\turi = savedStateHandle[AppRouter.KEY_DATA],\n\t)\n\n\tconstructor(args: Bundle?) : this(\n\t\tmanga = args?.getParcelableCompat<ParcelableManga>(KEY_MANGA)?.manga,\n\t\tid = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,\n\t\turi = null,\n\t)\n\n\tval mangaId: Long\n\t\tget() = if (id != ID_NONE) id else manga?.id ?: uri?.lastPathSegment?.toLongOrNull() ?: ID_NONE\n\n\tcompanion object {\n\n\t\tconst val ID_NONE = 0L\n\n\t\tfun of(manga: Manga) = MangaIntent(manga, manga.id, null)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/nav/NavUtil.kt",
    "content": "package org.koitharu.kotatsu.core.nav\n\nimport android.app.ActivityOptions\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.isOnScreen\n\ninline val FragmentActivity.router: AppRouter\n\tget() = AppRouter(this)\n\ninline val Fragment.router: AppRouter\n\tget() = AppRouter(this)\n\ntailrec fun Fragment.dismissParentDialog(): Boolean {\n\treturn when (val parent = parentFragment) {\n\t\tnull -> return false\n\t\tis DialogFragment -> {\n\t\t\tparent.dismiss()\n\t\t\ttrue\n\t\t}\n\n\t\telse -> parent.dismissParentDialog()\n\t}\n}\n\nfun scaleUpActivityOptionsOf(view: View): Bundle? {\n\tif (!view.context.isAnimationsEnabled || !view.isOnScreen()) {\n\t\treturn null\n\t}\n\treturn ActivityOptions.makeScaleUpAnimation(\n\t\t/* source = */ view,\n\t\t/* startX = */ 0,\n\t\t/* startY = */ 0,\n\t\t/* width = */ view.width,\n\t\t/* height = */ view.height,\n\t).toBundle()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/nav/ReaderIntent.kt",
    "content": "package org.koitharu.kotatsu.core.nav\n\nimport android.content.Context\nimport android.content.Intent\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.reader.ui.ReaderActivity\nimport org.koitharu.kotatsu.reader.ui.ReaderState\n\n@JvmInline\nvalue class ReaderIntent private constructor(\n\tval intent: Intent,\n) {\n\n\tclass Builder(context: Context) {\n\n\t\tprivate val intent = Intent(context, ReaderActivity::class.java)\n\t\t\t.setAction(ACTION_MANGA_READ)\n\n\t\tfun manga(manga: Manga) = apply {\n\t\t\tintent.putExtra(AppRouter.KEY_MANGA, ParcelableManga(manga))\n\t\t\tintent.setData(AppRouter.shortMangaUrl(manga.id))\n\t\t}\n\n\t\tfun mangaId(mangaId: Long) = apply {\n\t\t\tintent.putExtra(AppRouter.KEY_ID, mangaId)\n\t\t\tintent.setData(AppRouter.shortMangaUrl(mangaId))\n\t\t}\n\n\t\tfun incognito() = apply {\n\t\t\tintent.putExtra(EXTRA_INCOGNITO, true)\n\t\t}\n\n\t\tfun branch(branch: String?) = apply {\n\t\t\tintent.putExtra(EXTRA_BRANCH, branch)\n\t\t}\n\n\t\tfun state(state: ReaderState?) = apply {\n\t\t\tintent.putExtra(EXTRA_STATE, state)\n\t\t}\n\n\t\tfun bookmark(bookmark: Bookmark) = manga(\n\t\t\tbookmark.manga,\n\t\t).state(\n\t\t\tReaderState(\n\t\t\t\tchapterId = bookmark.chapterId,\n\t\t\t\tpage = bookmark.page,\n\t\t\t\tscroll = bookmark.scroll,\n\t\t\t),\n\t\t)\n\n\t\tfun build() = ReaderIntent(intent)\n\t}\n\n\tcompanion object {\n\t\tconst val ACTION_MANGA_READ = \"${BuildConfig.APPLICATION_ID}.action.READ_MANGA\"\n\t\tconst val EXTRA_STATE = \"state\"\n\t\tconst val EXTRA_BRANCH = \"branch\"\n\t\tconst val EXTRA_INCOGNITO = \"incognito\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/CacheLimitInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.CacheControl\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport java.util.concurrent.TimeUnit\n\nclass CacheLimitInterceptor : Interceptor {\n\n\tprivate val defaultMaxAge = TimeUnit.HOURS.toSeconds(1)\n\tprivate val defaultCacheControl = CacheControl.Builder()\n\t\t.maxAge(defaultMaxAge.toInt(), TimeUnit.SECONDS)\n\t\t.build()\n\t\t.toString()\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval response = chain.proceed(chain.request())\n\t\tval responseCacheControl = CacheControl.parse(response.headers)\n\t\tif (responseCacheControl.noStore || responseCacheControl.maxAgeSeconds <= defaultMaxAge) {\n\t\t\treturn response\n\t\t}\n\t\treturn response.newBuilder()\n\t\t\t.header(CommonHeaders.CACHE_CONTROL, defaultCacheControl)\n\t\t\t.build()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/CloudFlareInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport okio.IOException\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\n\nclass CloudFlareInterceptor : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval request = chain.request()\n\t\tval response = chain.proceed(request)\n\t\treturn when (CloudFlareHelper.checkResponseForProtection(response)) {\n\t\t\tCloudFlareHelper.PROTECTION_BLOCKED -> response.closeThrowing(\n\t\t\t\tCloudFlareBlockedException(\n\t\t\t\t\turl = request.url.toString(),\n\t\t\t\t\tsource = request.tag(MangaSource::class.java),\n\t\t\t\t),\n\t\t\t)\n\n\t\t\tCloudFlareHelper.PROTECTION_CAPTCHA -> response.closeThrowing(\n\t\t\t\tCloudFlareProtectedException(\n\t\t\t\t\turl = request.url.toString(),\n\t\t\t\t\tsource = request.tag(MangaSource::class.java),\n\t\t\t\t\theaders = request.headers,\n\t\t\t\t),\n\t\t\t)\n\n\t\t\telse -> response\n\t\t}\n\t}\n\n\tprivate fun Response.closeThrowing(error: IOException): Nothing {\n\t\ttry {\n\t\t\tclose()\n\t\t} catch (e: Exception) {\n\t\t\terror.addSuppressed(e)\n\t\t}\n\t\tthrow error\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeaders.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.CacheControl\n\nobject CommonHeaders {\n\n\tconst val REFERER = \"Referer\"\n\tconst val USER_AGENT = \"User-Agent\"\n\tconst val ACCEPT = \"Accept\"\n\tconst val CONTENT_TYPE = \"Content-Type\"\n\tconst val CONTENT_DISPOSITION = \"Content-Disposition\"\n\tconst val COOKIE = \"Cookie\"\n\tconst val CONTENT_ENCODING = \"Content-Encoding\"\n\tconst val ACCEPT_ENCODING = \"Accept-Encoding\"\n\tconst val AUTHORIZATION = \"Authorization\"\n\tconst val CACHE_CONTROL = \"Cache-Control\"\n\tconst val PROXY_AUTHORIZATION = \"Proxy-Authorization\"\n\tconst val RETRY_AFTER = \"Retry-After\"\n\tconst val LAST_MODIFIED = \"Last-Modified\"\n\tconst val IF_MODIFIED_SINCE = \"If-Modified-Since\"\n\tconst val MANGA_SOURCE = \"X-Manga-Source\"\n\n\tval CACHE_CONTROL_NO_STORE: CacheControl\n\t\tget() = CacheControl.Builder().noStore().build()\n\n\tconst val DATE_FORMAT = \"EEE, dd MMM yyyy HH:mm:ss zzz\"\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/CommonHeadersInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport dagger.Lazy\nimport okhttp3.Headers\nimport okhttp3.Interceptor\nimport okhttp3.Interceptor.Chain\nimport okhttp3.Request\nimport okhttp3.Response\nimport okio.IOException\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.mergeWith\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.net.IDN\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CommonHeadersInterceptor @Inject constructor(\n\tprivate val mangaRepositoryFactoryLazy: Lazy<MangaRepository.Factory>,\n\tprivate val mangaLoaderContextLazy: Lazy<MangaLoaderContextImpl>,\n) : Interceptor {\n\n\toverride fun intercept(chain: Chain): Response {\n\t\tval request = chain.request()\n\t\tval source = request.tag(MangaSource::class.java)\n\t\t\t?: request.headers[CommonHeaders.MANGA_SOURCE]?.let { MangaSource(it) }\n\t\tval repository = if (source is MangaParserSource) {\n\t\t\tmangaRepositoryFactoryLazy.get().create(source) as? ParserMangaRepository\n\t\t} else {\n\t\t\tif (BuildConfig.DEBUG && source == null) {\n\t\t\t\tIllegalArgumentException(\"Request without source tag: ${request.url}\")\n\t\t\t\t\t.printStackTrace()\n\t\t\t}\n\t\t\tnull\n\t\t}\n\t\tval headersBuilder = request.headers.newBuilder()\n\t\t\t.removeAll(CommonHeaders.MANGA_SOURCE)\n\t\trepository?.getRequestHeaders()?.let {\n\t\t\theadersBuilder.mergeWith(it, replaceExisting = false)\n\t\t}\n\t\tif (headersBuilder[CommonHeaders.USER_AGENT] == null) {\n\t\t\theadersBuilder[CommonHeaders.USER_AGENT] = mangaLoaderContextLazy.get().getDefaultUserAgent()\n\t\t}\n\t\tif (headersBuilder[CommonHeaders.REFERER] == null && repository != null) {\n\t\t\tval idn = IDN.toASCII(repository.domain)\n\t\t\theadersBuilder.trySet(CommonHeaders.REFERER, \"https://$idn/\")\n\t\t}\n\t\tval newRequest = request.newBuilder().headers(headersBuilder.build()).build()\n\t\treturn repository?.interceptSafe(ProxyChain(chain, newRequest)) ?: chain.proceed(newRequest)\n\t}\n\n\tprivate fun Headers.Builder.trySet(name: String, value: String) = try {\n\t\tset(name, value)\n\t} catch (e: IllegalArgumentException) {\n\t\te.printStackTraceDebug()\n\t}\n\n\tprivate fun Interceptor.interceptSafe(chain: Chain): Response = runCatchingCancellable {\n\t\tintercept(chain)\n\t}.getOrElse { e ->\n\t\tif (e is IOException || e is Error) {\n\t\t\tthrow e\n\t\t} else {\n\t\t\t// only IOException can be safely thrown from an Interceptor\n\t\t\tthrow IOException(\"Error in interceptor: ${e.message}\", e)\n\t\t}\n\t}\n\n\tprivate class ProxyChain(\n\t\tprivate val delegate: Chain,\n\t\tprivate val request: Request,\n\t) : Chain by delegate {\n\n\t\toverride fun request(): Request = request\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/DoHManager.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.Cache\nimport okhttp3.Dns\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.OkHttpClient\nimport okhttp3.dnsoverhttps.DnsOverHttps\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport java.net.InetAddress\nimport java.net.UnknownHostException\n\nclass DoHManager(\n\tcache: Cache,\n\tprivate val settings: AppSettings,\n) : Dns {\n\n\tprivate val bootstrapClient = OkHttpClient.Builder().cache(cache).build()\n\n\tprivate var cachedDelegate: Dns? = null\n\tprivate var cachedProvider: DoHProvider? = null\n\n\toverride fun lookup(hostname: String): List<InetAddress> {\n\t\treturn try {\n\t\t\tgetDelegate().lookup(hostname)\n\t\t} catch (e: UnknownHostException) {\n\t\t\t// fallback\n\t\t\tDns.SYSTEM.lookup(hostname)\n\t\t}\n\t}\n\n\t@Synchronized\n\tprivate fun getDelegate(): Dns {\n\t\tvar delegate = cachedDelegate\n\t\tval provider = settings.dnsOverHttps\n\t\tif (delegate == null || provider != cachedProvider) {\n\t\t\tdelegate = createDelegate(provider)\n\t\t\tcachedDelegate = delegate\n\t\t\tcachedProvider = provider\n\t\t}\n\t\treturn delegate\n\t}\n\n\tprivate fun createDelegate(provider: DoHProvider): Dns = when (provider) {\n\t\tDoHProvider.NONE -> Dns.SYSTEM\n\t\tDoHProvider.GOOGLE -> DnsOverHttps.Builder().client(bootstrapClient)\n\t\t\t.url(\"https://dns.google/dns-query\".toHttpUrl())\n\t\t\t.resolvePrivateAddresses(true)\n\t\t\t.bootstrapDnsHosts(\n\t\t\t\tlistOfNotNull(\n\t\t\t\t\ttryGetByIp(\"8.8.4.4\"),\n\t\t\t\t\ttryGetByIp(\"8.8.8.8\"),\n\t\t\t\t\ttryGetByIp(\"2001:4860:4860::8888\"),\n\t\t\t\t\ttryGetByIp(\"2001:4860:4860::8844\"),\n\t\t\t\t),\n\t\t\t).build()\n\n\t\tDoHProvider.CLOUDFLARE -> DnsOverHttps.Builder().client(bootstrapClient)\n\t\t\t.url(\"https://cloudflare-dns.com/dns-query\".toHttpUrl())\n\t\t\t.resolvePrivateAddresses(true)\n\t\t\t.bootstrapDnsHosts(\n\t\t\t\tlistOfNotNull(\n\t\t\t\t\ttryGetByIp(\"162.159.36.1\"),\n\t\t\t\t\ttryGetByIp(\"162.159.46.1\"),\n\t\t\t\t\ttryGetByIp(\"1.1.1.1\"),\n\t\t\t\t\ttryGetByIp(\"1.0.0.1\"),\n\t\t\t\t\ttryGetByIp(\"162.159.132.53\"),\n\t\t\t\t\ttryGetByIp(\"2606:4700:4700::1111\"),\n\t\t\t\t\ttryGetByIp(\"2606:4700:4700::1001\"),\n\t\t\t\t\ttryGetByIp(\"2606:4700:4700::0064\"),\n\t\t\t\t\ttryGetByIp(\"2606:4700:4700::6400\"),\n\t\t\t\t),\n\t\t\t).build()\n\n\t\tDoHProvider.ADGUARD -> DnsOverHttps.Builder().client(bootstrapClient)\n\t\t\t.url(\"https://dns-unfiltered.adguard.com/dns-query\".toHttpUrl())\n\t\t\t.resolvePrivateAddresses(true)\n\t\t\t.bootstrapDnsHosts(\n\t\t\t\tlistOfNotNull(\n\t\t\t\t\ttryGetByIp(\"94.140.14.140\"),\n\t\t\t\t\ttryGetByIp(\"94.140.14.141\"),\n\t\t\t\t\ttryGetByIp(\"2a10:50c0::1:ff\"),\n\t\t\t\t\ttryGetByIp(\"2a10:50c0::2:ff\"),\n\t\t\t\t),\n\t\t\t).build()\n\n\t\tDoHProvider.ZERO_MS -> DnsOverHttps.Builder().client(bootstrapClient)\n\t\t\t.url(\"https://v.recipes/dns-query\".toHttpUrl())\n\t\t\t.resolvePublicAddresses(true)\n\t\t\t.build()\n\t}\n\n\tprivate fun tryGetByIp(ip: String): InetAddress? = try {\n\t\tInetAddress.getByName(ip)\n\t} catch (e: UnknownHostException) {\n\t\te.printStackTraceDebug()\n\t\tnull\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/DoHProvider.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nenum class DoHProvider {\n\n\tNONE, GOOGLE, CLOUDFLARE, ADGUARD, ZERO_MS\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/GZipInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.Interceptor\nimport okhttp3.MultipartBody\nimport okhttp3.Response\nimport okio.IOException\nimport org.koitharu.kotatsu.core.exceptions.WrapperIOException\nimport org.koitharu.kotatsu.core.network.CommonHeaders.CONTENT_ENCODING\n\nclass GZipInterceptor : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response = try {\n\t\tval request = chain.request()\n\t\tif (request.body is MultipartBody) {\n\t\t\tchain.proceed(request)\n\t\t} else {\n\t\t\tval newRequest = request.newBuilder()\n\t\t\tnewRequest.addHeader(CONTENT_ENCODING, \"gzip\")\n\t\t\tchain.proceed(newRequest.build())\n\t\t}\n\t} catch (e: IOException) {\n\t\tthrow e\n\t} catch (e: Exception) {\n\t\tthrow WrapperIOException(e)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/HttpClients.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport javax.inject.Qualifier\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class BaseHttpClient\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class MangaHttpClient\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/NetworkModule.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport android.content.Context\nimport dagger.Binds\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport okhttp3.Cache\nimport okhttp3.CookieJar\nimport okhttp3.OkHttpClient\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar\nimport org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor\nimport org.koitharu.kotatsu.core.network.imageproxy.RealImageProxyInterceptor\nimport org.koitharu.kotatsu.core.network.proxy.ProxyProvider\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.assertNotInMainThread\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Provider\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\ninterface NetworkModule {\n\n\t@Binds\n\tfun bindCookieJar(androidCookieJar: MutableCookieJar): CookieJar\n\n\t@Binds\n\tfun bindImageProxyInterceptor(impl: RealImageProxyInterceptor): ImageProxyInterceptor\n\n\tcompanion object {\n\n\t\t@Provides\n\t\t@Singleton\n\t\tfun provideCookieJar(\n\t\t\t@ApplicationContext context: Context\n\t\t): MutableCookieJar = runCatching {\n\t\t\tAndroidCookieJar()\n\t\t}.getOrElse { e ->\n\t\t\te.printStackTraceDebug()\n\t\t\t// WebView is not available\n\t\t\tPreferencesCookieJar(context)\n\t\t}\n\n\t\t@Provides\n\t\t@Singleton\n\t\tfun provideHttpCache(\n\t\t\tlocalStorageManager: LocalStorageManager,\n\t\t): Cache = localStorageManager.createHttpCache()\n\n\t\t@Provides\n\t\t@Singleton\n\t\t@BaseHttpClient\n\t\tfun provideBaseHttpClient(\n\t\t\t@ApplicationContext contextProvider: Provider<Context>,\n\t\t\tcache: Cache,\n\t\t\tcookieJar: CookieJar,\n\t\t\tsettings: AppSettings,\n\t\t\tproxyProvider: ProxyProvider,\n\t\t): OkHttpClient = OkHttpClient.Builder().apply {\n\t\t\tassertNotInMainThread()\n\t\t\tconnectTimeout(20, TimeUnit.SECONDS)\n\t\t\treadTimeout(60, TimeUnit.SECONDS)\n\t\t\twriteTimeout(20, TimeUnit.SECONDS)\n\t\t\tcookieJar(cookieJar)\n\t\t\tproxySelector(proxyProvider.selector)\n\t\t\tproxyAuthenticator(proxyProvider.authenticator)\n\t\t\tdns(DoHManager(cache, settings))\n\t\t\tif (settings.isSSLBypassEnabled) {\n\t\t\t\tdisableCertificateVerification()\n\t\t\t} else {\n\t\t\t\tinstallExtraCertificates(contextProvider.get())\n\t\t\t}\n\t\t\tcache(cache)\n\t\t\taddInterceptor(GZipInterceptor())\n\t\t\taddInterceptor(CloudFlareInterceptor())\n\t\t\taddInterceptor(RateLimitInterceptor())\n\t\t\tif (BuildConfig.DEBUG) {\n\t\t\t\taddInterceptor(CurlLoggingInterceptor())\n\t\t\t}\n\t\t}.build()\n\n\t\t@Provides\n\t\t@Singleton\n\t\t@MangaHttpClient\n\t\tfun provideMangaHttpClient(\n\t\t\t@BaseHttpClient baseClient: OkHttpClient,\n\t\t\tcommonHeadersInterceptor: CommonHeadersInterceptor,\n\t\t): OkHttpClient = baseClient.newBuilder().apply {\n\t\t\taddNetworkInterceptor(CacheLimitInterceptor())\n\t\t\taddInterceptor(commonHeadersInterceptor)\n\t\t}.build()\n\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/RateLimitInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport okhttp3.internal.closeQuietly\nimport org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport java.util.concurrent.TimeUnit\n\nclass RateLimitInterceptor : Interceptor {\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval response = chain.proceed(chain.request())\n\t\tif (response.code == 429) {\n\t\t\tval request = response.request\n\t\t\tresponse.closeQuietly()\n\t\t\tthrow TooManyRequestExceptions(\n\t\t\t\turl = request.url.toString(),\n\t\t\t\tretryAfter = response.header(CommonHeaders.RETRY_AFTER)?.parseRetryAfter() ?: 0L,\n\t\t\t)\n\t\t}\n\t\treturn response\n\t}\n\n\tprivate fun String.parseRetryAfter(): Long {\n\t\treturn toLongOrNull()?.let { TimeUnit.SECONDS.toMillis(it) }\n\t\t\t?: ZonedDateTime.parse(this, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant().toEpochMilli()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/SSLUtils.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.AssetManager\nimport android.util.Log\nimport okhttp3.OkHttpClient\nimport okhttp3.tls.HandshakeCertificates\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport java.security.SecureRandom\nimport java.security.cert.CertificateFactory\nimport java.security.cert.X509Certificate\nimport javax.net.ssl.SSLContext\nimport javax.net.ssl.SSLSocketFactory\nimport javax.net.ssl.X509TrustManager\n\n@SuppressLint(\"CustomX509TrustManager\")\nfun OkHttpClient.Builder.disableCertificateVerification() = also { builder ->\n\trunCatching {\n\t\tval trustAllCerts = object : X509TrustManager {\n\t\t\toverride fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) = Unit\n\n\t\t\toverride fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) = Unit\n\n\t\t\toverride fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()\n\t\t}\n\t\tval sslContext = SSLContext.getInstance(\"SSL\")\n\t\tsslContext.init(null, arrayOf(trustAllCerts), SecureRandom())\n\t\tval sslSocketFactory: SSLSocketFactory = sslContext.socketFactory\n\t\tbuilder.sslSocketFactory(sslSocketFactory, trustAllCerts)\n\t\tbuilder.hostnameVerifier { _, _ -> true }\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}\n}\n\nfun OkHttpClient.Builder.installExtraCertificates(context: Context) = also { builder ->\n\tval certificatesBuilder = HandshakeCertificates.Builder()\n\t\t.addPlatformTrustedCertificates()\n\tval assets = context.assets.list(\"\").orEmpty()\n\tfor (path in assets) {\n\t\tif (path.endsWith(\".pem\")) {\n\t\t\tval cert = loadCert(context, path) ?: continue\n\t\t\tcertificatesBuilder.addTrustedCertificate(cert)\n\t\t}\n\t}\n\tval certificates = certificatesBuilder.build()\n\tbuilder.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager)\n}\n\nprivate fun loadCert(context: Context, path: String): X509Certificate? = runCatching {\n\tval cf = CertificateFactory.getInstance(\"X.509\")\n\tcontext.assets.open(path, AssetManager.ACCESS_STREAMING).use {\n\t\tcf.generateCertificate(it)\n\t} as X509Certificate\n}.onFailure { e ->\n\te.printStackTraceDebug()\n}.onSuccess {\n\tif (BuildConfig.DEBUG) {\n\t\tLog.i(\"ExtraCerts\", \"Loaded cert $path\")\n\t}\n}.getOrNull()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/AndroidCookieJar.kt",
    "content": "package org.koitharu.kotatsu.core.network.cookies\n\nimport android.webkit.CookieManager\nimport androidx.annotation.WorkerThread\nimport androidx.core.util.Predicate\nimport okhttp3.Cookie\nimport okhttp3.HttpUrl\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\nclass AndroidCookieJar : MutableCookieJar {\n\n\tprivate val cookieManager = CookieManager.getInstance()\n\n\t@WorkerThread\n\toverride fun loadForRequest(url: HttpUrl): List<Cookie> {\n\t\tval rawCookie = cookieManager.getCookie(url.toString()) ?: return emptyList()\n\t\treturn rawCookie.split(';').mapNotNull {\n\t\t\tCookie.parse(url, it)\n\t\t}\n\t}\n\n\t@WorkerThread\n\toverride fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {\n\t\tif (cookies.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tval urlString = url.toString()\n\t\tfor (cookie in cookies) {\n\t\t\tcookieManager.setCookie(urlString, cookie.toString())\n\t\t}\n\t}\n\n\toverride fun removeCookies(url: HttpUrl, predicate: Predicate<Cookie>?) {\n\t\tval cookies = loadForRequest(url)\n\t\tif (cookies.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tval urlString = url.toString()\n\t\tfor (c in cookies) {\n\t\t\tif (predicate != null && !predicate.test(c)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tval nc = c.newBuilder()\n\t\t\t\t.expiresAt(System.currentTimeMillis() - 100000)\n\t\t\t\t.build()\n\t\t\tcookieManager.setCookie(urlString, nc.toString())\n\t\t}\n\t}\n\n\toverride suspend fun clear() = suspendCoroutine<Boolean> { continuation ->\n\t\tcookieManager.removeAllCookies(continuation::resume)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/CookieWrapper.kt",
    "content": "package org.koitharu.kotatsu.core.network.cookies\n\nimport android.util.Base64\nimport okhttp3.Cookie\nimport java.io.ByteArrayInputStream\nimport java.io.ByteArrayOutputStream\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\n\n\ndata class CookieWrapper(\n\tval cookie: Cookie,\n) {\n\n\tconstructor(encodedString: String) : this(\n\t\tObjectInputStream(ByteArrayInputStream(Base64.decode(encodedString, Base64.NO_WRAP))).use {\n\t\t\tval name = it.readUTF()\n\t\t\tval value = it.readUTF()\n\t\t\tval expiresAt = it.readLong()\n\t\t\tval domain = it.readUTF()\n\t\t\tval path = it.readUTF()\n\t\t\tval secure = it.readBoolean()\n\t\t\tval httpOnly = it.readBoolean()\n\t\t\tval persistent = it.readBoolean()\n\t\t\tval hostOnly = it.readBoolean()\n\t\t\tCookie.Builder().also { c ->\n\t\t\t\tc.name(name)\n\t\t\t\tc.value(value)\n\t\t\t\tif (persistent) {\n\t\t\t\t\tc.expiresAt(expiresAt)\n\t\t\t\t}\n\t\t\t\tif (hostOnly) {\n\t\t\t\t\tc.hostOnlyDomain(domain)\n\t\t\t\t} else {\n\t\t\t\t\tc.domain(domain)\n\t\t\t\t}\n\t\t\t\tc.path(path)\n\t\t\t\tif (secure) {\n\t\t\t\t\tc.secure()\n\t\t\t\t}\n\t\t\t\tif (httpOnly) {\n\t\t\t\t\tc.httpOnly()\n\t\t\t\t}\n\t\t\t}.build()\n\t\t},\n\t)\n\n\tfun encode(): String {\n\t\tval output = ByteArrayOutputStream()\n\t\tObjectOutputStream(output).use {\n\t\t\tit.writeUTF(cookie.name)\n\t\t\tit.writeUTF(cookie.value)\n\t\t\tit.writeLong(cookie.expiresAt)\n\t\t\tit.writeUTF(cookie.domain)\n\t\t\tit.writeUTF(cookie.path)\n\t\t\tit.writeBoolean(cookie.secure)\n\t\t\tit.writeBoolean(cookie.httpOnly)\n\t\t\tit.writeBoolean(cookie.persistent)\n\t\t\tit.writeBoolean(cookie.hostOnly)\n\t\t}\n\t\treturn Base64.encodeToString(output.toByteArray(), Base64.NO_WRAP)\n\t}\n\n\tfun isExpired() = cookie.expiresAt < System.currentTimeMillis()\n\n\tfun key(): String {\n\t\treturn (if (cookie.secure) \"https\" else \"http\") + \"://\" + cookie.domain + cookie.path + \"|\" + cookie.name\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/MutableCookieJar.kt",
    "content": "package org.koitharu.kotatsu.core.network.cookies\n\nimport androidx.annotation.WorkerThread\nimport androidx.core.util.Predicate\nimport okhttp3.Cookie\nimport okhttp3.CookieJar\nimport okhttp3.HttpUrl\n\ninterface MutableCookieJar : CookieJar {\n\n\t@WorkerThread\n\toverride fun loadForRequest(url: HttpUrl): List<Cookie>\n\n\t@WorkerThread\n\toverride fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)\n\n\t@WorkerThread\n\tfun removeCookies(url: HttpUrl, predicate: Predicate<Cookie>?)\n\n\tsuspend fun clear(): Boolean\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/cookies/PreferencesCookieJar.kt",
    "content": "package org.koitharu.kotatsu.core.network.cookies\n\nimport android.content.Context\nimport androidx.annotation.WorkerThread\nimport androidx.collection.ArrayMap\nimport androidx.core.content.edit\nimport androidx.core.util.Predicate\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport okhttp3.Cookie\nimport okhttp3.HttpUrl\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\n\nprivate const val PREFS_NAME = \"cookies\"\n\nclass PreferencesCookieJar(\n\tcontext: Context,\n) : MutableCookieJar {\n\n\tprivate val cache = ArrayMap<String, CookieWrapper>()\n\tprivate val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n\tprivate var isLoaded = false\n\n\t@WorkerThread\n\t@Synchronized\n\toverride fun loadForRequest(url: HttpUrl): List<Cookie> {\n\t\tloadPersistent()\n\t\tval expired = HashSet<String>()\n\t\tval result = ArrayList<Cookie>()\n\t\tfor ((key, cookie) in cache) {\n\t\t\tif (cookie.isExpired()) {\n\t\t\t\texpired += key\n\t\t\t} else if (cookie.cookie.matches(url)) {\n\t\t\t\tresult += cookie.cookie\n\t\t\t}\n\t\t}\n\t\tif (expired.isNotEmpty()) {\n\t\t\tcache.removeAll(expired)\n\t\t\tremovePersistent(expired)\n\t\t}\n\t\treturn result\n\t}\n\n\t@WorkerThread\n\t@Synchronized\n\toverride fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {\n\t\tval wrapped = cookies.map { CookieWrapper(it) }\n\t\tprefs.edit(commit = true) {\n\t\t\tfor (cookie in wrapped) {\n\t\t\t\tval key = cookie.key()\n\t\t\t\tcache[key] = cookie\n\t\t\t\tif (cookie.cookie.persistent) {\n\t\t\t\t\tputString(key, cookie.encode())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Synchronized\n\t@WorkerThread\n\toverride fun removeCookies(url: HttpUrl, predicate: Predicate<Cookie>?) {\n\t\tloadPersistent()\n\t\tval toRemove = HashSet<String>()\n\t\tfor ((key, cookie) in cache) {\n\t\t\tif (cookie.isExpired() || cookie.cookie.matches(url)) {\n\t\t\t\tif (predicate == null || predicate.test(cookie.cookie)) {\n\t\t\t\t\ttoRemove += key\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (toRemove.isNotEmpty()) {\n\t\t\tcache.removeAll(toRemove)\n\t\t\tremovePersistent(toRemove)\n\t\t}\n\t}\n\n\toverride suspend fun clear(): Boolean {\n\t\tcache.clear()\n\t\twithContext(Dispatchers.IO) {\n\t\t\tprefs.edit(commit = true) { clear() }\n\t\t}\n\t\treturn true\n\t}\n\n\t@Synchronized\n\tprivate fun loadPersistent() {\n\t\tif (!isLoaded) {\n\t\t\tval map = prefs.all\n\t\t\tcache.ensureCapacity(map.size)\n\t\t\tfor ((k, v) in map) {\n\t\t\t\tval cookie = try {\n\t\t\t\t\tCookieWrapper(v as String)\n\t\t\t\t} catch (e: Exception) {\n\t\t\t\t\te.printStackTraceDebug()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcache[k] = cookie\n\t\t\t}\n\t\t\tisLoaded = true\n\t\t}\n\t}\n\n\tprivate fun removePersistent(keys: Collection<String>) {\n\t\tprefs.edit(commit = true) {\n\t\t\tfor (key in keys) {\n\t\t\t\tremove(key)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/imageproxy/BaseImageProxyInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network.imageproxy\n\nimport android.util.Log\nimport androidx.collection.ArraySet\nimport coil3.intercept.Interceptor\nimport coil3.network.HttpException\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.ImageResult\nimport coil3.request.SuccessResult\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.Response\nimport org.jsoup.HttpStatusException\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException\nimport org.koitharu.kotatsu.core.util.ext.ensureSuccess\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.isHttpOrHttps\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.net.HttpURLConnection\nimport java.util.Collections\n\nabstract class BaseImageProxyInterceptor : ImageProxyInterceptor {\n\n\tprivate val blacklist = Collections.synchronizedSet(ArraySet<String>())\n\n\tfinal override suspend fun intercept(chain: Interceptor.Chain): ImageResult {\n\t\tval request = chain.request\n\t\tval url: HttpUrl? = when (val data = request.data) {\n\t\t\tis HttpUrl -> data\n\t\t\tis String -> data.toHttpUrlOrNull()\n\t\t\telse -> null\n\t\t}\n\t\tif (url == null || !url.isHttpOrHttps || url.host in blacklist) {\n\t\t\treturn chain.proceed()\n\t\t}\n\t\tval newRequest = onInterceptImageRequest(request, url)\n\t\treturn when (val result = chain.withRequest(newRequest).proceed()) {\n\t\t\tis SuccessResult -> result\n\t\t\tis ErrorResult -> {\n\t\t\t\tlogDebug(result.throwable, newRequest.data)\n\t\t\t\tchain.proceed().also {\n\t\t\t\t\tif (it is SuccessResult && result.throwable.isBlockedByServer()) {\n\t\t\t\t\t\tblacklist.add(url.host)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfinal override suspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response {\n\t\tval newRequest = onInterceptPageRequest(request)\n\t\treturn runCatchingCancellable {\n\t\t\tokHttp.doCall(newRequest)\n\t\t}.recover { error ->\n\t\t\tlogDebug(error, newRequest.url)\n\t\t\tokHttp.doCall(request).also {\n\t\t\t\tif (error.isBlockedByServer()) {\n\t\t\t\t\tblacklist.add(request.url.host)\n\t\t\t\t}\n\t\t\t}\n\t\t}.getOrThrow()\n\t}\n\n\tprotected abstract suspend fun onInterceptImageRequest(request: ImageRequest, url: HttpUrl): ImageRequest\n\n\tprotected abstract suspend fun onInterceptPageRequest(request: Request): Request\n\n\tprivate suspend fun OkHttpClient.doCall(request: Request): Response {\n\t\treturn newCall(request).await().ensureSuccess()\n\t}\n\n\tprivate fun logDebug(e: Throwable, url: Any) {\n\t\tif (BuildConfig.DEBUG) {\n\t\t\tLog.w(\"ImageProxy\", \"${e.message}: $url\", e)\n\t\t}\n\t}\n\n\tprivate fun Throwable.isBlockedByServer(): Boolean {\n\t\treturn this is CloudFlareBlockedException\n\t\t\t|| (this is HttpException && response.code == HttpURLConnection.HTTP_FORBIDDEN)\n\t\t\t|| (this is HttpStatusException && statusCode == HttpURLConnection.HTTP_FORBIDDEN)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/imageproxy/ImageProxyInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network.imageproxy\n\nimport coil3.intercept.Interceptor\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.Response\n\ninterface ImageProxyInterceptor : Interceptor {\n\n\tsuspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/imageproxy/RealImageProxyInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network.imageproxy\n\nimport coil3.intercept.Interceptor\nimport coil3.request.ImageResult\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.plus\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.Response\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.parsers.util.await\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass RealImageProxyInterceptor @Inject constructor(\n\tprivate val settings: AppSettings,\n) : ImageProxyInterceptor {\n\n\tprivate val delegate = settings.observeAsStateFlow(\n\t\tscope = processLifecycleScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_IMAGES_PROXY,\n\t\tvalueProducer = { createDelegate() },\n\t)\n\n\toverride suspend fun intercept(chain: Interceptor.Chain): ImageResult {\n\t\treturn delegate.value?.intercept(chain) ?: chain.proceed()\n\t}\n\n\toverride suspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response {\n\t\treturn delegate.value?.interceptPageRequest(request, okHttp) ?: okHttp.newCall(request).await()\n\t}\n\n\tprivate fun createDelegate(): ImageProxyInterceptor? = when (val proxy = settings.imagesProxy) {\n\t\t-1 -> null\n\t\t0 -> WsrvNlProxyInterceptor()\n\t\t1 -> ZeroMsProxyInterceptor()\n\t\telse -> error(\"Unsupported images proxy $proxy\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/imageproxy/WsrvNlProxyInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network.imageproxy\n\nimport coil3.request.ImageRequest\nimport coil3.size.Dimension\nimport coil3.size.isOriginal\nimport okhttp3.HttpUrl\nimport okhttp3.Request\n\nclass WsrvNlProxyInterceptor : BaseImageProxyInterceptor() {\n\n\toverride suspend fun onInterceptImageRequest(request: ImageRequest, url: HttpUrl): ImageRequest {\n\t\tval newUrl = HttpUrl.Builder()\n\t\t\t.scheme(\"https\")\n\t\t\t.host(\"wsrv.nl\")\n\t\t\t.addQueryParameter(\"url\", url.toString())\n\t\t\t.addQueryParameter(\"we\", null)\n\t\tval size = request.sizeResolver.size()\n\t\tif (!size.isOriginal) {\n\t\t\tnewUrl.addQueryParameter(\"crop\", \"cover\")\n\t\t\t(size.height as? Dimension.Pixels)?.let { newUrl.addQueryParameter(\"h\", it.toString()) }\n\t\t\t(size.width as? Dimension.Pixels)?.let { newUrl.addQueryParameter(\"w\", it.toString()) }\n\t\t}\n\n\t\treturn request.newBuilder()\n\t\t\t.data(newUrl.build())\n\t\t\t.build()\n\t}\n\n\toverride suspend fun onInterceptPageRequest(request: Request): Request {\n\t\tval sourceUrl = request.url\n\t\tval targetUrl = HttpUrl.Builder()\n\t\t\t.scheme(\"https\")\n\t\t\t.host(\"wsrv.nl\")\n\t\t\t.addQueryParameter(\"url\", sourceUrl.toString())\n\t\t\t.addQueryParameter(\"we\", null)\n\t\treturn request.newBuilder()\n\t\t\t.url(targetUrl.build())\n\t\t\t.build()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/imageproxy/ZeroMsProxyInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network.imageproxy\n\nimport coil3.request.ImageRequest\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.Request\n\nclass ZeroMsProxyInterceptor : BaseImageProxyInterceptor() {\n\n\toverride suspend fun onInterceptImageRequest(request: ImageRequest, url: HttpUrl): ImageRequest {\n\t\tif (url.host == \"v.recipes\") {\n\t\t\treturn request\n\t\t}\n\t\tval newUrl = (\"https://v.recipes/i/$url\").toHttpUrl()\n\t\treturn request.newBuilder()\n\t\t\t.data(newUrl)\n\t\t\t.build()\n\t}\n\n\toverride suspend fun onInterceptPageRequest(request: Request): Request {\n\t\tval newUrl = (\"https://v.recipes/i/${request.url}\").toHttpUrl()\n\t\treturn request.newBuilder()\n\t\t\t.url(newUrl)\n\t\t\t.build()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/proxy/ProxyProvider.kt",
    "content": "package org.koitharu.kotatsu.core.network.proxy\n\nimport androidx.webkit.ProxyConfig\nimport androidx.webkit.ProxyController\nimport androidx.webkit.WebViewFeature\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.asExecutor\nimport okhttp3.Authenticator\nimport okhttp3.Credentials\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Route\nimport okio.IOException\nimport org.koitharu.kotatsu.core.exceptions.ProxyConfigException\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport java.net.InetSocketAddress\nimport java.net.PasswordAuthentication\nimport java.net.Proxy\nimport java.net.ProxySelector\nimport java.net.SocketAddress\nimport java.net.URI\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\nimport java.net.Authenticator as JavaAuthenticator\n\n@Singleton\nclass ProxyProvider @Inject constructor(\n\tprivate val settings: AppSettings,\n) {\n\n\tprivate var cachedProxy: Proxy? = null\n\n\tval selector = object : ProxySelector() {\n\t\toverride fun select(uri: URI?): List<Proxy> {\n\t\t\treturn listOf(getProxy())\n\t\t}\n\n\t\toverride fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {\n\t\t\tioe?.printStackTraceDebug()\n\t\t}\n\t}\n\n\tval authenticator = ProxyAuthenticator()\n\n\tinit {\n\t\tProxySelector.setDefault(selector)\n\t\tJavaAuthenticator.setDefault(authenticator)\n\t}\n\n\tsuspend fun applyWebViewConfig() {\n\t\tval isProxyEnabled = isProxyEnabled()\n\t\tif (!WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {\n\t\t\tif (isProxyEnabled) {\n\t\t\t\tthrow IllegalArgumentException(\"Proxy for WebView is not supported\") // TODO localize\n\t\t\t}\n\t\t} else {\n\t\t\tval controller = ProxyController.getInstance()\n\t\t\tif (settings.proxyType == Proxy.Type.DIRECT) {\n\t\t\t\tsuspendCoroutine { cont ->\n\t\t\t\t\tcontroller.clearProxyOverride(\n\t\t\t\t\t\t(cont.context[CoroutineDispatcher] ?: Dispatchers.Main).asExecutor(),\n\t\t\t\t\t) {\n\t\t\t\t\t\tcont.resume(Unit)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tval url = buildString {\n\t\t\t\t\twhen (settings.proxyType) {\n\t\t\t\t\t\tProxy.Type.DIRECT -> Unit\n\t\t\t\t\t\tProxy.Type.HTTP -> append(\"http\")\n\t\t\t\t\t\tProxy.Type.SOCKS -> append(\"socks\")\n\t\t\t\t\t}\n\t\t\t\t\tappend(\"://\")\n\t\t\t\t\tappend(settings.proxyAddress)\n\t\t\t\t\tappend(':')\n\t\t\t\t\tappend(settings.proxyPort)\n\t\t\t\t}\n\t\t\t\tif (settings.proxyType == Proxy.Type.SOCKS) {\n\t\t\t\t\tSystem.setProperty(\"java.net.socks.username\", settings.proxyLogin)\n\t\t\t\t\tSystem.setProperty(\"java.net.socks.password\", settings.proxyPassword)\n\t\t\t\t}\n\t\t\t\tval proxyConfig = ProxyConfig.Builder()\n\t\t\t\t\t.addProxyRule(url)\n\t\t\t\t\t.build()\n\t\t\t\tsuspendCoroutine { cont ->\n\t\t\t\t\tcontroller.setProxyOverride(\n\t\t\t\t\t\tproxyConfig,\n\t\t\t\t\t\t(cont.context[CoroutineDispatcher] ?: Dispatchers.Main).asExecutor(),\n\t\t\t\t\t) {\n\t\t\t\t\t\tcont.resume(Unit)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun isProxyEnabled() = settings.proxyType != Proxy.Type.DIRECT\n\n\tprivate fun getProxy(): Proxy {\n\t\tval type = settings.proxyType\n\t\tval address = settings.proxyAddress\n\t\tval port = settings.proxyPort\n\t\tif (type == Proxy.Type.DIRECT) {\n\t\t\treturn Proxy.NO_PROXY\n\t\t}\n\t\tif (address.isNullOrEmpty() || port < 0 || port > 0xFFFF) {\n\t\t\tthrow ProxyConfigException()\n\t\t}\n\t\tcachedProxy?.let {\n\t\t\tval addr = it.address() as? InetSocketAddress\n\t\t\tif (addr != null && it.type() == type && addr.port == port && addr.hostString == address) {\n\t\t\t\treturn it\n\t\t\t}\n\t\t}\n\t\tval proxy = Proxy(type, InetSocketAddress(address, port))\n\t\tcachedProxy = proxy\n\t\treturn proxy\n\t}\n\n\tinner class ProxyAuthenticator : Authenticator, JavaAuthenticator() {\n\n\t\toverride fun authenticate(route: Route?, response: Response): Request? {\n\t\t\tif (!isProxyEnabled()) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (response.request.header(CommonHeaders.PROXY_AUTHORIZATION) != null) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tval login = settings.proxyLogin ?: return null\n\t\t\tval password = settings.proxyPassword ?: return null\n\t\t\tval credential = Credentials.basic(login, password)\n\t\t\treturn response.request.newBuilder()\n\t\t\t\t.header(CommonHeaders.PROXY_AUTHORIZATION, credential)\n\t\t\t\t.build()\n\t\t}\n\n\t\tpublic override fun getPasswordAuthentication(): PasswordAuthentication? {\n\t\t\tif (!isProxyEnabled()) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tval login = settings.proxyLogin ?: return null\n\t\t\tval password = settings.proxyPassword ?: return null\n\t\t\treturn PasswordAuthentication(login, password.toCharArray())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/CaptchaContinuationClient.kt",
    "content": "package org.koitharu.kotatsu.core.network.webview\n\nimport android.graphics.Bitmap\nimport android.webkit.WebView\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\nimport kotlin.coroutines.Continuation\n\nclass CaptchaContinuationClient(\n\tprivate val cookieJar: MutableCookieJar,\n\tprivate val targetUrl: String,\n\tcontinuation: Continuation<Unit>,\n) : ContinuationResumeWebViewClient(continuation) {\n\n\tprivate val oldClearance = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl)\n\n\toverride fun onPageFinished(view: WebView?, url: String?) = Unit\n\n\toverride fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n\t\tsuper.onPageStarted(view, url, favicon)\n\t\tcheckClearance(view)\n\t}\n\n\tprivate fun checkClearance(view: WebView?) {\n\t\tval clearance = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl)\n\t\tif (clearance != null && clearance != oldClearance) {\n\t\t\tresumeContinuation(view)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/ContinuationResumeWebViewClient.kt",
    "content": "package org.koitharu.kotatsu.core.network.webview\n\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport kotlinx.coroutines.CancellableContinuation\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\n\nopen class ContinuationResumeWebViewClient(\n\tprivate val continuation: Continuation<Unit>,\n) : WebViewClient() {\n\n\toverride fun onPageFinished(view: WebView?, url: String?) {\n\t\tresumeContinuation(view)\n\t}\n\n\tprotected fun resumeContinuation(view: WebView?) {\n\t\tif (continuation !is CancellableContinuation || continuation.isActive) {\n\t\t\tview?.webViewClient = WebViewClient() // reset to default\n\t\t\tcontinuation.resume(Unit)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/WebViewExecutor.kt",
    "content": "package org.koitharu.kotatsu.core.network.webview\n\nimport android.content.Context\nimport android.util.AndroidRuntimeException\nimport android.webkit.WebSettings\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.annotation.MainThread\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport kotlinx.coroutines.withTimeout\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareException\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.core.network.proxy.ProxyProvider\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.util.ext.configureForParser\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.lang.ref.WeakReference\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport javax.inject.Singleton\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n@Singleton\nclass WebViewExecutor @Inject constructor(\n\t@ApplicationContext private val context: Context,\n\tprivate val proxyProvider: ProxyProvider,\n\tprivate val cookieJar: MutableCookieJar,\n\tprivate val mangaRepositoryFactoryProvider: Provider<MangaRepository.Factory>,\n) {\n\n\tprivate var webViewCached: WeakReference<WebView>? = null\n\tprivate val mutex = Mutex()\n\n\tval defaultUserAgent: String? by lazy {\n\t\ttry {\n\t\t\tWebSettings.getDefaultUserAgent(context)\n\t\t} catch (e: AndroidRuntimeException) {\n\t\t\te.printStackTraceDebug()\n\t\t\t// Probably WebView is not available\n\t\t\tnull\n\t\t}\n\t}\n\n\tsuspend fun evaluateJs(baseUrl: String?, script: String): String? = mutex.withLock {\n\t\twithContext(Dispatchers.Main.immediate) {\n\t\t\tval webView = obtainWebView()\n\t\t\ttry {\n\t\t\t\tif (!baseUrl.isNullOrEmpty()) {\n\t\t\t\t\tsuspendCoroutine { cont ->\n\t\t\t\t\t\twebView.webViewClient = ContinuationResumeWebViewClient(cont)\n\t\t\t\t\t\twebView.loadDataWithBaseURL(baseUrl, \" \", \"text/html\", null, null)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsuspendCoroutine { cont ->\n\t\t\t\t\twebView.evaluateJavascript(script) { result ->\n\t\t\t\t\t\tcont.resume(result?.takeUnless { it == \"null\" })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\twebView.reset()\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun tryResolveCaptcha(exception: CloudFlareException, timeout: Long): Boolean = mutex.withLock {\n\t\trunCatchingCancellable {\n\t\t\twithContext(Dispatchers.Main.immediate) {\n\t\t\t\tval webView = obtainWebView()\n\t\t\t\ttry {\n\t\t\t\t\texception.source.getUserAgent()?.let {\n\t\t\t\t\t\twebView.settings.userAgentString = it\n\t\t\t\t\t}\n\t\t\t\t\twithTimeout(timeout) {\n\t\t\t\t\t\tsuspendCancellableCoroutine { cont ->\n\t\t\t\t\t\t\twebView.webViewClient = CaptchaContinuationClient(\n\t\t\t\t\t\t\t\tcookieJar = cookieJar,\n\t\t\t\t\t\t\t\ttargetUrl = exception.url,\n\t\t\t\t\t\t\t\tcontinuation = cont,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\twebView.loadUrl(exception.url)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\twebView.reset()\n\t\t\t\t}\n\t\t\t}\n\t\t}.onFailure { e ->\n\t\t\texception.addSuppressed(e)\n\t\t\te.printStackTraceDebug()\n\t\t}.isSuccess\n\t}\n\n\tprivate suspend fun obtainWebView(): WebView {\n\t\twebViewCached?.get()?.let {\n\t\t\treturn it\n\t\t}\n\t\treturn withContext(Dispatchers.Main.immediate) {\n\t\t\twebViewCached?.get()?.let {\n\t\t\t\treturn@withContext it\n\t\t\t}\n\t\t\tWebView(context).also {\n\t\t\t\tit.configureForParser(null)\n\t\t\t\twebViewCached = WeakReference(it)\n\t\t\t\tproxyProvider.applyWebViewConfig()\n\t\t\t\tit.onResume()\n\t\t\t\tit.resumeTimers()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun MangaSource.getUserAgent(): String? {\n\t\tval repository = mangaRepositoryFactoryProvider.get().create(this) as? ParserMangaRepository\n\t\treturn repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)\n\t}\n\n\t@MainThread\n\tprivate fun WebView.reset() {\n\t\tstopLoading()\n\t\twebViewClient = WebViewClient()\n\t\tsettings.userAgentString = defaultUserAgent\n\t\tloadDataWithBaseURL(null, \" \", \"text/html\", null, null)\n\t\tclearHistory()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/adblock/AdBlock.kt",
    "content": "package org.koitharu.kotatsu.core.network.webview.adblock\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.annotation.WorkerThread\nimport dagger.Reusable\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okio.sink\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.isNotEmpty\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.requireBody\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\nimport java.net.HttpURLConnection\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\nimport javax.inject.Inject\n\n@Reusable\nclass AdBlock @Inject constructor(\n\t@ApplicationContext private val context: Context,\n\tprivate val settings: AppSettings,\n) {\n\n\tprivate var rules: RulesList? = null\n\n\t@WorkerThread\n\tfun shouldLoadUrl(url: String, baseUrl: String?): Boolean {\n\t\treturn shouldLoadUrl(\n\t\t\turl.lowercase().toHttpUrlOrNull() ?: return true,\n\t\t\tbaseUrl?.lowercase()?.toHttpUrlOrNull(),\n\t\t)\n\t}\n\n\t@WorkerThread\n\tfun shouldLoadUrl(url: HttpUrl, baseUrl: HttpUrl?): Boolean {\n\t\tif (!settings.isAdBlockEnabled) {\n\t\t\treturn true\n\t\t}\n\t\treturn synchronized(this) {\n\t\t\trules ?: parseRules().also { rules = it }\n\t\t}?.let {\n\t\t\tval rule = it[url, baseUrl]\n\t\t\tif (rule != null) {\n\t\t\t\tLog.i(TAG, \"Blocked $url by $rule\")\n\t\t\t}\n\t\t\trule == null\n\t\t} ?: true\n\t}\n\n\t@WorkerThread\n\tprivate fun parseRules() = runCatchingCancellable {\n\t\tlistFile(context).useLines { lines ->\n\t\t\tval rules = RulesList()\n\t\t\tlines.forEach { line -> rules.add(line) }\n\t\t\trules.trimToSize()\n\t\t\trules\n\t\t}\n\t}.onFailure { e ->\n\t\te.printStackTraceDebug()\n\t}.getOrNull()\n\n\tclass Updater @Inject constructor(\n\t\t@ApplicationContext private val context: Context,\n\t\t@BaseHttpClient private val okHttpClient: OkHttpClient,\n\t) {\n\n\t\tsuspend fun updateList() {\n\t\t\tval file = listFile(context)\n\t\t\tval dateFormat = SimpleDateFormat(CommonHeaders.DATE_FORMAT, Locale.ENGLISH)\n\t\t\tval requestBuilder = Request.Builder()\n\t\t\t\t.url(EASYLIST_URL)\n\t\t\t\t.get()\n\t\t\tif (file.exists() && file.isNotEmpty()) {\n\t\t\t\tval lastModified = file.lastModified()\n\t\t\t\trequestBuilder.header(CommonHeaders.IF_MODIFIED_SINCE, dateFormat.format(Date(lastModified)))\n\t\t\t}\n\t\t\tokHttpClient.newCall(\n\t\t\t\trequestBuilder.build(),\n\t\t\t).await().use { response ->\n\t\t\t\tif (response.code == HttpURLConnection.HTTP_NOT_MODIFIED) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tval lastModified = response.header(CommonHeaders.LAST_MODIFIED)?.let {\n\t\t\t\t\trunCatching {\n\t\t\t\t\t\tdateFormat.parse(it)\n\t\t\t\t\t}.getOrNull()\n\t\t\t\t}?.time ?: System.currentTimeMillis()\n\t\t\t\tresponse.requireBody().source().use { source ->\n\t\t\t\t\tfile.sink().use { sink ->\n\t\t\t\t\t\tsource.readAll(sink)\n\t\t\t\t\t}\n\t\t\t\t\tfile.setLastModified(lastModified)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tprivate companion object {\n\n\t\tfun listFile(context: Context): File {\n\t\t\tval root = File(context.externalCacheDir ?: context.cacheDir, LIST_DIR)\n\t\t\troot.mkdir()\n\t\t\treturn File(root, LIST_FILENAME)\n\t\t}\n\n\t\tprivate const val LIST_FILENAME = \"easylist.txt\"\n\t\tprivate const val LIST_DIR = \"adblock\"\n\t\tprivate const val EASYLIST_URL = \"https://easylist.to/easylist/easylist.txt\"\n\t\tprivate const val TAG = \"AdBlock\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/adblock/CSSRuleBuilder.kt",
    "content": "package org.koitharu.kotatsu.core.network.webview.adblock\n\nimport androidx.collection.ArraySet\n\nclass CSSRuleBuilder {\n\n\tprivate val selectors = ArraySet<String>()\n\n\tfun add(selector: String) {\n\t\tselectors.add(selector)\n\t}\n\n\tfun build() = buildString {\n\t\tappend(\"<style> {\")\n\t\tfor (selector in selectors) {\n\t\t\tappend(selector)\n\t\t\tappend(\";\")\n\t\t}\n\t\tappend(\"}!important</style>\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/adblock/Rule.kt",
    "content": "package org.koitharu.kotatsu.core.network.webview.adblock\n\nimport okhttp3.HttpUrl\n\nsealed interface Rule {\n\n\toperator fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean\n\n\tdata class Domain(private val domain: String) : Rule {\n\n\t\toverride fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean = (url.topPrivateDomain() ?: url.host) == domain\n\t}\n\n\tdata class ExactUrl(private val url: HttpUrl) : Rule {\n\n\t\toverride operator fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean = url == this.url\n\t}\n\n\tdata class Path(private val path: String, private val contains: Boolean) : Rule {\n\n\t\toverride fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean {\n\t\t\tval fullPath = url.host + \"/\" + url.encodedPath\n\t\t\treturn if (contains) {\n\t\t\t\tfullPath.contains(path)\n\t\t\t} else {\n\t\t\t\tfullPath.endsWith(path)\n\t\t\t}\n\t\t}\n\t}\n\n\tdata class WithModifiers(\n\t\tprivate val baseRule: Rule,\n\t\tprivate val script: Boolean?,\n\t\tprivate val thirdParty: Boolean?,\n\t\tprivate val domains: Set<String>?,\n\t\tprivate val domainsNot: Set<String>?,\n\t) : Rule {\n\n\t\toverride fun invoke(url: HttpUrl, baseUrl: HttpUrl?): Boolean {\n\t\t\tif (!baseRule.invoke(url, baseUrl)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (baseUrl == null) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tthirdParty?.let {\n\t\t\t\tval isThirdPartyRequest =\n\t\t\t\t\t(url.topPrivateDomain() ?: url.host) != (baseUrl.topPrivateDomain() ?: baseUrl.host)\n\t\t\t\tif (isThirdPartyRequest != it) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\t// TODO check other modifiers\n\t\t\treturn true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/network/webview/adblock/RulesList.kt",
    "content": "package org.koitharu.kotatsu.core.network.webview.adblock\n\nimport androidx.annotation.CheckResult\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\n\n/**\n * Very simple implementation of adblock list parser\n * Not all features are supported\n */\nclass RulesList {\n\n\tprivate val blockRules = ArrayList<Rule>()\n\tprivate val allowRules = ArrayList<Rule>()\n\n\toperator fun get(url: HttpUrl, baseUrl: HttpUrl?): Rule? {\n\t\tval rule = blockRules.find { x -> x(url, baseUrl) }\n\t\treturn rule?.takeIf { allowRules.none { x -> x(url, baseUrl) } }\n\t}\n\n\tfun add(line: String) {\n\t\tval parts = line.lowercase().trim().split('$')\n\t\tparts.first().addImpl(isWhitelist = false, modifiers = parts.getOrNull(1))\n\t}\n\n\tfun trimToSize() {\n\t\tblockRules.trimToSize()\n\t\tallowRules.trimToSize()\n\t}\n\n\tprivate fun String.addImpl(isWhitelist: Boolean, modifiers: String?) {\n\t\tval list = if (isWhitelist) allowRules else blockRules\n\n\t\twhen {\n\t\t\tstartsWith('!') || startsWith('[') -> {\n\t\t\t\t// Comment, do nothing\n\t\t\t}\n\n\t\t\tstartsWith(\"||\") -> {\n\t\t\t\t// domain\n\t\t\t\tlist += Rule.Domain(substring(2).substringBefore('^').trim()).withModifiers(modifiers)\n\t\t\t}\n\n\t\t\tstartsWith('|') -> {\n\t\t\t\tval url = substring(1).substringBefore('^').trim().toHttpUrlOrNull()\n\t\t\t\tif (url != null) {\n\t\t\t\t\tlist += Rule.ExactUrl(url).withModifiers(modifiers)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstartsWith(\"@@\") -> {\n\t\t\t\tsubstring(2).substringBefore('^').trim().addImpl(!isWhitelist, modifiers)\n\t\t\t}\n\n\t\t\tstartsWith(\"##\") -> {\n\t\t\t\t// TODO css rules\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tif (endsWith('*')) {\n\t\t\t\t\tlist += Rule.Path(this.dropLast(1), contains = true).withModifiers(modifiers)\n\t\t\t\t} else if (!contains('*')) { // wildcards is not supported yet\n\t\t\t\t\tlist += Rule.Path(this, contains = false).withModifiers(modifiers)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@CheckResult\n\tprivate fun Rule.withModifiers(options: String?): Rule {\n\t\tif (options.isNullOrEmpty()) {\n\t\t\treturn this\n\t\t}\n\t\tvar script: Boolean? = null\n\t\tvar thirdParty: Boolean? = null\n\t\toptions.split(',').forEach {\n\t\t\tval isNot = it.startsWith('~')\n\t\t\twhen (it.removePrefix(\"~\")) {\n\t\t\t\t\"script\" -> script = !isNot\n\t\t\t\t\"third-party\" -> thirdParty = !isNot\n\t\t\t}\n\t\t}\n\t\treturn Rule.WithModifiers(\n\t\t\tbaseRule = this,\n\t\t\tscript = script,\n\t\t\tthirdParty = thirdParty,\n\t\t\tdomains = null, //TODO\n\t\t\tdomainsNot = null, //TODO\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.content.pm.ShortcutManager\nimport android.os.Build\nimport androidx.annotation.VisibleForTesting\nimport androidx.core.content.pm.ShortcutInfoCompat\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.graphics.drawable.IconCompat\nimport androidx.core.graphics.drawable.toBitmap\nimport androidx.room.InvalidationTracker\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport coil3.request.transformations\nimport coil3.size.Scale\nimport coil3.size.Size\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.db.TABLE_HISTORY\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.favicon.faviconUri\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.image.ThumbnailTransformation\nimport org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.mapNotNullToSet\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass AppShortcutManager @Inject constructor(\n\t@LocalizedAppContext private val context: Context,\n\tprivate val coil: ImageLoader,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val mangaRepository: MangaDataRepository,\n\tprivate val settings: AppSettings,\n) : InvalidationTracker.Observer(TABLE_HISTORY), SharedPreferences.OnSharedPreferenceChangeListener {\n\n\tprivate val iconSize by lazy {\n\t\tSize(ShortcutManagerCompat.getIconMaxWidth(context), ShortcutManagerCompat.getIconMaxHeight(context))\n\t}\n\tprivate var shortcutsUpdateJob: Job? = null\n\n\tinit {\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onInvalidated(tables: Set<String>) {\n\t\tif (!settings.isDynamicShortcutsEnabled) {\n\t\t\treturn\n\t\t}\n\t\tval prevJob = shortcutsUpdateJob\n\t\tshortcutsUpdateJob = processLifecycleScope.launch(Dispatchers.Default) {\n\t\t\tprevJob?.join()\n\t\t\tupdateShortcutsImpl()\n\t\t}\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\tif (key == AppSettings.KEY_SHORTCUTS) {\n\t\t\tif (settings.isDynamicShortcutsEnabled) {\n\t\t\t\tonInvalidated(emptySet())\n\t\t\t} else {\n\t\t\t\tclearShortcuts()\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun requestPinShortcut(manga: Manga): Boolean = try {\n\t\tShortcutManagerCompat.requestPinShortcut(context, buildShortcutInfo(manga), null)\n\t} catch (e: IllegalStateException) {\n\t\te.printStackTraceDebug()\n\t\tfalse\n\t}\n\n\tsuspend fun requestPinShortcut(source: MangaSource): Boolean = try {\n\t\tShortcutManagerCompat.requestPinShortcut(context, buildShortcutInfo(source), null)\n\t} catch (e: IllegalStateException) {\n\t\te.printStackTraceDebug()\n\t\tfalse\n\t}\n\n\tfun getMangaShortcuts(): Set<Long> {\n\t\tval shortcuts = ShortcutManagerCompat.getShortcuts(\n\t\t\tcontext,\n\t\t\tShortcutManagerCompat.FLAG_MATCH_CACHED or ShortcutManagerCompat.FLAG_MATCH_PINNED or ShortcutManagerCompat.FLAG_MATCH_DYNAMIC,\n\t\t)\n\t\treturn shortcuts.mapNotNullToSet { it.id.toLongOrNull() }\n\t}\n\n\t@VisibleForTesting\n\tsuspend fun await(): Boolean {\n\t\treturn shortcutsUpdateJob?.join() != null\n\t}\n\n\tfun notifyMangaOpened(mangaId: Long) {\n\t\tShortcutManagerCompat.reportShortcutUsed(context, mangaId.toString())\n\t}\n\n\tfun isDynamicShortcutsAvailable(): Boolean {\n\t\treturn Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 &&\n\t\t\tcontext.getSystemService(ShortcutManager::class.java).maxShortcutCountPerActivity > 0\n\t}\n\n\tprivate suspend fun updateShortcutsImpl() = runCatchingCancellable {\n\t\tval maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context).coerceAtLeast(5)\n\t\tval shortcuts = historyRepository.getList(0, maxShortcuts)\n\t\t\t.filter { x -> x.title.isNotEmpty() }\n\t\t\t.map { buildShortcutInfo(it) }\n\t\tShortcutManagerCompat.setDynamicShortcuts(context, shortcuts)\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}\n\n\tprivate fun clearShortcuts() {\n\t\ttry {\n\t\t\tShortcutManagerCompat.removeAllDynamicShortcuts(context)\n\t\t} catch (_: IllegalStateException) {\n\t\t}\n\t}\n\n\tprivate suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat = withContext(Dispatchers.Default) {\n\t\tval icon = runCatchingCancellable {\n\t\t\tcoil.execute(\n\t\t\t\tImageRequest.Builder(context)\n\t\t\t\t\t.data(manga.coverUrl)\n\t\t\t\t\t.size(iconSize)\n\t\t\t\t\t.mangaSourceExtra(manga.source)\n\t\t\t\t\t.scale(Scale.FILL)\n\t\t\t\t\t.transformations(ThumbnailTransformation())\n\t\t\t\t\t.build(),\n\t\t\t).getDrawableOrThrow().toBitmap()\n\t\t}.fold(\n\t\t\tonSuccess = { IconCompat.createWithAdaptiveBitmap(it) },\n\t\t\tonFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },\n\t\t)\n\t\tmangaRepository.storeManga(manga, replaceExisting = true)\n\t\tval title = manga.title.ifEmpty {\n\t\t\tmanga.altTitles.firstOrNull()\n\t\t}.ifNullOrEmpty {\n\t\t\tcontext.getString(R.string.unknown)\n\t\t}\n\t\tShortcutInfoCompat.Builder(context, manga.id.toString())\n\t\t\t.setShortLabel(title)\n\t\t\t.setLongLabel(title)\n\t\t\t.setIcon(icon)\n\t\t\t.setLongLived(true)\n\t\t\t.setIntent(\n\t\t\t\tReaderIntent.Builder(context)\n\t\t\t\t\t.mangaId(manga.id)\n\t\t\t\t\t.build()\n\t\t\t\t\t.intent,\n\t\t\t).build()\n\t}\n\n\tprivate suspend fun buildShortcutInfo(source: MangaSource): ShortcutInfoCompat = withContext(Dispatchers.Default) {\n\t\tval icon = runCatchingCancellable {\n\t\t\tcoil.execute(\n\t\t\t\tImageRequest.Builder(context)\n\t\t\t\t\t.data(source.faviconUri())\n\t\t\t\t\t.mangaSourceExtra(source)\n\t\t\t\t\t.size(iconSize)\n\t\t\t\t\t.scale(Scale.FIT)\n\t\t\t\t\t.build(),\n\t\t\t).getDrawableOrThrow().toBitmap()\n\t\t}.fold(\n\t\t\tonSuccess = { IconCompat.createWithAdaptiveBitmap(it) },\n\t\t\tonFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },\n\t\t)\n\t\tval title = source.getTitle(context)\n\t\tShortcutInfoCompat.Builder(context, source.name)\n\t\t\t.setShortLabel(title)\n\t\t\t.setLongLabel(title)\n\t\t\t.setIcon(icon)\n\t\t\t.setLongLived(true)\n\t\t\t.setIntent(AppRouter.listIntent(context, source, null, null))\n\t\t\t.build()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppValidator.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport androidx.core.content.pm.PackageInfoCompat\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass AppValidator @Inject constructor(\n\t@ApplicationContext private val context: Context,\n) {\n\t@SuppressLint(\"InlinedApi\")\n\tval isOriginalApp = suspendLazy(Dispatchers.Default) {\n\t\tval certificates = mapOf(CERT_SHA256.hexToByteArray() to PackageManager.CERT_INPUT_SHA256)\n\t\tPackageInfoCompat.hasSignatures(context.packageManager, context.packageName, certificates, false)\n\t}\n\n\tprivate companion object {\n\t\tprivate const val CERT_SHA256 = \"67e15100bb809301783edcb6348fa3bbf83034d91e62868a91053dbd70db3f18\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkManageIntent.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport android.content.Intent\nimport android.os.Build\nimport android.provider.Settings\n\n@Suppress(\"FunctionName\")\nfun NetworkManageIntent(): Intent {\n\tval action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\tSettings.Panel.ACTION_INTERNET_CONNECTIVITY\n\t} else {\n\t\tSettings.ACTION_WIRELESS_SETTINGS\n\t}\n\treturn Intent(action)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport android.net.ConnectivityManager\nimport android.net.ConnectivityManager.NetworkCallback\nimport android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED\nimport android.net.Network\nimport android.net.NetworkCapabilities\nimport android.net.NetworkRequest\nimport android.os.Build\nimport coil3.network.ConnectivityChecker\nimport kotlinx.coroutines.flow.first\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.MediatorStateFlow\n\nclass NetworkState(\n\tprivate val connectivityManager: ConnectivityManager,\n\tprivate val settings: AppSettings,\n) : MediatorStateFlow<Boolean>(connectivityManager.isOnline(settings)), ConnectivityChecker {\n\n\tprivate val callback = NetworkCallbackImpl()\n\n\toverride val value: Boolean\n\t\tget() = connectivityManager.isOnline(settings)\n\n\toverride fun isOnline(): Boolean {\n\t\treturn connectivityManager.isOnline(settings)\n\t}\n\n\t@Synchronized\n\toverride fun onActive() {\n\t\tinvalidate()\n\t\tval request = NetworkRequest.Builder()\n\t\t\t.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)\n\t\t\t.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)\n\t\t\t.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)\n\t\t\t.addTransportType(NetworkCapabilities.TRANSPORT_VPN)\n\t\t\t.build()\n\t\tconnectivityManager.registerNetworkCallback(request, callback)\n\t}\n\n\t@Synchronized\n\toverride fun onInactive() {\n\t\tconnectivityManager.unregisterNetworkCallback(callback)\n\t}\n\n\tfun isMetered(): Boolean {\n\t\treturn connectivityManager.isActiveNetworkMetered\n\t}\n\n\tfun isDataSaverEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N\n\t\t&& connectivityManager.restrictBackgroundStatus == RESTRICT_BACKGROUND_STATUS_ENABLED\n\n\tfun isRestricted() = isMetered() && isDataSaverEnabled()\n\n\tfun isOfflineOrRestricted() = !isOnline() || isRestricted()\n\n\tsuspend fun awaitForConnection() {\n\t\tif (value) {\n\t\t\treturn\n\t\t}\n\t\tfirst { it }\n\t}\n\n\tprivate fun invalidate() {\n\t\tpublishValue(connectivityManager.isOnline(settings))\n\t}\n\n\tprivate inner class NetworkCallbackImpl : NetworkCallback() {\n\n\t\toverride fun onAvailable(network: Network) = invalidate()\n\n\t\toverride fun onLost(network: Network) = invalidate()\n\n\t\toverride fun onUnavailable() = invalidate()\n\t}\n\n\tprivate companion object {\n\n\t\tfun ConnectivityManager.isOnline(settings: AppSettings): Boolean {\n\t\t\tif (settings.isOfflineCheckDisabled) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn activeNetwork?.let { isOnline(it) } == true\n\t\t}\n\n\t\tprivate fun ConnectivityManager.isOnline(network: Network): Boolean {\n\t\t\tval capabilities = getNetworkCapabilities(network) ?: return false\n\t\t\treturn capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)\n\t\t\t\t|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)\n\t\t\t\t|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)\n\t\t\t\t|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/os/OpenDocumentTreeHelper.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.storage.StorageManager\nimport android.provider.DocumentsContract\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.ActivityResultCaller\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.RequiresApi\nimport androidx.core.app.ActivityOptionsCompat\n\n// https://stackoverflow.com/questions/77555641/saf-no-activity-found-to-handle-intent-android-intent-action-open-document-tr\nclass OpenDocumentTreeHelper(\n\tactivityResultCaller: ActivityResultCaller,\n\tflags: Int,\n\tcallback: ActivityResultCallback<Uri?>\n) : ActivityResultLauncher<Uri?>() {\n\n\tconstructor(activityResultCaller: ActivityResultCaller, callback: ActivityResultCallback<Uri?>) : this(\n\t\tactivityResultCaller,\n\t\t0,\n\t\tcallback,\n\t)\n\n\tprivate val pickFileTreeLauncherPrimaryStorage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\tactivityResultCaller.registerForActivityResult(OpenDocumentTreeContractPrimaryStorage(flags), callback)\n\t} else {\n\t\tnull\n\t}\n\tprivate val pickFileTreeLauncherDefault = activityResultCaller.registerForActivityResult(\n\t\tcontract = OpenDocumentTreeContractDefault(flags),\n\t\tcallback = callback,\n\t)\n\n\toverride fun launch(input: Uri?, options: ActivityOptionsCompat?) {\n\t\ttry {\n\t\t\tpickFileTreeLauncherDefault.launch(input, options)\n\t\t} catch (e: Exception) {\n\t\t\tif (pickFileTreeLauncherPrimaryStorage != null) {\n\t\t\t\ttry {\n\t\t\t\t\tpickFileTreeLauncherPrimaryStorage.launch(input, options)\n\t\t\t\t} catch (e2: Exception) {\n\t\t\t\t\te.addSuppressed(e2)\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun unregister() {\n\t\tpickFileTreeLauncherPrimaryStorage?.unregister()\n\t\tpickFileTreeLauncherDefault.unregister()\n\t}\n\n\toverride val contract: ActivityResultContract<Uri?, *>\n\t\tget() = pickFileTreeLauncherPrimaryStorage?.contract ?: pickFileTreeLauncherDefault.contract\n\n\tprivate open class OpenDocumentTreeContractDefault(\n\t\tprivate val flags: Int,\n\t) : ActivityResultContracts.OpenDocumentTree() {\n\n\t\toverride fun createIntent(context: Context, input: Uri?): Intent {\n\t\t\tval intent = super.createIntent(context, input)\n\t\t\tintent.addFlags(flags)\n\t\t\treturn intent\n\t\t}\n\t}\n\n\t@RequiresApi(Build.VERSION_CODES.Q)\n\tprivate class OpenDocumentTreeContractPrimaryStorage(\n\t\tprivate val flags: Int,\n\t) : OpenDocumentTreeContractDefault(flags) {\n\n\t\toverride fun createIntent(context: Context, input: Uri?): Intent {\n\t\t\tval intent = (context.getSystemService(Context.STORAGE_SERVICE) as? StorageManager)\n\t\t\t\t?.primaryStorageVolume\n\t\t\t\t?.createOpenDocumentTreeIntent()\n\t\t\tif (intent == null) { // fallback\n\t\t\t\treturn super.createIntent(context, input)\n\t\t\t}\n\t\t\tintent.addFlags(flags)\n\t\t\tif (input != null) {\n\t\t\t\tintent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input)\n\t\t\t}\n\t\t\treturn intent\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/os/RomCompat.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport kotlinx.coroutines.Dispatchers\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport java.io.InputStreamReader\n\nobject RomCompat {\n\n\tval isMiui = suspendLazy(Dispatchers.IO) {\n\t\tgetProp(\"ro.miui.ui.version.name\").isNotEmpty()\n\t}\n\n\t@Blocking\n\tprivate fun getProp(propName: String) = Runtime.getRuntime().exec(\"getprop $propName\").inputStream.use {\n\t\tit.reader().use(InputStreamReader::readText).trim()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/os/VoiceInputContract.kt",
    "content": "package org.koitharu.kotatsu.core.os\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport android.speech.RecognizerIntent\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.core.os.ConfigurationCompat\nimport java.util.Locale\n\nclass VoiceInputContract : ActivityResultContract<String?, String?>() {\n\n\toverride fun createIntent(context: Context, input: String?): Intent {\n\t\tval intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)\n\t\tintent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)\n\t\tval locale = ConfigurationCompat.getLocales(context.resources.configuration).get(0) ?: Locale.getDefault()\n\t\tintent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, locale.toLanguageTag())\n\t\tintent.putExtra(RecognizerIntent.EXTRA_PROMPT, input)\n\t\treturn intent\n\t}\n\n\toverride fun parseResult(resultCode: Int, intent: Intent?): String? {\n\t\treturn if (resultCode == Activity.RESULT_OK && intent != null) {\n\t\t\tval matches = intent.getStringArrayExtra(RecognizerIntent.EXTRA_RESULTS)\n\t\t\tmatches?.firstOrNull()\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/BitmapWrapper.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport android.graphics.Canvas\nimport androidx.core.graphics.createBitmap\nimport org.koitharu.kotatsu.parsers.bitmap.Bitmap\nimport org.koitharu.kotatsu.parsers.bitmap.Rect\nimport java.io.OutputStream\nimport android.graphics.Bitmap as AndroidBitmap\nimport android.graphics.Rect as AndroidRect\n\nclass BitmapWrapper private constructor(\n\tprivate val androidBitmap: AndroidBitmap,\n) : Bitmap, AutoCloseable {\n\n\tprivate val canvas by lazy { Canvas(androidBitmap) } // is not always used, so initialized lazily\n\n\toverride val height: Int\n\t\tget() = androidBitmap.height\n\n\toverride val width: Int\n\t\tget() = androidBitmap.width\n\n\toverride fun drawBitmap(sourceBitmap: Bitmap, src: Rect, dst: Rect) {\n\t\tval androidSourceBitmap = (sourceBitmap as BitmapWrapper).androidBitmap\n\t\tcanvas.drawBitmap(androidSourceBitmap, src.toAndroidRect(), dst.toAndroidRect(), null)\n\t}\n\n\toverride fun close() {\n\t\tandroidBitmap.recycle()\n\t}\n\n\tfun compressTo(output: OutputStream) {\n\t\tandroidBitmap.compress(AndroidBitmap.CompressFormat.PNG, 100, output)\n\t}\n\n\tcompanion object {\n\n\t\tfun create(width: Int, height: Int) = BitmapWrapper(\n\t\t\tcreateBitmap(width, height, AndroidBitmap.Config.ARGB_8888),\n\t\t)\n\n\t\tfun create(bitmap: AndroidBitmap) = BitmapWrapper(\n\t\t\tif (bitmap.isMutable) bitmap else bitmap.copy(AndroidBitmap.Config.ARGB_8888, true),\n\t\t)\n\n\t\tprivate fun Rect.toAndroidRect() = AndroidRect(left, top, right, bottom)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/CachingMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport android.util.Log\nimport androidx.collection.MutableLongSet\nimport coil3.request.CachePolicy\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.MainCoroutineDispatcher\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.currentCoroutineContext\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.cache.SafeDeferred\nimport org.koitharu.kotatsu.core.util.MultiMutex\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\n\nabstract class CachingMangaRepository(\n\tprivate val cache: MemoryContentCache,\n) : MangaRepository {\n\n\tprivate val detailsMutex = MultiMutex<Long>()\n\tprivate val relatedMangaMutex = MultiMutex<Long>()\n\tprivate val pagesMutex = MultiMutex<Long>()\n\n\tfinal override suspend fun getDetails(manga: Manga): Manga = getDetails(manga, CachePolicy.ENABLED)\n\n\tfinal override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = pagesMutex.withLock(chapter.id) {\n\t\tcache.getPages(source, chapter.url)?.let { return it }\n\t\tval pages = asyncSafe {\n\t\t\tgetPagesImpl(chapter).distinctById()\n\t\t}\n\t\tcache.putPages(source, chapter.url, pages)\n\t\tpages\n\t}.await()\n\n\tfinal override suspend fun getRelated(seed: Manga): List<Manga> = relatedMangaMutex.withLock(seed.id) {\n\t\tcache.getRelatedManga(source, seed.url)?.let { return it }\n\t\tval related = asyncSafe {\n\t\t\tgetRelatedMangaImpl(seed).filterNot { it.id == seed.id }\n\t\t}\n\t\tcache.putRelatedManga(source, seed.url, related)\n\t\trelated\n\t}.await()\n\n\tsuspend fun getDetails(manga: Manga, cachePolicy: CachePolicy): Manga = detailsMutex.withLock(manga.id) {\n\t\tif (cachePolicy.readEnabled) {\n\t\t\tcache.getDetails(source, manga.url)?.let { return it }\n\t\t}\n\t\tval details = asyncSafe {\n\t\t\tgetDetailsImpl(manga)\n\t\t}\n\t\tif (cachePolicy.writeEnabled) {\n\t\t\tcache.putDetails(source, manga.url, details)\n\t\t}\n\t\tdetails\n\t}.await()\n\n\tsuspend fun peekDetails(manga: Manga): Manga? {\n\t\treturn cache.getDetails(source, manga.url)\n\t}\n\n\tfun invalidateCache() {\n\t\tcache.clear(source)\n\t}\n\n\tprotected abstract suspend fun getDetailsImpl(manga: Manga): Manga\n\n\tprotected abstract suspend fun getRelatedMangaImpl(seed: Manga): List<Manga>\n\n\tprotected abstract suspend fun getPagesImpl(chapter: MangaChapter): List<MangaPage>\n\n\tprivate suspend fun <T> asyncSafe(block: suspend CoroutineScope.() -> T): SafeDeferred<T> {\n\t\tvar dispatcher = currentCoroutineContext()[CoroutineDispatcher.Key]\n\t\tif (dispatcher == null || dispatcher is MainCoroutineDispatcher) {\n\t\t\tdispatcher = Dispatchers.Default\n\t\t}\n\t\treturn SafeDeferred(\n\t\t\tprocessLifecycleScope.async(dispatcher) {\n\t\t\t\trunCatchingCancellable { block() }\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate fun List<MangaPage>.distinctById(): List<MangaPage> {\n\t\tif (isEmpty()) {\n\t\t\treturn emptyList()\n\t\t}\n\t\tval result = ArrayList<MangaPage>(size)\n\t\tval set = MutableLongSet(size)\n\t\tfor (page in this) {\n\t\t\tif (set.add(page.id)) {\n\t\t\t\tresult.add(page)\n\t\t\t} else if (BuildConfig.DEBUG) {\n\t\t\t\tLog.w(null, \"Duplicate page: $page\")\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/EmptyMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterOptions\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport java.util.EnumSet\n\nopen class EmptyMangaRepository(override val source: MangaSource) : MangaRepository {\n\n\toverride val sortOrders: Set<SortOrder>\n\t\tget() = EnumSet.allOf(SortOrder::class.java)\n\n\toverride var defaultSortOrder: SortOrder\n\t\tget() = SortOrder.NEWEST\n\t\tset(value) = Unit\n\n\toverride val filterCapabilities: MangaListFilterCapabilities\n\t\tget() = MangaListFilterCapabilities()\n\n\toverride suspend fun getList(offset: Int, order: SortOrder?, filter: MangaListFilter?): List<Manga> = stub(null)\n\n\toverride suspend fun getDetails(manga: Manga): Manga = stub(manga)\n\n\toverride suspend fun getPages(chapter: MangaChapter): List<MangaPage> = stub(null)\n\n\toverride suspend fun getPageUrl(page: MangaPage): String = stub(null)\n\n\toverride suspend fun getFilterOptions(): MangaListFilterOptions = stub(null)\n\n\toverride suspend fun getRelated(seed: Manga): List<Manga> = stub(seed)\n\n\tprivate fun stub(manga: Manga?): Nothing {\n\t\tthrow UnsupportedSourceException(\"This manga source is not supported\", manga)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport androidx.collection.LongObjectMap\nimport androidx.collection.MutableLongObjectMap\nimport androidx.core.net.toUri\nimport androidx.room.withTransaction\nimport dagger.Reusable\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES\nimport org.koitharu.kotatsu.core.db.TABLE_PREFERENCES\nimport org.koitharu.kotatsu.core.db.entity.ContentRating\nimport org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity\nimport org.koitharu.kotatsu.core.db.entity.toEntities\nimport org.koitharu.kotatsu.core.db.entity.toEntity\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaChapters\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.nav.MangaIntent\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.core.util.ext.toFileOrNull\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.reader.domain.ReaderColorFilter\nimport javax.inject.Inject\nimport javax.inject.Provider\n\n@Reusable\nclass MangaDataRepository @Inject constructor(\n\tprivate val db: MangaDatabase,\n\tprivate val resolverProvider: Provider<MangaLinkResolver>,\n\tprivate val appShortcutManagerProvider: Provider<AppShortcutManager>,\n) {\n\n\tsuspend fun saveReaderMode(manga: Manga, mode: ReaderMode) {\n\t\tdb.withTransaction {\n\t\t\tstoreManga(manga, replaceExisting = false)\n\t\t\tval entity = db.getPreferencesDao().find(manga.id) ?: newEntity(manga.id)\n\t\t\tdb.getPreferencesDao().upsert(entity.copy(mode = mode.id))\n\t\t}\n\t}\n\n\tsuspend fun saveColorFilter(manga: Manga, colorFilter: ReaderColorFilter?) {\n\t\tdb.withTransaction {\n\t\t\tstoreManga(manga, replaceExisting = false)\n\t\t\tval entity = db.getPreferencesDao().find(manga.id) ?: newEntity(manga.id)\n\t\t\tdb.getPreferencesDao().upsert(\n\t\t\t\tentity.copy(\n\t\t\t\t\tcfBrightness = colorFilter?.brightness ?: 0f,\n\t\t\t\t\tcfContrast = colorFilter?.contrast ?: 0f,\n\t\t\t\t\tcfInvert = colorFilter?.isInverted == true,\n\t\t\t\t\tcfGrayscale = colorFilter?.isGrayscale == true,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n\n\tsuspend fun resetColorFilters() {\n\t\tdb.getPreferencesDao().resetColorFilters()\n\t}\n\n\tsuspend fun getReaderMode(mangaId: Long): ReaderMode? {\n\t\treturn db.getPreferencesDao().find(mangaId)?.let { ReaderMode.valueOf(it.mode) }\n\t}\n\n\tsuspend fun getColorFilter(mangaId: Long): ReaderColorFilter? {\n\t\treturn db.getPreferencesDao().find(mangaId)?.getColorFilterOrNull()\n\t}\n\n\tsuspend fun getOverride(mangaId: Long): MangaOverride? {\n\t\treturn db.getPreferencesDao().find(mangaId)?.getOverrideOrNull()\n\t}\n\n\tsuspend fun getOverrides(): LongObjectMap<MangaOverride> {\n\t\tval entities = db.getPreferencesDao().getOverrides()\n\t\tval map = MutableLongObjectMap<MangaOverride>(entities.size)\n\t\tfor (entity in entities) {\n\t\t\tmap[entity.mangaId] = entity.getOverrideOrNull() ?: continue\n\t\t}\n\t\treturn map\n\t}\n\n\tsuspend fun setOverride(manga: Manga, override: MangaOverride?) {\n\t\tdb.withTransaction {\n\t\t\tstoreManga(manga, replaceExisting = false)\n\t\t\tval dao = db.getPreferencesDao()\n\t\t\tval entity = dao.find(manga.id) ?: newEntity(manga.id)\n\t\t\tdao.upsert(\n\t\t\t\tentity.copy(\n\t\t\t\t\ttitleOverride = override?.title?.nullIfEmpty(),\n\t\t\t\t\tcoverUrlOverride = override?.coverUrl?.nullIfEmpty(),\n\t\t\t\t\tcontentRatingOverride = override?.contentRating?.name,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n\n\tfun observeColorFilter(mangaId: Long): Flow<ReaderColorFilter?> {\n\t\treturn db.getPreferencesDao().observe(mangaId)\n\t\t\t.map { it?.getColorFilterOrNull() }\n\t\t\t.distinctUntilChanged()\n\t}\n\n\tsuspend fun findMangaById(mangaId: Long, withChapters: Boolean): Manga? {\n\t\tval chapters = if (withChapters) {\n\t\t\tdb.getChaptersDao().findAll(mangaId).takeUnless { it.isEmpty() }\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t\treturn db.getMangaDao().find(mangaId)?.toManga(chapters)\n\t}\n\n\tsuspend fun findMangaByPublicUrl(publicUrl: String): Manga? {\n\t\treturn db.getMangaDao().findByPublicUrl(publicUrl)?.toManga()\n\t}\n\n\tsuspend fun resolveIntent(intent: MangaIntent, withChapters: Boolean): Manga? = when {\n\t\tintent.manga != null -> intent.manga.withCachedChaptersIfNeeded(withChapters)\n\t\tintent.mangaId != 0L -> findMangaById(intent.mangaId, withChapters)\n\t\tintent.uri != null -> resolverProvider.get().resolve(intent.uri).withCachedChaptersIfNeeded(withChapters)\n\t\telse -> null\n\t}\n\n\tsuspend fun storeManga(manga: Manga, replaceExisting: Boolean) {\n\t\tif (!replaceExisting && db.getMangaDao().find(manga.id) != null) {\n\t\t\treturn\n\t\t}\n\t\tdb.withTransaction {\n\t\t\t// avoid storing local manga if remote one is already stored\n\t\t\tval existing = if (manga.isLocal) {\n\t\t\t\tdb.getMangaDao().find(manga.id)?.manga\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t\tif (existing == null || existing.source == manga.source.name) {\n\t\t\t\tval tags = manga.tags.toEntities()\n\t\t\t\tdb.getTagsDao().upsert(tags)\n\t\t\t\tdb.getMangaDao().upsert(manga.toEntity(), tags)\n\t\t\t\tif (!manga.isLocal) {\n\t\t\t\t\tmanga.chapters?.let { chapters ->\n\t\t\t\t\t\tdb.getChaptersDao().replaceAll(manga.id, chapters.withIndex().toEntities(manga.id))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun updateChapters(manga: Manga) {\n\t\tval chapters = manga.chapters\n\t\tif (!chapters.isNullOrEmpty() && manga.id in db.getMangaDao()) {\n\t\t\tdb.getChaptersDao().replaceAll(manga.id, chapters.withIndex().toEntities(manga.id))\n\t\t}\n\t}\n\n\tsuspend fun gcChaptersCache() {\n\t\tdb.getChaptersDao().gc()\n\t}\n\n\tsuspend fun findTags(source: MangaSource): Set<MangaTag> {\n\t\treturn db.getTagsDao().findTags(source.name).toMangaTags()\n\t}\n\n\tsuspend fun cleanupLocalManga() {\n\t\tval dao = db.getMangaDao()\n\t\tval broken = dao.findAllBySource(LocalMangaSource.name)\n\t\t\t.filter { x -> x.manga.url.toUri().toFileOrNull()?.exists() == false }\n\t\tif (broken.isNotEmpty()) {\n\t\t\tdao.delete(broken.map { it.manga })\n\t\t}\n\t}\n\n\tsuspend fun cleanupDatabase() {\n\t\tdb.withTransaction {\n\t\t\tgcChaptersCache()\n\t\t\tval idsFromShortcuts = appShortcutManagerProvider.get().getMangaShortcuts()\n\t\t\tdb.getMangaDao().cleanup(idsFromShortcuts)\n\t\t}\n\t}\n\n\tfun observeOverridesTrigger(emitInitialState: Boolean) = db.invalidationTracker.createFlow(\n\t\ttables = arrayOf(TABLE_PREFERENCES),\n\t\temitInitialState = emitInitialState,\n\t)\n\n\tfun observeFavoritesTrigger(emitInitialState: Boolean) = db.invalidationTracker.createFlow(\n\t\ttables = arrayOf(TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES),\n\t\temitInitialState = emitInitialState,\n\t)\n\n\tprivate suspend fun Manga.withCachedChaptersIfNeeded(flag: Boolean): Manga = if (flag && !isLocal && chapters.isNullOrEmpty()) {\n\t\tval cachedChapters = db.getChaptersDao().findAll(id)\n\t\tif (cachedChapters.isEmpty()) {\n\t\t\tthis\n\t\t} else {\n\t\t\tcopy(chapters = cachedChapters.toMangaChapters())\n\t\t}\n\t} else {\n\t\tthis\n\t}\n\n\tprivate fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? {\n\t\treturn if (cfBrightness != 0f || cfContrast != 0f || cfInvert || cfGrayscale || cfBookEffect) {\n\t\t\tReaderColorFilter(\n\t\t\t\tbrightness = cfBrightness,\n\t\t\t\tcontrast = cfContrast,\n\t\t\t\tisInverted = cfInvert,\n\t\t\t\tisGrayscale = cfGrayscale,\n\t\t\t\tisBookBackground = cfBookEffect\n\t\t\t)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate fun MangaPrefsEntity.getOverrideOrNull(): MangaOverride? {\n\t\treturn if (titleOverride.isNullOrEmpty() && coverUrlOverride.isNullOrEmpty() && contentRatingOverride.isNullOrEmpty()) {\n\t\t\tnull\n\t\t} else {\n\t\t\tMangaOverride(\n\t\t\t\tcoverUrl = coverUrlOverride?.nullIfEmpty(),\n\t\t\t\ttitle = titleOverride?.nullIfEmpty(),\n\t\t\t\tcontentRating = ContentRating(contentRatingOverride),\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun newEntity(mangaId: Long) = MangaPrefsEntity(\n\t\tmangaId = mangaId,\n\t\tmode = -1,\n\t\tcfBrightness = ReaderColorFilter.EMPTY.brightness,\n\t\tcfContrast = ReaderColorFilter.EMPTY.contrast,\n\t\tcfInvert = ReaderColorFilter.EMPTY.isInverted,\n\t\tcfGrayscale = ReaderColorFilter.EMPTY.isGrayscale,\n\t\tcfBookEffect = ReaderColorFilter.EMPTY.isBookBackground,\n\t\ttitleOverride = null,\n\t\tcoverUrlOverride = null,\n\t\tcontentRatingOverride = null,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLinkResolver.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport android.net.Uri\nimport coil3.request.CachePolicy\nimport dagger.Reusable\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.util.ext.isHttpUrl\nimport org.koitharu.kotatsu.parsers.MangaLoaderContext\nimport org.koitharu.kotatsu.parsers.exception.NotFoundException\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.almostEquals\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.levenshteinDistance\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\n@Reusable\nclass MangaLinkResolver @Inject constructor(\n\tprivate val repositoryFactory: MangaRepository.Factory,\n\tprivate val dataRepository: MangaDataRepository,\n\tprivate val context: MangaLoaderContext,\n) {\n\n\tsuspend fun resolve(uri: Uri): Manga {\n\t\treturn if (uri.scheme == \"kotatsu\" || uri.host == \"kotatsu.app\") {\n\t\t\tresolveAppLink(uri)\n\t\t} else {\n\t\t\tresolveExternalLink(uri.toString())\n\t\t} ?: throw NotFoundException(\"Cannot resolve link\", uri.toString())\n\t}\n\n\tprivate suspend fun resolveAppLink(uri: Uri): Manga? {\n\t\trequire(uri.pathSegments.singleOrNull() == \"manga\") { \"Invalid url\" }\n\t\turi.getQueryParameter(\"id\")?.let { mangaId ->\n\t\t\t// short url\n\t\t\treturn dataRepository.findMangaById(mangaId.toLong(), withChapters = false)\n\t\t}\n\t\tval sourceName = requireNotNull(uri.getQueryParameter(\"source\")) { \"Source is not specified\" }\n\t\tval source = MangaSource(sourceName)\n\t\trequire(source != UnknownMangaSource) { \"Manga source $sourceName is not supported\" }\n\t\tval repo = repositoryFactory.create(source)\n\t\treturn repo.findExact(\n\t\t\turl = uri.getQueryParameter(\"url\"),\n\t\t\ttitle = uri.getQueryParameter(\"name\"),\n\t\t)\n\t}\n\n\tprivate suspend fun resolveExternalLink(uri: String): Manga? {\n\t\tdataRepository.findMangaByPublicUrl(uri)?.let {\n\t\t\treturn it\n\t\t}\n\t\treturn context.newLinkResolver(uri).getManga()\n\t}\n\n\tprivate suspend fun MangaRepository.findExact(url: String?, title: String?): Manga? {\n\t\tif (!title.isNullOrEmpty()) {\n\t\t\tval list = getList(0, null, MangaListFilter(query = title))\n\t\t\tif (url != null) {\n\t\t\t\tlist.find { it.url == url }?.let {\n\t\t\t\t\treturn it\n\t\t\t\t}\n\t\t\t}\n\t\t\tlist.minByOrNull { it.title.levenshteinDistance(title) }\n\t\t\t\t?.takeIf { it.title.almostEquals(title, 0.2f) }\n\t\t\t\t?.let { return it }\n\t\t}\n\t\tval seed = getDetailsNoCache(\n\t\t\tgetSeedManga(source, url ?: return null, title),\n\t\t)\n\t\treturn runCatchingCancellable {\n\t\t\tval seedTitle = seed.title.ifEmpty {\n\t\t\t\tseed.altTitle\n\t\t\t}.ifNullOrEmpty {\n\t\t\t\tseed.author\n\t\t\t} ?: return@runCatchingCancellable null\n\t\t\tval seedList = getList(0, null, MangaListFilter(query = seedTitle))\n\t\t\tseedList.first { x -> x.url == url }\n\t\t}.getOrThrow()\n\t}\n\n\tprivate suspend fun MangaRepository.getDetailsNoCache(manga: Manga): Manga = if (this is CachingMangaRepository) {\n\t\tgetDetails(manga, CachePolicy.READ_ONLY)\n\t} else {\n\t\tgetDetails(manga)\n\t}\n\n\tprivate fun getSeedManga(source: MangaSource, url: String, title: String?) = Manga(\n\t\tid = run {\n\t\t\tvar h = 1125899906842597L\n\t\t\tsource.name.forEach { c ->\n\t\t\t\th = 31 * h + c.code\n\t\t\t}\n\t\t\turl.forEach { c ->\n\t\t\t\th = 31 * h + c.code\n\t\t\t}\n\t\t\th\n\t\t},\n\t\ttitle = title.orEmpty(),\n\t\taltTitle = null,\n\t\turl = url,\n\t\tpublicUrl = \"\",\n\t\trating = 0.0f,\n\t\tisNsfw = source.isNsfw(),\n\t\tcoverUrl = \"\",\n\t\ttags = emptySet(),\n\t\tstate = null,\n\t\tauthor = null,\n\t\tlargeCoverUrl = null,\n\t\tdescription = null,\n\t\tchapters = null,\n\t\tsource = source,\n\t)\n\n\tcompanion object {\n\n\t\tfun isValidLink(str: String): Boolean {\n\t\t\treturn str.isHttpUrl() || str.startsWith(\"kotatsu://\", ignoreCase = true)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.Base64\nimport androidx.core.os.LocaleListCompat\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.withTimeout\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Response\nimport okhttp3.ResponseBody.Companion.asResponseBody\nimport okio.Buffer\nimport org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException\nimport org.koitharu.kotatsu.core.image.BitmapDecoderCompat\nimport org.koitharu.kotatsu.core.network.MangaHttpClient\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.core.network.webview.WebViewExecutor\nimport org.koitharu.kotatsu.core.prefs.SourceSettings\nimport org.koitharu.kotatsu.core.util.ext.toList\nimport org.koitharu.kotatsu.core.util.ext.toMimeType\nimport org.koitharu.kotatsu.core.util.ext.use\nimport org.koitharu.kotatsu.parsers.MangaLoaderContext\nimport org.koitharu.kotatsu.parsers.MangaParser\nimport org.koitharu.kotatsu.parsers.bitmap.Bitmap\nimport org.koitharu.kotatsu.parsers.config.MangaSourceConfig\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.network.UserAgents\nimport org.koitharu.kotatsu.parsers.util.map\nimport java.util.Locale\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MangaLoaderContextImpl @Inject constructor(\n\t@MangaHttpClient override val httpClient: OkHttpClient,\n\toverride val cookieJar: MutableCookieJar,\n\t@ApplicationContext private val androidContext: Context,\n\tprivate val webViewExecutor: WebViewExecutor,\n) : MangaLoaderContext() {\n\n\tprivate val jsTimeout = TimeUnit.SECONDS.toMillis(4)\n\n\t@Deprecated(\"Provide a base url\")\n\t@SuppressLint(\"SetJavaScriptEnabled\")\n\toverride suspend fun evaluateJs(script: String): String? = evaluateJs(\"\", script)\n\n\toverride suspend fun evaluateJs(baseUrl: String, script: String): String? = withTimeout(jsTimeout) {\n\t\twebViewExecutor.evaluateJs(baseUrl, script)\n\t}\n\n\toverride fun getDefaultUserAgent(): String = webViewExecutor.defaultUserAgent ?: UserAgents.FIREFOX_MOBILE\n\n\toverride fun getConfig(source: MangaSource): MangaSourceConfig {\n\t\treturn SourceSettings(androidContext, source)\n\t}\n\n\toverride fun encodeBase64(data: ByteArray): String {\n\t\treturn Base64.encodeToString(data, Base64.NO_WRAP)\n\t}\n\n\toverride fun decodeBase64(data: String): ByteArray {\n\t\treturn Base64.decode(data, Base64.DEFAULT)\n\t}\n\n\toverride fun getPreferredLocales(): List<Locale> {\n\t\treturn LocaleListCompat.getAdjustedDefault().toList()\n\t}\n\n\toverride fun requestBrowserAction(\n\t\tparser: MangaParser,\n\t\turl: String,\n\t): Nothing = throw InteractiveActionRequiredException(parser.source, url)\n\n\toverride fun redrawImageResponse(response: Response, redraw: (image: Bitmap) -> Bitmap): Response {\n\t\treturn response.map { body ->\n\t\t\tBitmapDecoderCompat.decode(body.byteStream(), body.contentType()?.toMimeType(), isMutable = true)\n\t\t\t\t.use { bitmap ->\n\t\t\t\t\t(redraw(BitmapWrapper.create(bitmap)) as BitmapWrapper).use { result ->\n\t\t\t\t\t\tBuffer().also {\n\t\t\t\t\t\t\tresult.compressTo(it.outputStream())\n\t\t\t\t\t\t}.asResponseBody(\"image/jpeg\".toMediaType())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t}\n\n\toverride fun createBitmap(width: Int, height: Int): Bitmap = BitmapWrapper.create(width, height)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport android.content.Context\nimport androidx.annotation.AnyThread\nimport androidx.collection.ArrayMap\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.MangaSourceInfo\nimport org.koitharu.kotatsu.core.model.TestMangaSource\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaRepository\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaSource\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.parsers.MangaLoaderContext\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterOptions\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport java.lang.ref.WeakReference\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\ninterface MangaRepository {\n\n\tval source: MangaSource\n\n\tval sortOrders: Set<SortOrder>\n\n\tvar defaultSortOrder: SortOrder\n\n\tval filterCapabilities: MangaListFilterCapabilities\n\n\tsuspend fun getList(offset: Int, order: SortOrder?, filter: MangaListFilter?): List<Manga>\n\n\tsuspend fun getDetails(manga: Manga): Manga\n\n\tsuspend fun getPages(chapter: MangaChapter): List<MangaPage>\n\n\tsuspend fun getPageUrl(page: MangaPage): String\n\n\tsuspend fun getFilterOptions(): MangaListFilterOptions\n\n\tsuspend fun getRelated(seed: Manga): List<Manga>\n\n\tsuspend fun find(manga: Manga): Manga? {\n\t\tval list = getList(0, SortOrder.RELEVANCE, MangaListFilter(query = manga.title))\n\t\treturn list.find { x -> x.id == manga.id }\n\t}\n\n\t@Singleton\n\tclass Factory @Inject constructor(\n\t\t@ApplicationContext private val context: Context,\n\t\tprivate val localMangaRepository: LocalMangaRepository,\n\t\tprivate val loaderContext: MangaLoaderContext,\n\t\tprivate val contentCache: MemoryContentCache,\n\t\tprivate val mirrorSwitcher: MirrorSwitcher,\n\t) {\n\n\t\tprivate val cache = ArrayMap<MangaSource, WeakReference<MangaRepository>>()\n\n\t\t@AnyThread\n\t\tfun create(source: MangaSource): MangaRepository {\n\t\t\twhen (source) {\n\t\t\t\tis MangaSourceInfo -> return create(source.mangaSource)\n\t\t\t\tLocalMangaSource -> return localMangaRepository\n\t\t\t\tUnknownMangaSource -> return EmptyMangaRepository(source)\n\t\t\t}\n\t\t\tcache[source]?.get()?.let { return it }\n\t\t\treturn synchronized(cache) {\n\t\t\t\tcache[source]?.get()?.let { return it }\n\t\t\t\tval repository = createRepository(source)\n\t\t\t\tif (repository != null) {\n\t\t\t\t\tcache[source] = WeakReference(repository)\n\t\t\t\t\trepository\n\t\t\t\t} else {\n\t\t\t\t\tEmptyMangaRepository(source)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate fun createRepository(source: MangaSource): MangaRepository? = when (source) {\n\t\t\tis MangaParserSource -> ParserMangaRepository(\n\t\t\t\tparser = loaderContext.newParserInstance(source),\n\t\t\t\tcache = contentCache,\n\t\t\t\tmirrorSwitcher = mirrorSwitcher,\n\t\t\t)\n\n\t\t\tTestMangaSource -> TestMangaRepository(\n\t\t\t\tloaderContext = loaderContext,\n\t\t\t\tcache = contentCache,\n\t\t\t)\n\n\t\t\tis ExternalMangaSource -> if (source.isAvailable(context)) {\n\t\t\t\tExternalMangaRepository(\n\t\t\t\t\tcontentResolver = context.contentResolver,\n\t\t\t\t\tsource = source,\n\t\t\t\t\tcache = contentCache,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tEmptyMangaRepository(source)\n\t\t\t}\n\n\t\t\telse -> null\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MirrorSwitcher.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport android.util.Log\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.network.MangaHttpClient\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.util.EnumSet\nimport javax.inject.Inject\n\nclass MirrorSwitcher @Inject constructor(\n\tprivate val settings: AppSettings,\n\t@MangaHttpClient private val okHttpClient: OkHttpClient,\n) {\n\n\tprivate val blacklist = EnumSet.noneOf(MangaParserSource::class.java)\n\tprivate val mutex: Mutex = Mutex()\n\n\tval isEnabled: Boolean\n\t\tget() = settings.isMirrorSwitchingEnabled\n\n\tsuspend fun <T : Any> trySwitchMirror(repository: ParserMangaRepository, loader: suspend () -> T?): T? {\n\t\tval source = repository.source\n\t\tif (!isEnabled || source in blacklist) {\n\t\t\treturn null\n\t\t}\n\t\tval availableMirrors = repository.domains\n\t\tval currentHost = repository.domain\n\t\tif (availableMirrors.size <= 1 || currentHost !in availableMirrors) {\n\t\t\treturn null\n\t\t}\n\t\tmutex.withLock {\n\t\t\tif (source in blacklist) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tlogd { \"Looking for mirrors for ${source}...\" }\n\t\t\tfindRedirect(repository)?.let { mirror ->\n\t\t\t\trepository.domain = mirror\n\t\t\t\trunCatchingCancellable {\n\t\t\t\t\tloader()?.takeIfValid()\n\t\t\t\t}.getOrNull()?.let {\n\t\t\t\t\tlogd { \"Found redirect for $source: $mirror\" }\n\t\t\t\t\treturn it\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (mirror in availableMirrors) {\n\t\t\t\trepository.domain = mirror\n\t\t\t\trunCatchingCancellable {\n\t\t\t\t\tloader()?.takeIfValid()\n\t\t\t\t}.getOrNull()?.let {\n\t\t\t\t\tlogd { \"Found mirror for $source: $mirror\" }\n\t\t\t\t\treturn it\n\t\t\t\t}\n\t\t\t}\n\t\t\trepository.domain = currentHost // rollback\n\t\t\tblacklist.add(source)\n\t\t\tlogd { \"$source blacklisted\" }\n\t\t\treturn null\n\t\t}\n\t}\n\n\tsuspend fun findRedirect(repository: ParserMangaRepository): String? {\n\t\tif (!isEnabled) {\n\t\t\treturn null\n\t\t}\n\t\tval currentHost = repository.domain\n\t\tval newHost = okHttpClient.newCall(\n\t\t\tRequest.Builder()\n\t\t\t\t.url(\"https://$currentHost\")\n\t\t\t\t.head()\n\t\t\t\t.build(),\n\t\t).await().use {\n\t\t\tif (it.isSuccessful) {\n\t\t\t\tit.request.url.host\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}\n\t\treturn if (newHost != currentHost) {\n\t\t\tnewHost\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate fun <T : Any> T.takeIfValid() = takeIf {\n\t\twhen (it) {\n\t\t\tis Collection<*> -> it.isNotEmpty()\n\t\t\telse -> true\n\t\t}\n\t}\n\n\tprivate companion object {\n\n\t\tconst val TAG = \"MirrorSwitcher\"\n\n\t\tinline fun logd(message: () -> String) {\n\t\t\tif (BuildConfig.DEBUG) {\n\t\t\t\tLog.d(TAG, message())\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/ParserMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport kotlinx.coroutines.Dispatchers\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException\nimport org.koitharu.kotatsu.core.exceptions.ProxyConfigException\nimport org.koitharu.kotatsu.core.prefs.SourceSettings\nimport org.koitharu.kotatsu.parsers.MangaParser\nimport org.koitharu.kotatsu.parsers.MangaParserAuthProvider\nimport org.koitharu.kotatsu.parsers.config.ConfigKey\nimport org.koitharu.kotatsu.parsers.exception.AuthRequiredException\nimport org.koitharu.kotatsu.parsers.model.Favicons\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterOptions\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\n\nclass ParserMangaRepository(\n\tprivate val parser: MangaParser,\n\tprivate val mirrorSwitcher: MirrorSwitcher,\n\tcache: MemoryContentCache,\n) : CachingMangaRepository(cache), Interceptor {\n\n\tprivate val filterOptionsLazy = suspendLazy(Dispatchers.Default) {\n\t\twithMirrors {\n\t\t\tparser.getFilterOptions()\n\t\t}\n\t}\n\n\toverride val source: MangaParserSource\n\t\tget() = parser.source\n\n\toverride val sortOrders: Set<SortOrder>\n\t\tget() = parser.availableSortOrders\n\n\toverride val filterCapabilities: MangaListFilterCapabilities\n\t\tget() = parser.filterCapabilities\n\n\toverride var defaultSortOrder: SortOrder\n\t\tget() = getConfig().defaultSortOrder ?: sortOrders.first()\n\t\tset(value) {\n\t\t\tgetConfig().defaultSortOrder = value\n\t\t}\n\n\tvar domain: String\n\t\tget() = parser.domain\n\t\tset(value) {\n\t\t\tgetConfig()[parser.configKeyDomain] = value\n\t\t}\n\n\tval domains: Array<out String>\n\t\tget() = parser.configKeyDomain.presetValues\n\n\toverride fun intercept(chain: Interceptor.Chain): Response = parser.intercept(chain)\n\n\toverride suspend fun getList(offset: Int, order: SortOrder?, filter: MangaListFilter?): List<Manga> {\n\t\treturn withMirrors {\n\t\t\tparser.getList(offset, order ?: defaultSortOrder, filter ?: MangaListFilter.EMPTY)\n\t\t}\n\t}\n\n\toverride suspend fun getPagesImpl(\n\t\tchapter: MangaChapter\n\t): List<MangaPage> = withMirrors {\n\t\tparser.getPages(chapter)\n\t}\n\n\toverride suspend fun getPageUrl(page: MangaPage): String = withMirrors {\n\t\tparser.getPageUrl(page).also { result ->\n\t\t\tcheck(result.isNotEmpty()) { \"Page url is empty\" }\n\t\t}\n\t}\n\n\toverride suspend fun getFilterOptions(): MangaListFilterOptions = filterOptionsLazy.get()\n\n\tsuspend fun getFavicons(): Favicons = withMirrors {\n\t\tparser.getFavicons()\n\t}\n\n\toverride suspend fun getRelatedMangaImpl(seed: Manga): List<Manga> = parser.getRelatedManga(seed)\n\n\toverride suspend fun getDetailsImpl(manga: Manga): Manga = withMirrors {\n\t\tparser.getDetails(manga)\n\t}\n\n\tfun getAuthProvider(): MangaParserAuthProvider? = parser.authorizationProvider\n\n\tfun getRequestHeaders() = parser.getRequestHeaders()\n\n\tfun getConfigKeys(): List<ConfigKey<*>> = ArrayList<ConfigKey<*>>().also {\n\t\tparser.onCreateConfig(it)\n\t}\n\n\tfun getAvailableMirrors(): List<String> {\n\t\treturn parser.configKeyDomain.presetValues.toList()\n\t}\n\n\tfun isSlowdownEnabled(): Boolean {\n\t\treturn getConfig().isSlowdownEnabled\n\t}\n\n\tfun getConfig() = parser.config as SourceSettings\n\n\tprivate suspend fun <T : Any> withMirrors(block: suspend () -> T): T {\n\t\tif (!mirrorSwitcher.isEnabled) {\n\t\t\treturn block()\n\t\t}\n\t\tval initialResult = runCatchingCancellable { block() }\n\t\tif (initialResult.isValidResult()) {\n\t\t\treturn initialResult.getOrThrow()\n\t\t}\n\t\tval newResult = mirrorSwitcher.trySwitchMirror(this, block)\n\t\treturn newResult ?: initialResult.getOrThrow()\n\t}\n\n\tprivate fun Result<Any>.isValidResult() = fold(\n\t\tonSuccess = {\n\t\t\twhen (it) {\n\t\t\t\tis Collection<*> -> it.isNotEmpty()\n\t\t\t\telse -> true\n\t\t\t}\n\t\t},\n\t\tonFailure = {\n\t\t\twhen (it.cause) {\n\t\t\t\tis CloudFlareProtectedException,\n\t\t\t\tis AuthRequiredException,\n\t\t\t\tis InteractiveActionRequiredException,\n\t\t\t\tis ProxyConfigException -> true\n\n\t\t\t\telse -> false\n\t\t\t}\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser.external\n\nimport android.content.ContentResolver\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.parser.CachingMangaRepository\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterOptions\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport java.util.EnumSet\n\nclass ExternalMangaRepository(\n\tcontentResolver: ContentResolver,\n\toverride val source: ExternalMangaSource,\n\tcache: MemoryContentCache,\n) : CachingMangaRepository(cache) {\n\n\tprivate val contentSource = ExternalPluginContentSource(contentResolver, source)\n\n\tprivate val capabilities by lazy {\n\t\trunCatching {\n\t\t\tcontentSource.getCapabilities()\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}.getOrNull()\n\t}\n\n\tprivate val filterOptions = suspendLazy(initializer = contentSource::getListFilterOptions)\n\n\toverride val sortOrders: Set<SortOrder>\n\t\tget() = capabilities?.availableSortOrders ?: EnumSet.of(SortOrder.POPULARITY)\n\n\toverride val filterCapabilities: MangaListFilterCapabilities\n\t\tget() = capabilities?.listFilterCapabilities ?: MangaListFilterCapabilities()\n\n\toverride var defaultSortOrder: SortOrder\n\t\tget() = capabilities?.availableSortOrders?.firstOrNull() ?: SortOrder.ALPHABETICAL\n\t\tset(value) = Unit\n\n\toverride suspend fun getFilterOptions(): MangaListFilterOptions = filterOptions.get()\n\n\toverride suspend fun getList(offset: Int, order: SortOrder?, filter: MangaListFilter?): List<Manga> =\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\tcontentSource.getList(offset, order ?: defaultSortOrder, filter ?: MangaListFilter.EMPTY)\n\t\t}\n\n\toverride suspend fun getDetailsImpl(manga: Manga): Manga = runInterruptible(Dispatchers.IO) {\n\t\tcontentSource.getDetails(manga)\n\t}\n\n\toverride suspend fun getPagesImpl(chapter: MangaChapter): List<MangaPage> = runInterruptible(Dispatchers.IO) {\n\t\tcontentSource.getPages(chapter)\n\t}\n\n\toverride suspend fun getPageUrl(page: MangaPage): String = runInterruptible(Dispatchers.IO) {\n\t\tcontentSource.getPageUrl(page.url)\n\t}\n\n\toverride suspend fun getRelatedMangaImpl(seed: Manga): List<Manga> = emptyList() // TODO\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalMangaSource.kt",
    "content": "package org.koitharu.kotatsu.core.parser.external\n\nimport android.content.Context\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\ndata class ExternalMangaSource(\n\tval packageName: String,\n\tval authority: String,\n) : MangaSource {\n\n\toverride val name: String\n\t\tget() = \"content:$packageName/$authority\"\n\n\tprivate var cachedName: String? = null\n\n\tfun isAvailable(context: Context): Boolean {\n\t\treturn context.packageManager.resolveContentProvider(authority, 0)?.isEnabled == true\n\t}\n\n\tfun resolveName(context: Context): String {\n\t\tcachedName?.let {\n\t\t\treturn it\n\t\t}\n\t\tval pm = context.packageManager\n\t\tval info = pm.resolveContentProvider(authority, 0)\n\t\treturn info?.loadLabel(pm)?.toString()?.also {\n\t\t\tcachedName = it\n\t\t} ?: authority\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt",
    "content": "package org.koitharu.kotatsu.core.parser.external\n\nimport android.content.ContentResolver\nimport android.database.Cursor\nimport androidx.annotation.WorkerThread\nimport androidx.collection.ArraySet\nimport androidx.core.net.toUri\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.Demographic\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterOptions\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.find\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.mapNotNullToSet\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.splitTwoParts\nimport java.util.EnumSet\nimport java.util.Locale\n\nclass ExternalPluginContentSource(\n\tprivate val contentResolver: ContentResolver,\n\tprivate val source: ExternalMangaSource,\n) {\n\n\t@Blocking\n\t@WorkerThread\n\tfun getListFilterOptions() = MangaListFilterOptions(\n\t\tavailableTags = fetchTags(),\n\t\tavailableStates = fetchEnumSet(MangaState::class.java, \"filter/states\"),\n\t\tavailableContentRating = fetchEnumSet(ContentRating::class.java, \"filter/content_ratings\"),\n\t\tavailableContentTypes = fetchEnumSet(ContentType::class.java, \"filter/content_types\"),\n\t\tavailableDemographics = fetchEnumSet(Demographic::class.java, \"filter/demographics\"),\n\t\tavailableLocales = fetchLocales(),\n\t)\n\n\t@Blocking\n\t@WorkerThread\n\tfun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {\n\t\tval uri = \"content://${source.authority}/manga\".toUri().buildUpon()\n\t\turi.appendQueryParameter(\"offset\", offset.toString())\n\t\tfilter.tags.forEach { uri.appendQueryParameter(\"tags_include\", \"${it.key}=${it.title}\") }\n\t\tfilter.tagsExclude.forEach { uri.appendQueryParameter(\"tags_exclude\", \"${it.key}=${it.title}\") }\n\t\tfilter.states.forEach { uri.appendQueryParameter(\"state\", it.name) }\n\t\tfilter.locale?.let { uri.appendQueryParameter(\"locale\", it.language) }\n\t\tfilter.contentRating.forEach { uri.appendQueryParameter(\"content_rating\", it.name) }\n\t\tif (!filter.author.isNullOrEmpty()) {\n\t\t\turi.appendQueryParameter(\"author\", filter.author)\n\t\t}\n\t\tif (!filter.query.isNullOrEmpty()) {\n\t\t\turi.appendQueryParameter(\"query\", filter.query)\n\t\t}\n\t\treturn contentResolver.query(uri.build(), null, null, null, order.name)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tval result = ArrayList<Manga>(cursor.count)\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tdo {\n\t\t\t\t\t\tresult += cursor.getManga()\n\t\t\t\t\t} while (cursor.moveToNext())\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t}\n\n\t@Blocking\n\t@WorkerThread\n\tfun getDetails(manga: Manga): Manga {\n\t\tval chapters = queryChapters(manga.url)\n\t\tval details = queryDetails(manga.url)\n\t\treturn Manga(\n\t\t\tid = manga.id,\n\t\t\ttitle = details.title.ifBlank { manga.title },\n\t\t\taltTitles = details.altTitles.ifEmpty { manga.altTitles },\n\t\t\turl = details.url.ifEmpty { manga.url },\n\t\t\tpublicUrl = details.publicUrl.ifEmpty { manga.publicUrl },\n\t\t\trating = maxOf(details.rating, manga.rating),\n\t\t\tcontentRating = details.contentRating,\n\t\t\tcoverUrl = details.coverUrl.ifNullOrEmpty { manga.coverUrl },\n\t\t\ttags = details.tags + manga.tags,\n\t\t\tstate = details.state ?: manga.state,\n\t\t\tauthors = details.authors.ifEmpty { manga.authors },\n\t\t\tlargeCoverUrl = details.largeCoverUrl.ifNullOrEmpty { manga.largeCoverUrl },\n\t\t\tdescription = details.description.ifNullOrEmpty { manga.description },\n\t\t\tchapters = chapters,\n\t\t\tsource = source,\n\t\t)\n\t}\n\n\t@Blocking\n\t@WorkerThread\n\tfun getPages(chapter: MangaChapter): List<MangaPage> {\n\t\tval uri = \"content://${source.authority}/chapters\".toUri()\n\t\t\t.buildUpon()\n\t\t\t.appendPath(chapter.url)\n\t\t\t.build()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tval result = ArrayList<MangaPage>(cursor.count)\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tdo {\n\t\t\t\t\t\tresult += MangaPage(\n\t\t\t\t\t\t\tid = cursor.getLong(COLUMN_ID),\n\t\t\t\t\t\t\turl = cursor.getString(COLUMN_URL),\n\t\t\t\t\t\t\tpreview = cursor.getStringOrNull(COLUMN_PREVIEW),\n\t\t\t\t\t\t\tsource = source,\n\t\t\t\t\t\t)\n\t\t\t\t\t} while (cursor.moveToNext())\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t}\n\n\t@Blocking\n\t@WorkerThread\n\tprivate fun fetchTags(): Set<MangaTag> {\n\t\tval uri = \"content://${source.authority}/filter/tags\".toUri()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tval result = ArraySet<MangaTag>(cursor.count)\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tdo {\n\t\t\t\t\t\tresult += MangaTag(\n\t\t\t\t\t\t\tkey = cursor.getString(COLUMN_KEY),\n\t\t\t\t\t\t\ttitle = cursor.getString(COLUMN_TITLE),\n\t\t\t\t\t\t\tsource = source,\n\t\t\t\t\t\t)\n\t\t\t\t\t} while (cursor.moveToNext())\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t}\n\n\t@Blocking\n\t@WorkerThread\n\tfun getPageUrl(url: String): String {\n\t\tval uri = \"content://${source.authority}/manga/pages/0\".toUri().buildUpon()\n\t\t\t.appendQueryParameter(\"url\", url)\n\t\t\t.build()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tcursor.getString(COLUMN_VALUE)\n\t\t\t\t} else {\n\t\t\t\t\turl\n\t\t\t\t}\n\t\t\t}\n\t}\n\n\t@Blocking\n\t@WorkerThread\n\tprivate fun fetchLocales(): Set<Locale> {\n\t\tval uri = \"content://${source.authority}/filter/locales\".toUri()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tval result = ArraySet<Locale>(cursor.count)\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tdo {\n\t\t\t\t\t\tresult += Locale(cursor.getString(COLUMN_NAME))\n\t\t\t\t\t} while (cursor.moveToNext())\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t}\n\n\tfun getCapabilities(): MangaSourceCapabilities? {\n\t\tval uri = \"content://${source.authority}/capabilities\".toUri()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tMangaSourceCapabilities(\n\t\t\t\t\t\tavailableSortOrders = cursor.getStringOrNull(COLUMN_SORT_ORDERS)\n\t\t\t\t\t\t\t?.split(',')\n\t\t\t\t\t\t\t?.mapNotNullTo(EnumSet.noneOf(SortOrder::class.java)) {\n\t\t\t\t\t\t\t\tSortOrder.entries.find(it)\n\t\t\t\t\t\t\t}.orEmpty(),\n\t\t\t\t\t\tlistFilterCapabilities = MangaListFilterCapabilities(\n\t\t\t\t\t\t\tisMultipleTagsSupported = cursor.getBooleanOrDefault(COLUMN_MULTIPLE_TAGS, false),\n\t\t\t\t\t\t\tisTagsExclusionSupported = cursor.getBooleanOrDefault(COLUMN_TAGS_EXCLUSION, false),\n\t\t\t\t\t\t\tisSearchSupported = cursor.getBooleanOrDefault(COLUMN_SEARCH, false),\n\t\t\t\t\t\t\tisSearchWithFiltersSupported = cursor.getBooleanOrDefault(\n\t\t\t\t\t\t\t\tCOLUMN_SEARCH_WITH_FILTERS,\n\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tisYearSupported = cursor.getBooleanOrDefault(COLUMN_YEAR, false),\n\t\t\t\t\t\t\tisYearRangeSupported = cursor.getBooleanOrDefault(COLUMN_YEAR_RANGE, false),\n\t\t\t\t\t\t\tisOriginalLocaleSupported = cursor.getBooleanOrDefault(COLUMN_ORIGINAL_LOCALE, false),\n\t\t\t\t\t\t\tisAuthorSearchSupported = cursor.getBooleanOrDefault(COLUMN_AUTHOR, false),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t}\n\n\tprivate fun queryDetails(url: String): Manga {\n\t\tval uri = \"content://${source.authority}/manga\".toUri()\n\t\t\t.buildUpon()\n\t\t\t.appendPath(url)\n\t\t\t.build()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tcursor.moveToFirst()\n\t\t\t\tcursor.getManga()\n\t\t\t}\n\t}\n\n\tprivate fun queryChapters(url: String): List<MangaChapter> {\n\t\tval uri = \"content://${source.authority}/manga/chapters\".toUri()\n\t\t\t.buildUpon()\n\t\t\t.appendPath(url)\n\t\t\t.build()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tval result = ArrayList<MangaChapter>(cursor.count)\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tdo {\n\t\t\t\t\t\tresult += MangaChapter(\n\t\t\t\t\t\t\tid = cursor.getLong(COLUMN_ID),\n\t\t\t\t\t\t\ttitle = cursor.getStringOrNull(COLUMN_NAME),\n\t\t\t\t\t\t\tnumber = cursor.getFloatOrDefault(COLUMN_NUMBER, 0f),\n\t\t\t\t\t\t\tvolume = cursor.getIntOrDefault(COLUMN_VOLUME, 0),\n\t\t\t\t\t\t\turl = cursor.getString(COLUMN_URL),\n\t\t\t\t\t\t\tscanlator = cursor.getStringOrNull(COLUMN_SCANLATOR),\n\t\t\t\t\t\t\tuploadDate = cursor.getLongOrDefault(COLUMN_UPLOAD_DATE, 0L),\n\t\t\t\t\t\t\tbranch = cursor.getStringOrNull(COLUMN_BRANCH),\n\t\t\t\t\t\t\tsource = source,\n\t\t\t\t\t\t)\n\t\t\t\t\t} while (cursor.moveToNext())\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t}\n\n\tprivate fun ExternalPluginCursor.getManga() = Manga(\n\t\tid = getLong(COLUMN_ID),\n\t\ttitle = getString(COLUMN_TITLE),\n\t\taltTitles = setOfNotNull(getStringOrNull(COLUMN_ALT_TITLE)),\n\t\turl = getString(COLUMN_URL),\n\t\tpublicUrl = getString(COLUMN_PUBLIC_URL),\n\t\trating = getFloat(COLUMN_RATING),\n\t\tcontentRating = if (getBooleanOrDefault(COLUMN_IS_NSFW, false)) {\n\t\t\tContentRating.ADULT\n\t\t} else {\n\t\t\tnull\n\t\t},\n\t\tcoverUrl = getStringOrNull(COLUMN_COVER_URL),\n\t\ttags = getStringOrNull(COLUMN_TAGS)?.split(':')?.mapNotNullToSet {\n\t\t\tval parts = it.splitTwoParts('=') ?: return@mapNotNullToSet null\n\t\t\tMangaTag(key = parts.first, title = parts.second, source = source)\n\t\t}.orEmpty(),\n\t\tstate = getStringOrNull(COLUMN_STATE)?.let { MangaState.entries.find(it) },\n\t\tauthors = getStringOrNull(COLUMN_AUTHOR)?.split(',')?.mapNotNullToSet {\n\t\t\tit.trim().nullIfEmpty()\n\t\t}.orEmpty(),\n\t\tlargeCoverUrl = getStringOrNull(COLUMN_LARGE_COVER_URL),\n\t\tdescription = getStringOrNull(COLUMN_DESCRIPTION),\n\t\tchapters = emptyList(),\n\t\tsource = source,\n\t)\n\n\tprivate fun <E : Enum<E>> fetchEnumSet(cls: Class<E>, path: String): EnumSet<E> {\n\t\tval uri = \"content://${source.authority}/$path\".toUri()\n\t\treturn contentResolver.query(uri, null, null, null, null)\n\t\t\t.safe()\n\t\t\t.use { cursor ->\n\t\t\t\tval result = EnumSet.noneOf(cls)\n\t\t\t\tval enumConstants = cls.enumConstants ?: return@use result\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tdo {\n\t\t\t\t\t\tval name = cursor.getString(COLUMN_NAME)\n\t\t\t\t\t\tval enumValue = enumConstants.find { it.name == name }\n\t\t\t\t\t\tif (enumValue != null) {\n\t\t\t\t\t\t\tresult.add(enumValue)\n\t\t\t\t\t\t}\n\t\t\t\t\t} while (cursor.moveToNext())\n\t\t\t\t}\n\t\t\t\tresult\n\t\t\t}\n\t}\n\n\tprivate fun Cursor?.safe() = ExternalPluginCursor(\n\t\tsource = source,\n\t\tcursor = this ?: throw IncompatiblePluginException(source.name, null),\n\t)\n\n\tclass MangaSourceCapabilities(\n\t\tval availableSortOrders: Set<SortOrder>,\n\t\tval listFilterCapabilities: MangaListFilterCapabilities,\n\t)\n\n\tprivate companion object {\n\n\t\tconst val COLUMN_SORT_ORDERS = \"sort_orders\"\n\t\tconst val COLUMN_MULTIPLE_TAGS = \"multiple_tags\"\n\t\tconst val COLUMN_TAGS_EXCLUSION = \"tags_exclusion\"\n\t\tconst val COLUMN_SEARCH = \"search\"\n\t\tconst val COLUMN_SEARCH_WITH_FILTERS = \"search_with_filters\"\n\t\tconst val COLUMN_YEAR = \"year\"\n\t\tconst val COLUMN_YEAR_RANGE = \"year_range\"\n\t\tconst val COLUMN_ORIGINAL_LOCALE = \"original_locale\"\n\t\tconst val COLUMN_ID = \"id\"\n\t\tconst val COLUMN_NAME = \"name\"\n\t\tconst val COLUMN_NUMBER = \"number\"\n\t\tconst val COLUMN_VOLUME = \"volume\"\n\t\tconst val COLUMN_URL = \"url\"\n\t\tconst val COLUMN_SCANLATOR = \"scanlator\"\n\t\tconst val COLUMN_UPLOAD_DATE = \"upload_date\"\n\t\tconst val COLUMN_BRANCH = \"branch\"\n\t\tconst val COLUMN_TITLE = \"title\"\n\t\tconst val COLUMN_ALT_TITLE = \"alt_title\"\n\t\tconst val COLUMN_PUBLIC_URL = \"public_url\"\n\t\tconst val COLUMN_RATING = \"rating\"\n\t\tconst val COLUMN_IS_NSFW = \"is_nsfw\"\n\t\tconst val COLUMN_COVER_URL = \"cover_url\"\n\t\tconst val COLUMN_TAGS = \"tags\"\n\t\tconst val COLUMN_STATE = \"state\"\n\t\tconst val COLUMN_AUTHOR = \"author\"\n\t\tconst val COLUMN_LARGE_COVER_URL = \"large_cover_url\"\n\t\tconst val COLUMN_DESCRIPTION = \"description\"\n\t\tconst val COLUMN_PREVIEW = \"preview\"\n\t\tconst val COLUMN_KEY = \"key\"\n\t\tconst val COLUMN_VALUE = \"value\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginCursor.kt",
    "content": "package org.koitharu.kotatsu.core.parser.external\n\nimport android.database.Cursor\nimport android.database.CursorWrapper\nimport org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException\nimport org.koitharu.kotatsu.core.util.ext.getBoolean\n\nclass ExternalPluginCursor(private val source: ExternalMangaSource, cursor: Cursor) : CursorWrapper(cursor) {\n\n\toverride fun getColumnIndexOrThrow(columnName: String?): Int = try {\n\t\tsuper.getColumnIndexOrThrow(columnName)\n\t} catch (e: Exception) {\n\t\tthrow IncompatiblePluginException(source.name, e)\n\t}\n\n\tfun getString(columnName: String): String = getString(getColumnIndexOrThrow(columnName))\n\n\tfun getStringOrNull(columnName: String): String? {\n\t\tval columnIndex = getColumnIndex(columnName)\n\t\treturn when {\n\t\t\tcolumnIndex < 0 -> null\n\t\t\tisNull(columnIndex) -> null\n\t\t\telse -> getString(columnIndex).takeUnless { it == \"null\" }\n\t\t}\n\t}\n\n\tfun getBoolean(columnName: String): Boolean = getBoolean(getColumnIndexOrThrow(columnName))\n\n\tfun getBooleanOrDefault(columnName: String, defaultValue: Boolean): Boolean {\n\t\tval columnIndex = getColumnIndex(columnName)\n\t\treturn when {\n\t\t\tcolumnIndex < 0 -> defaultValue\n\t\t\tisNull(columnIndex) -> defaultValue\n\t\t\telse -> getBoolean(columnIndex)\n\t\t}\n\t}\n\n\tfun getInt(columnName: String): Int = getInt(getColumnIndexOrThrow(columnName))\n\n\tfun getIntOrDefault(columnName: String, defaultValue: Int): Int {\n\t\tval columnIndex = getColumnIndex(columnName)\n\t\treturn when {\n\t\t\tcolumnIndex < 0 -> defaultValue\n\t\t\tisNull(columnIndex) -> defaultValue\n\t\t\telse -> getInt(columnIndex)\n\t\t}\n\t}\n\n\tfun getLong(columnName: String): Long = getLong(getColumnIndexOrThrow(columnName))\n\n\tfun getLongOrDefault(columnName: String, defaultValue: Long): Long {\n\t\tval columnIndex = getColumnIndex(columnName)\n\t\treturn when {\n\t\t\tcolumnIndex < 0 -> defaultValue\n\t\t\tisNull(columnIndex) -> defaultValue\n\t\t\telse -> getLong(columnIndex)\n\t\t}\n\t}\n\n\tfun getFloat(columnName: String): Float = getFloat(getColumnIndexOrThrow(columnName))\n\n\tfun getFloatOrDefault(columnName: String, defaultValue: Float): Float {\n\t\tval columnIndex = getColumnIndex(columnName)\n\t\treturn when {\n\t\t\tcolumnIndex < 0 -> defaultValue\n\t\t\tisNull(columnIndex) -> defaultValue\n\t\t\telse -> getFloat(columnIndex)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt",
    "content": "package org.koitharu.kotatsu.core.parser.favicon\n\nimport android.graphics.Color\nimport android.graphics.drawable.AdaptiveIconDrawable\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.LayerDrawable\nimport android.net.Uri\nimport android.os.Build\nimport coil3.ColorImage\nimport coil3.ImageLoader\nimport coil3.asImage\nimport coil3.decode.DataSource\nimport coil3.decode.ImageSource\nimport coil3.fetch.FetchResult\nimport coil3.fetch.Fetcher\nimport coil3.fetch.ImageFetchResult\nimport coil3.fetch.SourceFetchResult\nimport coil3.request.Options\nimport coil3.size.pxOrElse\nimport coil3.toAndroidUri\nimport coil3.toBitmap\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.runInterruptible\nimport okio.FileSystem\nimport okio.IOException\nimport okio.Path.Companion.toOkioPath\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.parser.EmptyMangaRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaRepository\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.fetch\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull\nimport org.koitharu.kotatsu.local.data.FaviconCache\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.data.LocalStorageCache\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\nimport javax.inject.Inject\nimport coil3.Uri as CoilUri\n\nclass FaviconFetcher(\n\tprivate val uri: Uri,\n\tprivate val options: Options,\n\tprivate val imageLoader: ImageLoader,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val localStorageCache: LocalStorageCache,\n) : Fetcher {\n\n\toverride suspend fun fetch(): FetchResult? {\n\t\tval mangaSource = MangaSource(uri.schemeSpecificPart)\n\n\t\treturn when (val repo = mangaRepositoryFactory.create(mangaSource)) {\n\t\t\tis ParserMangaRepository -> fetchParserFavicon(repo)\n\t\t\tis ExternalMangaRepository -> fetchPluginIcon(repo)\n\t\t\tis EmptyMangaRepository -> ImageFetchResult(\n\t\t\t\timage = ColorImage(Color.WHITE),\n\t\t\t\tisSampled = false,\n\t\t\t\tdataSource = DataSource.MEMORY,\n\t\t\t)\n\n\t\t\tis LocalMangaRepository -> imageLoader.fetch(R.drawable.ic_storage, options)\n\n\t\t\telse -> throw IllegalArgumentException(\"Unsupported repo ${repo.javaClass.simpleName}\")\n\t\t}\n\t}\n\n\tprivate suspend fun fetchParserFavicon(repository: ParserMangaRepository): FetchResult {\n\t\tval sizePx = maxOf(\n\t\t\toptions.size.width.pxOrElse { FALLBACK_SIZE },\n\t\t\toptions.size.height.pxOrElse { FALLBACK_SIZE },\n\t\t)\n\t\tval cacheKey = options.diskCacheKey ?: \"${repository.source.name}_$sizePx\"\n\t\tif (options.diskCachePolicy.readEnabled) {\n\t\t\tlocalStorageCache[cacheKey]?.let { file ->\n\t\t\t\treturn SourceFetchResult(\n\t\t\t\t\tsource = ImageSource(file.toOkioPath(), FileSystem.SYSTEM),\n\t\t\t\t\tmimeType = MimeTypes.probeMimeType(file)?.toString(),\n\t\t\t\t\tdataSource = DataSource.DISK,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tvar favicons = repository.getFavicons()\n\t\tvar lastError: Exception? = null\n\t\twhile (favicons.isNotEmpty()) {\n\t\t\tcurrentCoroutineContext().ensureActive()\n\t\t\tval icon = favicons.find(sizePx) ?: throwNSEE(lastError)\n\t\t\ttry {\n\t\t\t\tval result = imageLoader.fetch(icon.url, options)\n\t\t\t\tif (result != null) {\n\t\t\t\t\treturn if (options.diskCachePolicy.writeEnabled) {\n\t\t\t\t\t\twriteToCache(cacheKey, result)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfavicons -= icon\n\t\t\t\t}\n\t\t\t} catch (e: CloudFlareProtectedException) {\n\t\t\t\tthrow e\n\t\t\t} catch (e: IOException) {\n\t\t\t\tlastError = e\n\t\t\t\tfavicons -= icon\n\t\t\t}\n\t\t}\n\t\tthrowNSEE(lastError)\n\t}\n\n\tprivate suspend fun fetchPluginIcon(repository: ExternalMangaRepository): FetchResult {\n\t\tval source = repository.source\n\t\tval pm = options.context.packageManager\n\t\tval icon = runInterruptible {\n\t\t\tval provider = pm.resolveContentProvider(source.authority, 0)\n\t\t\tprovider?.loadIcon(pm) ?: pm.getApplicationIcon(source.packageName)\n\t\t}\n\t\treturn ImageFetchResult(\n\t\t\timage = icon.nonAdaptive().asImage(),\n\t\t\tisSampled = false,\n\t\t\tdataSource = DataSource.DISK,\n\t\t)\n\t}\n\n\tprivate suspend fun writeToCache(key: String, result: FetchResult): FetchResult = runCatchingCancellable {\n\t\twhen (result) {\n\t\t\tis ImageFetchResult -> {\n\t\t\t\tif (result.dataSource == DataSource.NETWORK) {\n\t\t\t\t\tlocalStorageCache.set(key, result.image.toBitmap()).asFetchResult()\n\t\t\t\t} else {\n\t\t\t\t\tresult\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tis SourceFetchResult -> {\n\t\t\t\tif (result.dataSource == DataSource.NETWORK) {\n\t\t\t\t\tresult.source.source().use {\n\t\t\t\t\t\tlocalStorageCache.set(key, it, result.mimeType?.toMimeTypeOrNull()).asFetchResult()\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresult\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrDefault(result)\n\n\tprivate fun File.asFetchResult() = SourceFetchResult(\n\t\tsource = ImageSource(toOkioPath(), FileSystem.SYSTEM),\n\t\tmimeType = MimeTypes.probeMimeType(this)?.toString(),\n\t\tdataSource = DataSource.DISK,\n\t)\n\n\tclass Factory @Inject constructor(\n\t\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\t\t@FaviconCache private val faviconCache: LocalStorageCache,\n\t) : Fetcher.Factory<CoilUri> {\n\n\t\toverride fun create(\n\t\t\tdata: CoilUri,\n\t\t\toptions: Options,\n\t\t\timageLoader: ImageLoader\n\t\t): Fetcher? = if (data.scheme == URI_SCHEME_FAVICON) {\n\t\t\tFaviconFetcher(data.toAndroidUri(), options, imageLoader, mangaRepositoryFactory, faviconCache)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate companion object {\n\n\t\tconst val FALLBACK_SIZE = 9999 // largest icon\n\n\t\tprivate fun throwNSEE(lastError: Exception?): Nothing {\n\t\t\tif (lastError != null) {\n\t\t\t\tthrow lastError\n\t\t\t} else {\n\t\t\t\tthrow NoSuchElementException(\"No favicons found\")\n\t\t\t}\n\t\t}\n\n\t\tprivate fun Drawable.nonAdaptive() =\n\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this is AdaptiveIconDrawable) {\n\t\t\t\tLayerDrawable(arrayOf(background, foreground))\n\t\t\t} else {\n\t\t\t\tthis\n\t\t\t}\n\n\t}\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/parser/favicon/FaviconUri.kt",
    "content": "package org.koitharu.kotatsu.core.parser.favicon\n\nimport android.net.Uri\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nconst val URI_SCHEME_FAVICON = \"favicon\"\n\nfun MangaSource.faviconUri(): Uri = Uri.fromParts(URI_SCHEME_FAVICON, name, null)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.content.pm.ActivityInfo\nimport android.net.ConnectivityManager\nimport android.net.Uri\nimport android.os.Build\nimport android.provider.Settings\nimport androidx.annotation.FloatRange\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.collection.ArraySet\nimport androidx.core.content.edit\nimport androidx.core.os.LocaleListCompat\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.preference.PreferenceManager\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.filter\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.onStart\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.ZoomMode\nimport org.koitharu.kotatsu.core.network.DoHProvider\nimport org.koitharu.kotatsu.core.util.ext.connectivityManager\nimport org.koitharu.kotatsu.core.util.ext.getEnumValue\nimport org.koitharu.kotatsu.core.util.ext.observeChanges\nimport org.koitharu.kotatsu.core.util.ext.putAll\nimport org.koitharu.kotatsu.core.util.ext.putEnumValue\nimport org.koitharu.kotatsu.core.util.ext.takeIfReadable\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.explore.data.SourcesSortOrder\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.find\nimport org.koitharu.kotatsu.parsers.util.mapNotNullToSet\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.reader.domain.ReaderColorFilter\nimport java.io.File\nimport java.net.Proxy\nimport java.util.EnumSet\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass AppSettings @Inject constructor(@ApplicationContext context: Context) {\n\n\tprivate val prefs = PreferenceManager.getDefaultSharedPreferences(context)\n\tprivate val connectivityManager = context.connectivityManager\n\tprivate val mangaListBadgesDefault = ArraySet(context.resources.getStringArray(R.array.values_list_badges))\n\n\tvar listMode: ListMode\n\t\tget() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.GRID)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) }\n\n\tval theme: Int\n\t\tget() = prefs.getString(KEY_THEME, null)?.toIntOrNull()\n\t\t\t?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM\n\n\tval colorScheme: ColorScheme\n\t\tget() = prefs.getEnumValue(KEY_COLOR_THEME, ColorScheme.default)\n\n\tval isAmoledTheme: Boolean\n\t\tget() = prefs.getBoolean(KEY_THEME_AMOLED, false)\n\n\tvar mainNavItems: List<NavItem>\n\t\tget() {\n\t\t\tval raw = prefs.getString(KEY_NAV_MAIN, null)?.split(',')\n\t\t\treturn if (raw.isNullOrEmpty()) {\n\t\t\t\tlistOf(NavItem.HISTORY, NavItem.FAVORITES, NavItem.EXPLORE, NavItem.FEED)\n\t\t\t} else {\n\t\t\t\traw.mapNotNull { x -> NavItem.entries.find(x) }.ifEmpty { listOf(NavItem.EXPLORE) }\n\t\t\t}\n\t\t}\n\t\tset(value) {\n\t\t\tprefs.edit {\n\t\t\t\tputString(KEY_NAV_MAIN, value.joinToString(\",\") { it.name })\n\t\t\t}\n\t\t}\n\n\tval isNavLabelsVisible: Boolean\n\t\tget() = prefs.getBoolean(KEY_NAV_LABELS, true)\n\n\tval isNavBarPinned: Boolean\n\t\tget() = prefs.getBoolean(KEY_NAV_PINNED, false)\n\n\tval isMainFabEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_MAIN_FAB, true)\n\n\tvar gridSize: Int\n\t\tget() = prefs.getInt(KEY_GRID_SIZE, 100)\n\t\tset(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }\n\n\tvar gridSizePages: Int\n\t\tget() = prefs.getInt(KEY_GRID_SIZE_PAGES, 100)\n\t\tset(value) = prefs.edit { putInt(KEY_GRID_SIZE_PAGES, value) }\n\n\tval isQuickFilterEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_QUICK_FILTER, true)\n\n\tval isDescriptionExpanded: Boolean\n\t\tget() = !prefs.getBoolean(KEY_COLLAPSE_DESCRIPTION, true)\n\n\tvar historyListMode: ListMode\n\t\tget() = prefs.getEnumValue(KEY_LIST_MODE_HISTORY, listMode)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_HISTORY, value) }\n\n\tvar suggestionsListMode: ListMode\n\t\tget() = prefs.getEnumValue(KEY_LIST_MODE_SUGGESTIONS, listMode)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_SUGGESTIONS, value) }\n\n\tvar favoritesListMode: ListMode\n\t\tget() = prefs.getEnumValue(KEY_LIST_MODE_FAVORITES, listMode)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_FAVORITES, value) }\n\n\tval isTagsWarningsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_TAGS_WARNINGS, true)\n\n\tvar isNsfwContentDisabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_DISABLE_NSFW, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_DISABLE_NSFW, value) }\n\n\tvar appLocales: LocaleListCompat\n\t\tget() {\n\t\t\tval raw = prefs.getString(KEY_APP_LOCALE, null)\n\t\t\treturn LocaleListCompat.forLanguageTags(raw)\n\t\t}\n\t\tset(value) {\n\t\t\tprefs.edit {\n\t\t\t\tputString(KEY_APP_LOCALE, value.toLanguageTags())\n\t\t\t}\n\t\t}\n\n\tvar isReaderDoubleOnLandscape: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_DOUBLE_PAGES, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_READER_DOUBLE_PAGES, value) }\n\n\tvar isReaderDoubleOnFoldable: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_DOUBLE_FOLDABLE, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_READER_DOUBLE_FOLDABLE, value) }\n\n\t@get:FloatRange(0.0, 1.0)\n\tvar readerDoublePagesSensitivity: Float\n\t\tget() = prefs.getFloat(KEY_READER_DOUBLE_PAGES_SENSITIVITY, 0.5f)\n\t\tset(@FloatRange(0.0, 1.0) value) = prefs.edit { putFloat(KEY_READER_DOUBLE_PAGES_SENSITIVITY, value) }\n\n\tval readerScreenOrientation: Int\n\t\tget() = prefs.getString(KEY_READER_ORIENTATION, null)?.toIntOrNull()\n\t\t\t?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED\n\n\tval isReaderVolumeButtonsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_VOLUME_BUTTONS, false)\n\n\tval isReaderZoomButtonsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_ZOOM_BUTTONS, false)\n\n\tval isReaderControlAlwaysLTR: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_CONTROL_LTR, false)\n\n\tval isReaderNavigationInverted: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_NAVIGATION_INVERTED, false)\n\n\tval isReaderFullscreenEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_FULLSCREEN, true)\n\n\tval isReaderOptimizationEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_OPTIMIZE, false)\n\n\tval readerControls: Set<ReaderControl>\n\t\tget() = prefs.getStringSet(KEY_READER_CONTROLS, null)?.mapNotNullTo(EnumSet.noneOf(ReaderControl::class.java)) {\n\t\t\tReaderControl.entries.find(it)\n\t\t} ?: ReaderControl.DEFAULT\n\n\tval isOfflineCheckDisabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_OFFLINE_DISABLED, false)\n\n\tvar isAllFavouritesVisible: Boolean\n\t\tget() = prefs.getBoolean(KEY_ALL_FAVOURITES_VISIBLE, true)\n\t\tset(value) = prefs.edit { putBoolean(KEY_ALL_FAVOURITES_VISIBLE, value) }\n\n\tval isTrackerEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_TRACKER_ENABLED, true)\n\n\tval isTrackerWifiOnly: Boolean\n\t\tget() = prefs.getBoolean(KEY_TRACKER_WIFI_ONLY, false)\n\n\tval trackerFrequencyFactor: Float\n\t\tget() = prefs.getString(KEY_TRACKER_FREQUENCY, null)?.toFloatOrNull() ?: 1f\n\n\tval isTrackerNotificationsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_TRACKER_NOTIFICATIONS, true)\n\n\tval isTrackerNsfwDisabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_TRACKER_NO_NSFW, false)\n\n\tval trackerDownloadStrategy: TrackerDownloadStrategy\n\t\tget() = prefs.getEnumValue(KEY_TRACKER_DOWNLOAD, TrackerDownloadStrategy.DISABLED)\n\n\tvar notificationSound: Uri\n\t\tget() = prefs.getString(KEY_NOTIFICATIONS_SOUND, null)?.toUriOrNull()\n\t\t\t?: Settings.System.DEFAULT_NOTIFICATION_URI\n\t\tset(value) = prefs.edit { putString(KEY_NOTIFICATIONS_SOUND, value.toString()) }\n\n\tval notificationVibrate: Boolean\n\t\tget() = prefs.getBoolean(KEY_NOTIFICATIONS_VIBRATE, false)\n\n\tval notificationLight: Boolean\n\t\tget() = prefs.getBoolean(KEY_NOTIFICATIONS_LIGHT, true)\n\n\tval readerAnimation: ReaderAnimation\n\t\tget() = prefs.getEnumValue(KEY_READER_ANIMATION, ReaderAnimation.DEFAULT)\n\n\tval readerBackground: ReaderBackground\n\t\tget() = prefs.getEnumValue(KEY_READER_BACKGROUND, ReaderBackground.DEFAULT)\n\n\tval defaultReaderMode: ReaderMode\n\t\tget() = prefs.getEnumValue(KEY_READER_MODE, ReaderMode.STANDARD)\n\n\tval isReaderModeDetectionEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_MODE_DETECT, true)\n\n\tvar isHistoryGroupingEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_HISTORY_GROUPING, true)\n\t\tset(value) = prefs.edit { putBoolean(KEY_HISTORY_GROUPING, value) }\n\n\tvar isUpdatedGroupingEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_UPDATED_GROUPING, true)\n\t\tset(value) = prefs.edit { putBoolean(KEY_UPDATED_GROUPING, value) }\n\n\tvar isFeedHeaderVisible: Boolean\n\t\tget() = prefs.getBoolean(KEY_FEED_HEADER, true)\n\t\tset(value) = prefs.edit { putBoolean(KEY_FEED_HEADER, value) }\n\n\tval progressIndicatorMode: ProgressIndicatorMode\n\t\tget() = prefs.getEnumValue(KEY_PROGRESS_INDICATORS, ProgressIndicatorMode.PERCENT_READ)\n\n\tvar incognitoModeForNsfw: TriStateOption\n\t\tget() = prefs.getEnumValue(KEY_INCOGNITO_NSFW, TriStateOption.ASK)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_INCOGNITO_NSFW, value) }\n\n\tvar isIncognitoModeEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_INCOGNITO_MODE, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_INCOGNITO_MODE, value) }\n\n\tval isReaderMultiTaskEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_MULTITASK, false)\n\n\tvar isChaptersReverse: Boolean\n\t\tget() = prefs.getBoolean(KEY_REVERSE_CHAPTERS, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_REVERSE_CHAPTERS, value) }\n\n\tvar isChaptersGridView: Boolean\n\t\tget() = prefs.getBoolean(KEY_GRID_VIEW_CHAPTERS, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_GRID_VIEW_CHAPTERS, value) }\n\n\tval zoomMode: ZoomMode\n\t\tget() = prefs.getEnumValue(KEY_ZOOM_MODE, ZoomMode.FIT_CENTER)\n\n\tval trackSources: Set<String>\n\t\tget() = prefs.getStringSet(KEY_TRACK_SOURCES, null) ?: setOf(TRACK_FAVOURITES)\n\n\tvar appPassword: String?\n\t\tget() = prefs.getString(KEY_APP_PASSWORD, null)\n\t\tset(value) = prefs.edit {\n\t\t\tif (value != null) putString(KEY_APP_PASSWORD, value) else remove(KEY_APP_PASSWORD)\n\t\t}\n\n\tvar isAppPasswordNumeric: Boolean\n\t\tget() = prefs.getBoolean(KEY_APP_PASSWORD_NUMERIC, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_APP_PASSWORD_NUMERIC, value) }\n\n\tval searchSuggestionTypes: Set<SearchSuggestionType>\n\t\tget() = prefs.getStringSet(KEY_SEARCH_SUGGESTION_TYPES, null)?.let { stringSet ->\n\t\t\tstringSet.mapNotNullTo(EnumSet.noneOf(SearchSuggestionType::class.java)) { x ->\n\t\t\t\tenumValueOf<SearchSuggestionType>(x)\n\t\t\t}\n\t\t} ?: EnumSet.allOf(SearchSuggestionType::class.java)\n\n\tvar isBiometricProtectionEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)\n\t\tset(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }\n\n\tval isMirrorSwitchingEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_MIRROR_SWITCHING, false)\n\n\tval isExitConfirmationEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_EXIT_CONFIRM, false)\n\n\tval isDynamicShortcutsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_SHORTCUTS, true)\n\n\tval isUnstableUpdatesAllowed: Boolean\n\t\tget() = prefs.getBoolean(KEY_UPDATES_UNSTABLE, false)\n\n\tval isPagesTabEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_PAGES_TAB, true)\n\n\tval defaultDetailsTab: Int\n\t\tget() = if (isPagesTabEnabled) {\n\t\t\tval raw = prefs.getString(KEY_DETAILS_TAB, null)?.toIntOrNull() ?: -1\n\t\t\tif (raw == -1) {\n\t\t\t\tlastDetailsTab\n\t\t\t} else {\n\t\t\t\traw\n\t\t\t}.coerceIn(0, 2)\n\t\t} else {\n\t\t\t0\n\t\t}\n\n\tvar lastDetailsTab: Int\n\t\tget() = prefs.getInt(KEY_DETAILS_LAST_TAB, 0)\n\t\tset(value) = prefs.edit { putInt(KEY_DETAILS_LAST_TAB, value) }\n\n\tval isContentPrefetchEnabled: Boolean\n\t\tget() {\n\t\t\tif (isBackgroundNetworkRestricted()) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tval policy =\n\t\t\t\tNetworkPolicy.from(prefs.getString(KEY_PREFETCH_CONTENT, null), NetworkPolicy.NEVER)\n\t\t\treturn policy.isNetworkAllowed(connectivityManager)\n\t\t}\n\n\tvar sourcesSortOrder: SourcesSortOrder\n\t\tget() = prefs.getEnumValue(KEY_SOURCES_ORDER, SourcesSortOrder.MANUAL)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_SOURCES_ORDER, value) }\n\n\tvar isSourcesGridMode: Boolean\n\t\tget() = prefs.getBoolean(KEY_SOURCES_GRID, true)\n\t\tset(value) = prefs.edit { putBoolean(KEY_SOURCES_GRID, value) }\n\n\tvar sourcesVersion: Int\n\t\tget() = prefs.getInt(KEY_SOURCES_VERSION, 0)\n\t\tset(value) = prefs.edit { putInt(KEY_SOURCES_VERSION, value) }\n\n\tvar isAllSourcesEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_SOURCES_ENABLED_ALL, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_SOURCES_ENABLED_ALL, value) }\n\n\tval isPagesNumbersEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_PAGES_NUMBERS, false)\n\n\tval screenshotsPolicy: ScreenshotsPolicy\n\t\tget() = prefs.getEnumValue(KEY_SCREENSHOTS_POLICY, ScreenshotsPolicy.ALLOW)\n\n\tval isAdBlockEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_ADBLOCK, false)\n\n\tvar userSpecifiedMangaDirectories: Set<File>\n\t\tget() {\n\t\t\tval set = prefs.getStringSet(KEY_LOCAL_MANGA_DIRS, emptySet()).orEmpty()\n\t\t\treturn set.mapNotNullToSet { File(it).takeIfReadable() }\n\t\t}\n\t\tset(value) {\n\t\t\tval set = value.mapToSet { it.absolutePath }\n\t\t\tprefs.edit { putStringSet(KEY_LOCAL_MANGA_DIRS, set) }\n\t\t}\n\n\tvar mangaStorageDir: File?\n\t\tget() = prefs.getString(KEY_LOCAL_STORAGE, null)?.let {\n\t\t\tFile(it)\n\t\t}?.takeIf { it.exists() && it in userSpecifiedMangaDirectories }\n\t\tset(value) = prefs.edit {\n\t\t\tif (value == null) {\n\t\t\t\tremove(KEY_LOCAL_STORAGE)\n\t\t\t} else {\n\t\t\t\tval userDirs = userSpecifiedMangaDirectories\n\t\t\t\tif (value !in userDirs) {\n\t\t\t\t\tuserSpecifiedMangaDirectories = userDirs + value\n\t\t\t\t}\n\t\t\t\tputString(KEY_LOCAL_STORAGE, value.path)\n\t\t\t}\n\t\t}\n\n\tvar allowDownloadOnMeteredNetwork: TriStateOption\n\t\tget() = prefs.getEnumValue(KEY_DOWNLOADS_METERED_NETWORK, TriStateOption.ASK)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_DOWNLOADS_METERED_NETWORK, value) }\n\n\tval preferredDownloadFormat: DownloadFormat\n\t\tget() = prefs.getEnumValue(KEY_DOWNLOADS_FORMAT, DownloadFormat.AUTOMATIC)\n\n\tvar isSuggestionsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_SUGGESTIONS, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_SUGGESTIONS, value) }\n\n\tval isSuggestionsWiFiOnly: Boolean\n\t\tget() = prefs.getBoolean(KEY_SUGGESTIONS_WIFI_ONLY, false)\n\n\tval isSuggestionsExcludeNsfw: Boolean\n\t\tget() = prefs.getBoolean(KEY_SUGGESTIONS_EXCLUDE_NSFW, false)\n\n\tval isSuggestionsIncludeDisabledSources: Boolean\n\t\tget() = prefs.getBoolean(KEY_SUGGESTIONS_DISABLED_SOURCES, false)\n\n\tval isSuggestionsNotificationAvailable: Boolean\n\t\tget() = prefs.getBoolean(KEY_SUGGESTIONS_NOTIFICATIONS, false)\n\n\tval suggestionsTagsBlacklist: Set<String>\n\t\tget() {\n\t\t\tval string = prefs.getString(KEY_SUGGESTIONS_EXCLUDE_TAGS, null)?.trimEnd(' ', ',')\n\t\t\tif (string.isNullOrEmpty()) {\n\t\t\t\treturn emptySet()\n\t\t\t}\n\t\t\treturn string.split(',').mapToSet { it.trim() }\n\t\t}\n\n\tval isReaderBarEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_BAR, true)\n\n\tval isReaderBarTransparent: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_BAR_TRANSPARENT, true)\n\n\tval isReaderChapterToastEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_CHAPTER_TOAST, true)\n\n\tval isReaderKeepScreenOn: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_SCREEN_ON, true)\n\n\tvar readerColorFilter: ReaderColorFilter?\n\t\tget() = runCatching {\n\t\t\tReaderColorFilter(\n\t\t\t\tbrightness = prefs.getFloat(KEY_CF_BRIGHTNESS, ReaderColorFilter.EMPTY.brightness),\n\t\t\t\tcontrast = prefs.getFloat(KEY_CF_CONTRAST, ReaderColorFilter.EMPTY.contrast),\n\t\t\t\tisInverted = prefs.getBoolean(KEY_CF_INVERTED, ReaderColorFilter.EMPTY.isInverted),\n\t\t\t\tisGrayscale = prefs.getBoolean(KEY_CF_GRAYSCALE, ReaderColorFilter.EMPTY.isGrayscale),\n\t\t\t\tisBookBackground = prefs.getBoolean(KEY_CF_BOOK, ReaderColorFilter.EMPTY.isBookBackground),\n\t\t\t).takeUnless { it.isEmpty }\n\t\t}.getOrNull()\n\t\tset(value) {\n\t\t\tprefs.edit {\n\t\t\t\tif (value != null) {\n\t\t\t\t\tputFloat(KEY_CF_BRIGHTNESS, value.brightness)\n\t\t\t\t\tputFloat(KEY_CF_CONTRAST, value.contrast)\n\t\t\t\t\tputBoolean(KEY_CF_INVERTED, value.isInverted)\n\t\t\t\t\tputBoolean(KEY_CF_GRAYSCALE, value.isGrayscale)\n\t\t\t\t\tputBoolean(KEY_CF_BOOK, value.isBookBackground)\n\t\t\t\t} else {\n\t\t\t\t\tremove(KEY_CF_BRIGHTNESS)\n\t\t\t\t\tremove(KEY_CF_CONTRAST)\n\t\t\t\t\tremove(KEY_CF_INVERTED)\n\t\t\t\t\tremove(KEY_CF_GRAYSCALE)\n\t\t\t\t\tremove(KEY_CF_BOOK)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tval imagesProxy: Int\n\t\tget() {\n\t\t\tval raw = prefs.getString(KEY_IMAGES_PROXY, null)?.toIntOrNull()\n\t\t\treturn raw ?: if (prefs.getBoolean(KEY_IMAGES_PROXY_OLD, false)) 0 else -1\n\t\t}\n\n\tval dnsOverHttps: DoHProvider\n\t\tget() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)\n\n\tvar isSSLBypassEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_SSL_BYPASS, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_SSL_BYPASS, value) }\n\n\tval proxyType: Proxy.Type\n\t\tget() {\n\t\t\tval raw = prefs.getString(KEY_PROXY_TYPE, null) ?: return Proxy.Type.DIRECT\n\t\t\treturn enumValues<Proxy.Type>().find { it.name == raw } ?: Proxy.Type.DIRECT\n\t\t}\n\n\tval proxyAddress: String?\n\t\tget() = prefs.getString(KEY_PROXY_ADDRESS, null)\n\n\tval proxyPort: Int\n\t\tget() = prefs.getString(KEY_PROXY_PORT, null)?.toIntOrNull() ?: 0\n\n\tval proxyLogin: String?\n\t\tget() = prefs.getString(KEY_PROXY_LOGIN, null)?.nullIfEmpty()\n\n\tval proxyPassword: String?\n\t\tget() = prefs.getString(KEY_PROXY_PASSWORD, null)?.nullIfEmpty()\n\n\tvar localListOrder: SortOrder\n\t\tget() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }\n\n\tvar historySortOrder: ListSortOrder\n\t\tget() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.LAST_READ)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_HISTORY_ORDER, value) }\n\n\tvar allFavoritesSortOrder: ListSortOrder\n\t\tget() = prefs.getEnumValue(KEY_FAVORITES_ORDER, ListSortOrder.NEWEST)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_FAVORITES_ORDER, value) }\n\n\tval isRelatedMangaEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_RELATED_MANGA, true)\n\n\tval isWebtoonZoomEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_WEBTOON_ZOOM, true)\n\n\tvar isWebtoonGapsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_WEBTOON_GAPS, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_WEBTOON_GAPS, value) }\n\n\tvar isWebtoonPullGestureEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_WEBTOON_PULL_GESTURE, false)\n\t\tset(value) = prefs.edit { putBoolean(KEY_WEBTOON_PULL_GESTURE, value) }\n\n\t@get:FloatRange(from = 0.0, to = 0.5)\n\tval defaultWebtoonZoomOut: Float\n\t\tget() = prefs.getInt(KEY_WEBTOON_ZOOM_OUT, 0).coerceIn(0, 50) / 100f\n\n\t@get:FloatRange(from = 0.0, to = 1.0)\n\tvar readerAutoscrollSpeed: Float\n\t\tget() = prefs.getFloat(KEY_READER_AUTOSCROLL_SPEED, 0f)\n\t\tset(@FloatRange(from = 0.0, to = 1.0) value) = prefs.edit {\n\t\t\tputFloat(\n\t\t\t\tKEY_READER_AUTOSCROLL_SPEED,\n\t\t\t\tvalue,\n\t\t\t)\n\t\t}\n\n\tvar isReaderAutoscrollFabVisible: Boolean\n\t\tget() = prefs.getBoolean(KEY_READER_AUTOSCROLL_FAB, true)\n\t\tset(value) = prefs.edit { putBoolean(KEY_READER_AUTOSCROLL_FAB, value) }\n\n\tval isPagesPreloadEnabled: Boolean\n\t\tget() {\n\t\t\tif (isBackgroundNetworkRestricted()) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tval policy = NetworkPolicy.from(\n\t\t\t\tprefs.getString(KEY_PAGES_PRELOAD, null),\n\t\t\t\tNetworkPolicy.NON_METERED,\n\t\t\t)\n\t\t\treturn policy.isNetworkAllowed(connectivityManager)\n\t\t}\n\n\tval is32BitColorsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_32BIT_COLOR, false)\n\n\tval isDiscordRpcEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_DISCORD_RPC, false)\n\n\tval isDiscordRpcSkipNsfw: Boolean\n\t\tget() = prefs.getBoolean(KEY_DISCORD_RPC_SKIP_NSFW, false)\n\n\tvar discordToken: String?\n\t\tget() = prefs.getString(KEY_DISCORD_TOKEN, null)?.trim()?.nullIfEmpty()\n\t\tset(value) = prefs.edit { putString(KEY_DISCORD_TOKEN, value?.nullIfEmpty()) }\n\n\tval isPeriodicalBackupEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_BACKUP_PERIODICAL_ENABLED, false)\n\n\tval periodicalBackupFrequency: Float\n\t\tget() = prefs.getString(KEY_BACKUP_PERIODICAL_FREQUENCY, null)?.toFloatOrNull() ?: 7f\n\n\tval periodicalBackupFrequencyMillis: Long\n\t\tget() = (TimeUnit.DAYS.toMillis(1) * periodicalBackupFrequency).toLong()\n\n\tval periodicalBackupMaxCount: Int\n\t\tget() = if (prefs.getBoolean(KEY_BACKUP_PERIODICAL_TRIM, true)) {\n\t\t\tprefs.getInt(KEY_BACKUP_PERIODICAL_COUNT, 10)\n\t\t} else {\n\t\t\tInt.MAX_VALUE\n\t\t}\n\n\tvar periodicalBackupDirectory: Uri?\n\t\tget() = prefs.getString(KEY_BACKUP_PERIODICAL_OUTPUT, null)?.toUriOrNull()\n\t\tset(value) = prefs.edit { putString(KEY_BACKUP_PERIODICAL_OUTPUT, value?.toString()) }\n\n\tval isBackupTelegramUploadEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_BACKUP_TG_ENABLED, false)\n\n\tval backupTelegramChatId: String?\n\t\tget() = prefs.getString(KEY_BACKUP_TG_CHAT, null)?.nullIfEmpty()\n\n\tval isReadingTimeEstimationEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_READING_TIME, true)\n\n\tval isPagesSavingAskEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_PAGES_SAVE_ASK, true)\n\n\tval isStatsEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_STATS_ENABLED, false)\n\n\tval isAutoLocalChaptersCleanupEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_CHAPTERS_CLEAR_AUTO, false)\n\n\tfun isPagesCropEnabled(mode: ReaderMode): Boolean {\n\t\tval rawValue = prefs.getStringSet(KEY_READER_CROP, emptySet())\n\t\tif (rawValue.isNullOrEmpty()) {\n\t\t\treturn false\n\t\t}\n\t\tval needle = if (mode == ReaderMode.WEBTOON) READER_CROP_WEBTOON else READER_CROP_PAGED\n\t\treturn needle.toString() in rawValue\n\t}\n\n\tfun isTipEnabled(tip: String): Boolean {\n\t\treturn prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true\n\t}\n\n\tfun closeTip(tip: String) {\n\t\tval closedTips = prefs.getStringSet(KEY_TIPS_CLOSED, emptySet()).orEmpty()\n\t\tif (tip in closedTips) {\n\t\t\treturn\n\t\t}\n\t\tprefs.edit { putStringSet(KEY_TIPS_CLOSED, closedTips + tip) }\n\t}\n\n\tfun isIncognitoModeEnabled(isNsfw: Boolean): Boolean {\n\t\treturn isIncognitoModeEnabled || (isNsfw && incognitoModeForNsfw == TriStateOption.ENABLED)\n\t}\n\n\tfun getPagesSaveDir(context: Context): DocumentFile? =\n\t\tprefs.getString(KEY_PAGES_SAVE_DIR, null)?.toUriOrNull()?.let {\n\t\t\tDocumentFile.fromTreeUri(context, it)?.takeIf { it.canWrite() }\n\t\t}\n\n\tfun setPagesSaveDir(uri: Uri?) {\n\t\tprefs.edit { putString(KEY_PAGES_SAVE_DIR, uri?.toString()) }\n\t}\n\n\tfun getMangaListBadges(): Int {\n\t\tval raw = prefs.getStringSet(KEY_MANGA_LIST_BADGES, mangaListBadgesDefault).orEmpty()\n\t\tvar result = 0\n\t\tfor (item in raw) {\n\t\t\tresult = result or item.toInt()\n\t\t}\n\t\treturn result\n\t}\n\n\tfun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {\n\t\tprefs.registerOnSharedPreferenceChangeListener(listener)\n\t}\n\n\tfun unsubscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {\n\t\tprefs.unregisterOnSharedPreferenceChangeListener(listener)\n\t}\n\n\tfun observeChanges() = prefs.observeChanges()\n\n\tfun observe(vararg keys: String): Flow<String?> = prefs.observeChanges()\n\t\t.filter { key -> key == null || key in keys }\n\t\t.onStart { emit(null) }\n\t\t.flowOn(Dispatchers.IO)\n\n\tfun getAllValues(): Map<String, *> = prefs.all\n\n\tfun upsertAll(m: Map<String, *>) = prefs.edit {\n\t\tclear()\n\t\tputAll(m)\n\t}\n\n\tprivate fun isBackgroundNetworkRestricted(): Boolean {\n\t\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n\t\t\tconnectivityManager.restrictBackgroundStatus == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tconst val TRACK_HISTORY = \"history\"\n\t\tconst val TRACK_FAVOURITES = \"favourites\"\n\n\t\tconst val KEY_ADBLOCK = \"adblock\"\n\t\tconst val KEY_LIST_MODE = \"list_mode_2\"\n\t\tconst val KEY_LIST_MODE_HISTORY = \"list_mode_history\"\n\t\tconst val KEY_LIST_MODE_FAVORITES = \"list_mode_favorites\"\n\t\tconst val KEY_LIST_MODE_SUGGESTIONS = \"list_mode_suggestions\"\n\t\tconst val KEY_THEME = \"theme\"\n\t\tconst val KEY_COLOR_THEME = \"color_theme\"\n\t\tconst val KEY_THEME_AMOLED = \"amoled_theme\"\n\t\tconst val KEY_OFFLINE_DISABLED = \"no_offline\"\n\t\tconst val KEY_PAGES_CACHE_CLEAR = \"pages_cache_clear\"\n\t\tconst val KEY_HTTP_CACHE_CLEAR = \"http_cache_clear\"\n\t\tconst val KEY_COOKIES_CLEAR = \"cookies_clear\"\n\t\tconst val KEY_CHAPTERS_CLEAR = \"chapters_clear\"\n\t\tconst val KEY_CHAPTERS_CLEAR_AUTO = \"chapters_clear_auto\"\n\t\tconst val KEY_THUMBS_CACHE_CLEAR = \"thumbs_cache_clear\"\n\t\tconst val KEY_SEARCH_HISTORY_CLEAR = \"search_history_clear\"\n\t\tconst val KEY_UPDATES_FEED_CLEAR = \"updates_feed_clear\"\n\t\tconst val KEY_GRID_SIZE = \"grid_size\"\n\t\tconst val KEY_GRID_SIZE_PAGES = \"grid_size_pages\"\n\t\tconst val KEY_REMOTE_SOURCES = \"remote_sources\"\n\t\tconst val KEY_LOCAL_STORAGE = \"local_storage\"\n\t\tconst val KEY_READER_DOUBLE_PAGES = \"reader_double_pages\"\n\t\tconst val KEY_READER_DOUBLE_PAGES_SENSITIVITY = \"reader_double_pages_sensitivity_2\"\n\t\tconst val KEY_READER_DOUBLE_FOLDABLE = \"reader_double_foldable\"\n\t\tconst val KEY_READER_ZOOM_BUTTONS = \"reader_zoom_buttons\"\n\t\tconst val KEY_READER_CONTROL_LTR = \"reader_taps_ltr\"\n\t\tconst val KEY_READER_NAVIGATION_INVERTED = \"reader_navigation_inverted\"\n\t\tconst val KEY_READER_FULLSCREEN = \"reader_fullscreen\"\n\t\tconst val KEY_READER_VOLUME_BUTTONS = \"reader_volume_buttons\"\n\t\tconst val KEY_READER_ORIENTATION = \"reader_orientation\"\n\t\tconst val KEY_TRACKER_ENABLED = \"tracker_enabled\"\n\t\tconst val KEY_TRACKER_WIFI_ONLY = \"tracker_wifi\"\n\t\tconst val KEY_TRACKER_FREQUENCY = \"tracker_freq\"\n\t\tconst val KEY_TRACK_SOURCES = \"track_sources\"\n\t\tconst val KEY_TRACK_CATEGORIES = \"track_categories\"\n\t\tconst val KEY_TRACK_WARNING = \"track_warning\"\n\t\tconst val KEY_TRACKER_NOTIFICATIONS = \"tracker_notifications\"\n\t\tconst val KEY_TRACKER_NO_NSFW = \"tracker_no_nsfw\"\n\t\tconst val KEY_TRACKER_DOWNLOAD = \"tracker_download\"\n\t\tconst val KEY_NOTIFICATIONS_SETTINGS = \"notifications_settings\"\n\t\tconst val KEY_NOTIFICATIONS_SOUND = \"notifications_sound\"\n\t\tconst val KEY_NOTIFICATIONS_VIBRATE = \"notifications_vibrate\"\n\t\tconst val KEY_NOTIFICATIONS_LIGHT = \"notifications_light\"\n\t\tconst val KEY_NOTIFICATIONS_INFO = \"tracker_notifications_info\"\n\t\tconst val KEY_READER_ANIMATION = \"reader_animation2\"\n\t\tconst val KEY_READER_CONTROLS = \"reader_controls\"\n\t\tconst val KEY_READER_MODE = \"reader_mode\"\n\t\tconst val KEY_READER_MODE_DETECT = \"reader_mode_detect\"\n\t\tconst val KEY_READER_CROP = \"reader_crop\"\n\t\tconst val KEY_APP_PASSWORD = \"app_password\"\n\t\tconst val KEY_APP_PASSWORD_NUMERIC = \"app_password_num\"\n\t\tconst val KEY_PROTECT_APP = \"protect_app\"\n\t\tconst val KEY_PROTECT_APP_BIOMETRIC = \"protect_app_bio\"\n\t\tconst val KEY_ZOOM_MODE = \"zoom_mode\"\n\t\tconst val KEY_BACKUP = \"backup\"\n\t\tconst val KEY_RESTORE = \"restore\"\n\t\tconst val KEY_BACKUP_PERIODICAL_ENABLED = \"backup_periodic\"\n\t\tconst val KEY_BACKUP_PERIODICAL_FREQUENCY = \"backup_periodic_freq\"\n\t\tconst val KEY_BACKUP_PERIODICAL_TRIM = \"backup_periodic_trim\"\n\t\tconst val KEY_BACKUP_PERIODICAL_COUNT = \"backup_periodic_count\"\n\t\tconst val KEY_BACKUP_PERIODICAL_OUTPUT = \"backup_periodic_output\"\n\t\tconst val KEY_BACKUP_PERIODICAL_LAST = \"backup_periodic_last\"\n\t\tconst val KEY_HISTORY_GROUPING = \"history_grouping\"\n\t\tconst val KEY_UPDATED_GROUPING = \"updated_grouping\"\n\t\tconst val KEY_PROGRESS_INDICATORS = \"progress_indicators\"\n\t\tconst val KEY_REVERSE_CHAPTERS = \"reverse_chapters\"\n\t\tconst val KEY_GRID_VIEW_CHAPTERS = \"grid_view_chapters\"\n\t\tconst val KEY_INCOGNITO_NSFW = \"incognito_nsfw\"\n\t\tconst val KEY_PAGES_NUMBERS = \"pages_numbers\"\n\t\tconst val KEY_SCREENSHOTS_POLICY = \"screenshots_policy\"\n\t\tconst val KEY_PAGES_PRELOAD = \"pages_preload\"\n\t\tconst val KEY_SUGGESTIONS = \"suggestions\"\n\t\tconst val KEY_SUGGESTIONS_WIFI_ONLY = \"suggestions_wifi\"\n\t\tconst val KEY_SUGGESTIONS_EXCLUDE_NSFW = \"suggestions_exclude_nsfw\"\n\t\tconst val KEY_SUGGESTIONS_EXCLUDE_TAGS = \"suggestions_exclude_tags\"\n\t\tconst val KEY_SUGGESTIONS_DISABLED_SOURCES = \"suggestions_disabled_sources\"\n\t\tconst val KEY_SUGGESTIONS_NOTIFICATIONS = \"suggestions_notifications\"\n\t\tconst val KEY_SHIKIMORI = \"shikimori\"\n\t\tconst val KEY_ANILIST = \"anilist\"\n\t\tconst val KEY_MAL = \"mal\"\n\t\tconst val KEY_KITSU = \"kitsu\"\n\t\tconst val KEY_DOWNLOADS_METERED_NETWORK = \"downloads_metered_network\"\n\t\tconst val KEY_DOWNLOADS_FORMAT = \"downloads_format\"\n\t\tconst val KEY_ALL_FAVOURITES_VISIBLE = \"all_favourites_visible\"\n\t\tconst val KEY_DOH = \"doh\"\n\t\tconst val KEY_EXIT_CONFIRM = \"exit_confirm\"\n\t\tconst val KEY_INCOGNITO_MODE = \"incognito\"\n\t\tconst val KEY_READER_MULTITASK = \"reader_multitask\"\n\t\tconst val KEY_SYNC = \"sync\"\n\t\tconst val KEY_SYNC_SETTINGS = \"sync_settings\"\n\t\tconst val KEY_READER_BAR = \"reader_bar\"\n\t\tconst val KEY_READER_BAR_TRANSPARENT = \"reader_bar_transparent\"\n\t\tconst val KEY_READER_CHAPTER_TOAST = \"reader_chapter_toast\"\n\t\tconst val KEY_READER_BACKGROUND = \"reader_background\"\n\t\tconst val KEY_READER_SCREEN_ON = \"reader_screen_on\"\n\t\tconst val KEY_SHORTCUTS = \"dynamic_shortcuts\"\n\t\tconst val KEY_READER_TAP_ACTIONS = \"reader_tap_actions\"\n\t\tconst val KEY_READER_OPTIMIZE = \"reader_optimize\"\n\t\tconst val KEY_LOCAL_LIST_ORDER = \"local_order\"\n\t\tconst val KEY_HISTORY_ORDER = \"history_order\"\n\t\tconst val KEY_FAVORITES_ORDER = \"fav_order\"\n\t\tconst val KEY_WEBTOON_GAPS = \"webtoon_gaps\"\n\t\tconst val KEY_WEBTOON_ZOOM = \"webtoon_zoom\"\n\t\tconst val KEY_WEBTOON_ZOOM_OUT = \"webtoon_zoom_out\"\n\t\tconst val KEY_WEBTOON_PULL_GESTURE = \"webtoon_pull_gesture\"\n\t\tconst val KEY_PREFETCH_CONTENT = \"prefetch_content\"\n\t\tconst val KEY_APP_LOCALE = \"app_locale\"\n\t\tconst val KEY_SOURCES_GRID = \"sources_grid\"\n\t\tconst val KEY_UPDATES_UNSTABLE = \"updates_unstable\"\n\t\tconst val KEY_TIPS_CLOSED = \"tips_closed\"\n\t\tconst val KEY_SSL_BYPASS = \"ssl_bypass\"\n\t\tconst val KEY_READER_AUTOSCROLL_SPEED = \"as_speed\"\n\t\tconst val KEY_READER_AUTOSCROLL_FAB = \"as_fab\"\n\t\tconst val KEY_MIRROR_SWITCHING = \"mirror_switching\"\n\t\tconst val KEY_PROXY = \"proxy\"\n\t\tconst val KEY_PROXY_TYPE = \"proxy_type_2\"\n\t\tconst val KEY_PROXY_ADDRESS = \"proxy_address\"\n\t\tconst val KEY_PROXY_PORT = \"proxy_port\"\n\t\tconst val KEY_PROXY_AUTH = \"proxy_auth\"\n\t\tconst val KEY_PROXY_LOGIN = \"proxy_login\"\n\t\tconst val KEY_PROXY_PASSWORD = \"proxy_password\"\n\t\tconst val KEY_IMAGES_PROXY = \"images_proxy_2\"\n\t\tconst val KEY_LOCAL_MANGA_DIRS = \"local_manga_dirs\"\n\t\tconst val KEY_DISABLE_NSFW = \"no_nsfw\"\n\t\tconst val KEY_RELATED_MANGA = \"related_manga\"\n\t\tconst val KEY_NAV_MAIN = \"nav_main\"\n\t\tconst val KEY_NAV_LABELS = \"nav_labels\"\n\t\tconst val KEY_NAV_PINNED = \"nav_pinned\"\n\t\tconst val KEY_MAIN_FAB = \"main_fab\"\n\t\tconst val KEY_32BIT_COLOR = \"enhanced_colors\"\n\t\tconst val KEY_SOURCES_ORDER = \"sources_sort_order\"\n\t\tconst val KEY_SOURCES_CATALOG = \"sources_catalog\"\n\t\tconst val KEY_CF_BRIGHTNESS = \"cf_brightness\"\n\t\tconst val KEY_CF_CONTRAST = \"cf_contrast\"\n\t\tconst val KEY_CF_INVERTED = \"cf_inverted\"\n\t\tconst val KEY_CF_GRAYSCALE = \"cf_grayscale\"\n\t\tconst val KEY_CF_BOOK = \"cf_book\"\n\t\tconst val KEY_PAGES_TAB = \"pages_tab\"\n\t\tconst val KEY_DETAILS_TAB = \"details_tab\"\n\t\tconst val KEY_DETAILS_LAST_TAB = \"details_last_tab\"\n\t\tconst val KEY_READING_TIME = \"reading_time\"\n\t\tconst val KEY_PAGES_SAVE_DIR = \"pages_dir\"\n\t\tconst val KEY_PAGES_SAVE_ASK = \"pages_dir_ask\"\n\t\tconst val KEY_STATS_ENABLED = \"stats_on\"\n\t\tconst val KEY_FEED_HEADER = \"feed_header\"\n\t\tconst val KEY_SEARCH_SUGGESTION_TYPES = \"search_suggest_types\"\n\t\tconst val KEY_SOURCES_VERSION = \"sources_version\"\n\t\tconst val KEY_SOURCES_ENABLED_ALL = \"sources_enabled_all\"\n\t\tconst val KEY_QUICK_FILTER = \"quick_filter\"\n\t\tconst val KEY_COLLAPSE_DESCRIPTION = \"description_collapse\"\n\t\tconst val KEY_BACKUP_TG_ENABLED = \"backup_periodic_tg_enabled\"\n\t\tconst val KEY_BACKUP_TG_CHAT = \"backup_periodic_tg_chat_id\"\n\t\tconst val KEY_MANGA_LIST_BADGES = \"manga_list_badges\"\n\t\tconst val KEY_TAGS_WARNINGS = \"tags_warnings\"\n\t\tconst val KEY_DISCORD_RPC = \"discord_rpc\"\n\t\tconst val KEY_DISCORD_RPC_SKIP_NSFW = \"discord_rpc_skip_nsfw\"\n\t\tconst val KEY_DISCORD_TOKEN = \"discord_token\"\n\n\t\t// keys for non-persistent preferences\n\t\tconst val KEY_APP_VERSION = \"app_version\"\n\t\tconst val KEY_IGNORE_DOZE = \"ignore_dose\"\n\t\tconst val KEY_TRACKER_DEBUG = \"tracker_debug\"\n\t\tconst val KEY_LINK_WEBLATE = \"about_app_translation\"\n\t\tconst val KEY_LINK_TELEGRAM = \"about_telegram\"\n\t\tconst val KEY_LINK_GITHUB = \"about_github\"\n\t\tconst val KEY_LINK_MANUAL = \"about_help\"\n\t\tconst val KEY_PROXY_TEST = \"proxy_test\"\n\t\tconst val KEY_OPEN_BROWSER = \"open_browser\"\n\t\tconst val KEY_HANDLE_LINKS = \"handle_links\"\n\t\tconst val KEY_BACKUP_TG = \"backup_periodic_tg\"\n\t\tconst val KEY_BACKUP_TG_OPEN = \"backup_periodic_tg_open\"\n\t\tconst val KEY_BACKUP_TG_TEST = \"backup_periodic_tg_test\"\n\t\tconst val KEY_CLEAR_MANGA_DATA = \"manga_data_clear\"\n\t\tconst val KEY_STORAGE_USAGE = \"storage_usage\"\n\t\tconst val KEY_WEBVIEW_CLEAR = \"webview_clear\"\n\n\t\t// old keys are for migration only\n\t\tprivate const val KEY_IMAGES_PROXY_OLD = \"images_proxy\"\n\n\t\t// values\n\t\tprivate const val READER_CROP_PAGED = 1\n\t\tprivate const val READER_CROP_WEBTOON = 2\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.transform\n\nfun <T> AppSettings.observeAsFlow(key: String, valueProducer: AppSettings.() -> T) = flow {\n\tvar lastValue: T = valueProducer()\n\temit(lastValue)\n\tobserveChanges().collect {\n\t\tif (it == key) {\n\t\t\tval value = valueProducer()\n\t\t\tif (value != lastValue) {\n\t\t\t\temit(value)\n\t\t\t}\n\t\t\tlastValue = value\n\t\t}\n\t}\n}\n\nfun <T> AppSettings.observeAsStateFlow(\n\tscope: CoroutineScope,\n\tkey: String,\n\tvalueProducer: AppSettings.() -> T,\n): StateFlow<T> = observeChanges().transform {\n\tif (it == key) {\n\t\temit(valueProducer())\n\t}\n}.stateIn(scope, SharingStarted.Eagerly, valueProducer())\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppWidgetConfig.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport android.appwidget.AppWidgetProvider\nimport android.content.Context\nimport android.os.Build\nimport androidx.core.content.edit\n\nprivate const val CATEGORY_ID = \"cat_id\"\nprivate const val BACKGROUND = \"bg\"\n\nclass AppWidgetConfig(\n\tcontext: Context,\n\tcls: Class<out AppWidgetProvider>,\n\tval widgetId: Int,\n) {\n\n\tprivate val prefs = context.getSharedPreferences(\"appwidget_${cls.simpleName}_$widgetId\", Context.MODE_PRIVATE)\n\n\tvar categoryId: Long\n\t\tget() = prefs.getLong(CATEGORY_ID, 0L)\n\t\tset(value) = prefs.edit { putLong(CATEGORY_ID, value) }\n\n\tvar hasBackground: Boolean\n\t\tget() = prefs.getBoolean(BACKGROUND, Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)\n\t\tset(value) = prefs.edit { putBoolean(BACKGROUND, value) }\n\n\tfun clear() {\n\t\tprefs.edit { clear() }\n\t}\n\n\tfun copyFrom(other: AppWidgetConfig) {\n\t\tprefs.edit {\n\t\t\tclear()\n\t\t\tputLong(CATEGORY_ID, other.categoryId)\n\t\t\tputBoolean(BACKGROUND, other.hasBackground)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ColorScheme.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\nimport androidx.annotation.StringRes\nimport androidx.annotation.StyleRes\nimport com.google.android.material.color.DynamicColors\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.util.find\n\n@Keep\nenum class ColorScheme(\n\t@StyleRes val styleResId: Int,\n\t@StringRes val titleResId: Int,\n) {\n\n\tDEFAULT(R.style.ThemeOverlay_Kotatsu_Totoro, R.string.theme_name_totoro),\n\tMONET(R.style.ThemeOverlay_Kotatsu_Monet, R.string.theme_name_dynamic),\n\tEXPRESSIVE(R.style.ThemeOverlay_Kotatsu_Expressive, R.string.theme_name_expressive),\n\tMIKU(R.style.ThemeOverlay_Kotatsu_Miku, R.string.theme_name_miku),\n\tRENA(R.style.ThemeOverlay_Kotatsu_Asuka, R.string.theme_name_asuka),\n\tFROG(R.style.ThemeOverlay_Kotatsu_Mion, R.string.theme_name_mion),\n\tBLUEBERRY(R.style.ThemeOverlay_Kotatsu_Rikka, R.string.theme_name_rikka),\n\tSAKURA(R.style.ThemeOverlay_Kotatsu_Sakura, R.string.theme_name_sakura),\n\tMAMIMI(R.style.ThemeOverlay_Kotatsu_Mamimi, R.string.theme_name_mamimi),\n\tKANADE(R.style.ThemeOverlay_Kotatsu_Kanade, R.string.theme_name_kanade),\n\tITSUKA(R.style.ThemeOverlay_Kotatsu_Itsuka, R.string.theme_name_itsuka),\n\t;\n\n\tcompanion object {\n\n\t\tval default: ColorScheme\n\t\t\tget() = if (DynamicColors.isDynamicColorAvailable()) {\n\t\t\t\tMONET\n\t\t\t} else {\n\t\t\t\tDEFAULT\n\t\t\t}\n\n\t\tfun getAvailableList(): List<ColorScheme> {\n\t\t\tval list = ColorScheme.entries.toMutableList()\n\t\t\tif (!DynamicColors.isDynamicColorAvailable()) {\n\t\t\t\tlist.remove(MONET)\n\t\t\t\tlist.remove(EXPRESSIVE)\n\t\t\t}\n\t\t\treturn list\n\t\t}\n\n\t\tfun safeValueOf(name: String): ColorScheme? {\n\t\t\treturn ColorScheme.entries.find(name)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/DownloadFormat.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class DownloadFormat {\n\n\tAUTOMATIC,\n\tSINGLE_CBZ,\n\tMULTIPLE_CBZ,\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ListMode.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class ListMode {\n\n\tLIST, DETAILED_LIST, GRID;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/NavItem.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.IdRes\nimport androidx.annotation.Keep\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\n\n@Keep\nenum class NavItem(\n\t@IdRes val id: Int,\n\t@StringRes val title: Int,\n\t@DrawableRes val icon: Int,\n) {\n\n\tHISTORY(R.id.nav_history, R.string.history, R.drawable.ic_history_selector),\n\tFAVORITES(R.id.nav_favorites, R.string.favourites, R.drawable.ic_favourites_selector),\n\tLOCAL(R.id.nav_local, R.string.on_device, R.drawable.ic_storage_selector),\n\tEXPLORE(R.id.nav_explore, R.string.explore, R.drawable.ic_explore_selector),\n\tSUGGESTIONS(R.id.nav_suggestions, R.string.suggestions, R.drawable.ic_suggestion_selector),\n\tFEED(R.id.nav_feed, R.string.feed, R.drawable.ic_feed_selector),\n\tUPDATED(R.id.nav_updated, R.string.updated, R.drawable.ic_updated_selector),\n\tBOOKMARKS(R.id.nav_bookmarks, R.string.bookmarks, R.drawable.ic_bookmark_selector),\n\t;\n\n\tfun isAvailable(settings: AppSettings): Boolean = when (this) {\n\t\tSUGGESTIONS -> settings.isSuggestionsEnabled\n\t\tUPDATED, FEED -> settings.isTrackerEnabled\n\t\telse -> true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/NetworkPolicy.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport android.net.ConnectivityManager\nimport androidx.annotation.Keep\n\n@Keep\nenum class NetworkPolicy(\n\tprivate val key: Int,\n) {\n\n\tNEVER(0),\n\tALWAYS(1),\n\tNON_METERED(2);\n\n\tfun isNetworkAllowed(cm: ConnectivityManager) = when (this) {\n\t\tNEVER -> false\n\t\tALWAYS -> true\n\t\tNON_METERED -> !cm.isActiveNetworkMetered\n\t}\n\n\tcompanion object {\n\n\t\tfun from(key: String?, default: NetworkPolicy): NetworkPolicy {\n\t\t\tval intKey = key?.toIntOrNull() ?: return default\n\t\t\treturn NetworkPolicy.entries.find { it.key == intKey } ?: default\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ProgressIndicatorMode.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class ProgressIndicatorMode {\n\n\tNONE, PERCENT_READ, PERCENT_LEFT, CHAPTERS_READ, CHAPTERS_LEFT;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderAnimation.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class ReaderAnimation {\n\n\t// Do not rename this\n\tNONE, DEFAULT, ADVANCED;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderBackground.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.view.ContextThemeWrapper\nimport androidx.annotation.Keep\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.drawable.toDrawable\nimport org.koitharu.kotatsu.core.util.ext.getThemeDrawable\nimport org.koitharu.kotatsu.core.util.ext.isNightMode\nimport com.google.android.material.R as materialR\n\n@Keep\nenum class ReaderBackground {\n\n\tDEFAULT, LIGHT, DARK, WHITE, BLACK;\n\n\tfun resolve(context: Context): Drawable? = when (this) {\n\t\tDEFAULT -> context.getThemeDrawable(android.R.attr.windowBackground)\n\t\tLIGHT -> ContextThemeWrapper(context, materialR.style.ThemeOverlay_Material3_Light)\n\t\t\t.getThemeDrawable(android.R.attr.windowBackground)\n\n\t\tDARK -> ContextThemeWrapper(context, materialR.style.ThemeOverlay_Material3_Dark)\n\t\t\t.getThemeDrawable(android.R.attr.windowBackground)\n\n\t\tWHITE -> ContextCompat.getColor(context, android.R.color.white).toDrawable()\n\t\tBLACK -> ContextCompat.getColor(context, android.R.color.black).toDrawable()\n\t}\n\n\tfun isLight(context: Context): Boolean = when (this) {\n\t\tDEFAULT -> !context.resources.isNightMode\n\n\t\tLIGHT,\n\t\tWHITE -> true\n\n\t\tDARK,\n\t\tBLACK -> false\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderControl.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport java.util.EnumSet\n\nenum class ReaderControl {\n\n\tPREV_CHAPTER, NEXT_CHAPTER, SLIDER, PAGES_SHEET, SCREEN_ROTATION, SAVE_PAGE, TIMER, BOOKMARK;\n\n\tcompanion object {\n\n\t\tval DEFAULT: Set<ReaderControl> = EnumSet.of(\n\t\t\tPREV_CHAPTER, NEXT_CHAPTER, SLIDER, PAGES_SHEET,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ReaderMode.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class ReaderMode(val id: Int) {\n\n\tSTANDARD(1),\n\tREVERSED(3),\n\tVERTICAL(4),\n\tWEBTOON(2),\n\t;\n\n\tcompanion object {\n\n\t\tfun valueOf(id: Int) = entries.firstOrNull { it.id == id }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class ScreenshotsPolicy {\n\n\t// Do not rename this\n\tALLOW, BLOCK_NSFW, BLOCK_INCOGNITO, BLOCK_ALL;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\n\n@Keep\nenum class SearchSuggestionType(\n\t@StringRes val titleResId: Int,\n) {\n\n\tGENRES(R.string.genres),\n\tQUERIES_RECENT(R.string.recent_queries),\n\tQUERIES_SUGGEST(R.string.suggested_queries),\n\tMANGA(R.string.content_type_manga),\n\tSOURCES(R.string.remote_sources),\n\tRECENT_SOURCES(R.string.recent_sources),\n\tAUTHORS(R.string.authors),\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport android.content.Context\nimport android.content.SharedPreferences.OnSharedPreferenceChangeListener\nimport androidx.core.content.edit\nimport org.koitharu.kotatsu.core.util.ext.getEnumValue\nimport org.koitharu.kotatsu.core.util.ext.putEnumValue\nimport org.koitharu.kotatsu.core.util.ext.sanitizeHeaderValue\nimport org.koitharu.kotatsu.parsers.config.ConfigKey\nimport org.koitharu.kotatsu.parsers.config.MangaSourceConfig\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.settings.utils.validation.DomainValidator\nimport java.io.File\n\nclass SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig {\n\n    private val prefs = context.getSharedPreferences(\n        source.name.replace(File.separatorChar, '$'),\n        Context.MODE_PRIVATE,\n    )\n\n\tvar defaultSortOrder: SortOrder?\n\t\tget() = prefs.getEnumValue(KEY_SORT_ORDER, SortOrder::class.java)\n\t\tset(value) = prefs.edit { putEnumValue(KEY_SORT_ORDER, value) }\n\n\tval isSlowdownEnabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_SLOWDOWN, false)\n\n\tval isCaptchaNotificationsDisabled: Boolean\n\t\tget() = prefs.getBoolean(KEY_NO_CAPTCHA, false)\n\n\t@Suppress(\"UNCHECKED_CAST\")\n\toverride fun <T> get(key: ConfigKey<T>): T {\n\t\treturn when (key) {\n\t\t\tis ConfigKey.UserAgent -> prefs.getString(key.key, key.defaultValue)\n\t\t\t\t.ifNullOrEmpty { key.defaultValue }\n\t\t\t\t.sanitizeHeaderValue()\n\n\t\t\tis ConfigKey.Domain -> prefs.getString(key.key, key.defaultValue)\n\t\t\t\t?.trim()\n\t\t\t\t?.takeIf { DomainValidator.isValidDomain(it) }\n\t\t\t\t?: key.defaultValue\n\n\t\t\tis ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue)\n\t\t\tis ConfigKey.SplitByTranslations -> prefs.getBoolean(key.key, key.defaultValue)\n\t\t\tis ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.nullIfEmpty()\n\t\t} as T\n\t}\n\n\toperator fun <T> set(key: ConfigKey<T>, value: T) = prefs.edit {\n\t\twhen (key) {\n\t\t\tis ConfigKey.Domain -> putString(key.key, value as String?)\n\t\t\tis ConfigKey.ShowSuspiciousContent -> putBoolean(key.key, value as Boolean)\n\t\t\tis ConfigKey.UserAgent -> putString(key.key, (value as String?)?.sanitizeHeaderValue())\n\t\t\tis ConfigKey.SplitByTranslations -> putBoolean(key.key, value as Boolean)\n\t\t\tis ConfigKey.PreferredImageServer -> putString(key.key, value as String? ?: \"\")\n\t\t}\n\t}\n\n\tfun subscribe(listener: OnSharedPreferenceChangeListener) {\n\t\tprefs.registerOnSharedPreferenceChangeListener(listener)\n\t}\n\n\tfun unsubscribe(listener: OnSharedPreferenceChangeListener) {\n\t\tprefs.unregisterOnSharedPreferenceChangeListener(listener)\n\t}\n\n\tcompanion object {\n\n\t\tconst val KEY_DOMAIN = \"domain\"\n\t\tconst val KEY_NO_CAPTCHA = \"no_captcha\"\n\t\tconst val KEY_SLOWDOWN = \"slowdown\"\n\t\tconst val KEY_SORT_ORDER = \"sort_order\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/TrackerDownloadStrategy.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class TrackerDownloadStrategy {\n\n\tDISABLED, DOWNLOADED;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/TriStateOption.kt",
    "content": "package org.koitharu.kotatsu.core.prefs\n\nimport androidx.annotation.Keep\n\n@Keep\nenum class TriStateOption {\n\n\tENABLED, ASK, DISABLED;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/AlertDialogFragment.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.app.Dialog\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.CallSuper\nimport androidx.appcompat.app.AlertDialog\nimport androidx.fragment.app.DialogFragment\nimport androidx.viewbinding.ViewBinding\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\n\nabstract class AlertDialogFragment<B : ViewBinding> : DialogFragment() {\n\n\tvar viewBinding: B? = null\n\t\tprivate set\n\n\tfinal override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n\t\tval binding = onCreateViewBinding(layoutInflater, null)\n\t\tviewBinding = binding\n\t\treturn MaterialAlertDialogBuilder(requireContext(), theme)\n\t\t\t.setView(binding.root)\n\t\t\t.run(::onBuildDialog)\n\t\t\t.create()\n\t\t\t.also(::onDialogCreated)\n\t}\n\n\tfinal override fun onCreateView(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t\tsavedInstanceState: Bundle?,\n\t) = viewBinding?.root\n\n\tfinal override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tonViewBindingCreated(requireViewBinding(), savedInstanceState)\n\t}\n\n\t@CallSuper\n\toverride fun onDestroyView() {\n\t\tviewBinding = null\n\t\tsuper.onDestroyView()\n\t}\n\n\topen fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder = builder\n\n\topen fun onDialogCreated(dialog: AlertDialog) = Unit\n\n\tfun requireViewBinding(): B = checkNotNull(viewBinding) {\n\t\t\"Fragment $this did not return a ViewBinding from onCreateView() or this was called before onCreateView().\"\n\t}\n\n\tprotected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B\n\n\tprotected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.res.Configuration\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.enableEdgeToEdge\nimport androidx.annotation.CallSuper\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.appcompat.view.ActionMode\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.app.ActivityCompat\nimport androidx.core.view.OnApplyWindowInsetsListener\nimport androidx.core.view.ViewCompat\nimport androidx.fragment.app.FragmentManager\nimport androidx.viewbinding.ViewBinding\nimport dagger.hilt.android.EntryPointAccessors\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flowOf\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.util.ActionModeDelegate\nimport org.koitharu.kotatsu.core.util.ext.isWebViewUnavailable\nimport org.koitharu.kotatsu.main.ui.protect.ScreenshotPolicyHelper\nimport androidx.appcompat.R as appcompatR\n\nabstract class BaseActivity<B : ViewBinding> :\n\tAppCompatActivity(),\n\tOnApplyWindowInsetsListener,\n\tScreenshotPolicyHelper.ContentContainer {\n\n\tprivate var isAmoledTheme = false\n\n\tlateinit var viewBinding: B\n\t\tprivate set\n\n\tprotected lateinit var exceptionResolver: ExceptionResolver\n\t\tprivate set\n\n\t@JvmField\n\tval actionModeDelegate = ActionModeDelegate()\n\n\tprivate lateinit var entryPoint: BaseActivityEntryPoint\n\n\toverride fun attachBaseContext(newBase: Context) {\n\t\tentryPoint = EntryPointAccessors.fromApplication<BaseActivityEntryPoint>(newBase.applicationContext)\n\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {\n\t\t\tAppCompatDelegate.setApplicationLocales(entryPoint.settings.appLocales)\n\t\t}\n\t\tsuper.attachBaseContext(newBase)\n\t}\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tval settings = entryPoint.settings\n\t\tisAmoledTheme = settings.isAmoledTheme\n\t\tsetTheme(settings.colorScheme.styleResId)\n\t\tif (isAmoledTheme) {\n\t\t\tsetTheme(R.style.ThemeOverlay_Kotatsu_Amoled)\n\t\t}\n\t\tputDataToExtras(intent)\n\t\texceptionResolver = entryPoint.exceptionResolverFactory.create(this)\n\t\tenableEdgeToEdge()\n\t\tsuper.onCreate(savedInstanceState)\n\t}\n\n\toverride fun onPostCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onPostCreate(savedInstanceState)\n\t\tonBackPressedDispatcher.addCallback(actionModeDelegate)\n\t}\n\n\toverride fun onNewIntent(intent: Intent) {\n\t\tputDataToExtras(intent)\n\t\tsuper.onNewIntent(intent)\n\t}\n\n\t@Deprecated(\"Use ViewBinding\", level = DeprecationLevel.ERROR)\n\toverride fun setContentView(layoutResID: Int) = throw UnsupportedOperationException()\n\n\t@Deprecated(\"Use ViewBinding\", level = DeprecationLevel.ERROR)\n\toverride fun setContentView(view: View?) = throw UnsupportedOperationException()\n\n\tprotected fun setContentView(binding: B) {\n\t\tthis.viewBinding = binding\n\t\tsuper.setContentView(binding.root)\n\t\tViewCompat.setOnApplyWindowInsetsListener(binding.root, this)\n\t\tval toolbar = (binding.root.findViewById<View>(R.id.toolbar) as? Toolbar)\n\t\ttoolbar?.let(this::setSupportActionBar)\n\t}\n\n\tprotected fun setDisplayHomeAsUp(isEnabled: Boolean, showUpAsClose: Boolean) {\n\t\tsupportActionBar?.run {\n\t\t\tsetDisplayHomeAsUpEnabled(isEnabled)\n\t\t\tif (showUpAsClose) {\n\t\t\t\tsetHomeAsUpIndicator(appcompatR.drawable.abc_ic_clear_material)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onSupportNavigateUp(): Boolean {\n\t\tval fm = supportFragmentManager\n\t\tif (fm.isStateSaved) {\n\t\t\treturn false\n\t\t}\n\t\tif (fm.backStackEntryCount > 0) {\n\t\t\tfm.popBackStack()\n\t\t} else {\n\t\t\tdispatchNavigateUp()\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n\t\tif (BuildConfig.DEBUG) {\n\t\t\tif (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {\n\t\t\t\tActivityCompat.recreate(this)\n\t\t\t\treturn true\n\t\t\t} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {\n\t\t\t\tthrow RuntimeException(\"Test crash\")\n\t\t\t}\n\t\t}\n\t\treturn super.onKeyDown(keyCode, event)\n\t}\n\n\tprotected fun isDarkAmoledTheme(): Boolean {\n\t\tval uiMode = resources.configuration.uiMode\n\t\tval isNight = uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES\n\t\treturn isNight && isAmoledTheme\n\t}\n\n\t@CallSuper\n\toverride fun onSupportActionModeStarted(mode: ActionMode) {\n\t\tsuper.onSupportActionModeStarted(mode)\n\t\tactionModeDelegate.onSupportActionModeStarted(mode, window)\n\t}\n\n\t@CallSuper\n\toverride fun onSupportActionModeFinished(mode: ActionMode) {\n\t\tsuper.onSupportActionModeFinished(mode)\n\t\tactionModeDelegate.onSupportActionModeFinished(mode, window)\n\t}\n\n\tprotected open fun dispatchNavigateUp() {\n\t\tval upIntent = parentActivityIntent\n\t\tif (upIntent != null) {\n\t\t\tif (!navigateUpTo(upIntent)) {\n\t\t\t\tstartActivity(upIntent)\n\t\t\t}\n\t\t} else {\n\t\t\tfinishAfterTransition()\n\t\t}\n\t}\n\n\toverride fun isNsfwContent(): Flow<Boolean> = flowOf(false)\n\n\tprivate fun putDataToExtras(intent: Intent?) {\n\t\tintent?.putExtra(AppRouter.KEY_DATA, intent.data)\n\t}\n\n\tprotected fun setContentViewWebViewSafe(viewBindingProducer: () -> B): Boolean {\n\t\treturn try {\n\t\t\tsetContentView(viewBindingProducer())\n\t\t\ttrue\n\t\t} catch (e: Exception) {\n\t\t\tif (e.isWebViewUnavailable()) {\n\t\t\t\tToast.makeText(this, R.string.web_view_unavailable, Toast.LENGTH_LONG).show()\n\t\t\t\tfinishAfterTransition()\n\t\t\t\tfalse\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected fun hasViewBinding() = ::viewBinding.isInitialized\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivityEntryPoint.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport dagger.hilt.EntryPoint\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.prefs.AppSettings\n\n@EntryPoint\n@InstallIn(SingletonComponent::class)\ninterface BaseActivityEntryPoint {\n\n\tval settings: AppSettings\n\n\tval exceptionResolverFactory: ExceptionResolver.Factory\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseAppWidgetProvider.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.appwidget.AppWidgetManager\nimport android.appwidget.AppWidgetProvider\nimport android.content.Context\nimport android.widget.RemoteViews\nimport androidx.annotation.CallSuper\nimport org.koitharu.kotatsu.core.prefs.AppWidgetConfig\n\nabstract class BaseAppWidgetProvider : AppWidgetProvider() {\n\n\t@CallSuper\n\toverride fun onUpdate(\n\t\tcontext: Context,\n\t\tappWidgetManager: AppWidgetManager,\n\t\tappWidgetIds: IntArray\n\t) {\n\t\tappWidgetIds.forEach { id ->\n\t\t\tval config = AppWidgetConfig(context, javaClass, id)\n\t\t\tval views = onUpdateWidget(context, config)\n\t\t\tappWidgetManager.updateAppWidget(id, views)\n\t\t}\n\t}\n\n\toverride fun onDeleted(context: Context, appWidgetIds: IntArray) {\n\t\tsuper.onDeleted(context, appWidgetIds)\n\t\tfor (id in appWidgetIds) {\n\t\t\tAppWidgetConfig(context, javaClass, id).clear()\n\t\t}\n\t}\n\n\toverride fun onRestored(context: Context, oldWidgetIds: IntArray, newWidgetIds: IntArray) {\n\t\tsuper.onRestored(context, oldWidgetIds, newWidgetIds)\n\t\tif (oldWidgetIds.size != newWidgetIds.size) {\n\t\t\treturn\n\t\t}\n\t\tfor (i in oldWidgetIds.indices) {\n\t\t\tval oldId = oldWidgetIds[i]\n\t\t\tval newId = newWidgetIds[i]\n\t\t\tval oldConfig = AppWidgetConfig(context, javaClass, oldId)\n\t\t\tval newConfig = AppWidgetConfig(context, javaClass, newId)\n\t\t\tnewConfig.copyFrom(oldConfig)\n\t\t\toldConfig.clear()\n\t\t}\n\t}\n\n\tprotected abstract fun onUpdateWidget(\n\t\tcontext: Context,\n\t\tconfig: AppWidgetConfig,\n\t): RemoteViews\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFragment.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.OnApplyWindowInsetsListener\nimport androidx.core.view.ViewCompat\nimport androidx.fragment.app.Fragment\nimport androidx.viewbinding.ViewBinding\nimport dagger.hilt.android.EntryPointAccessors\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.ui.util.ActionModeDelegate\n\nabstract class BaseFragment<B : ViewBinding> :\n\tOnApplyWindowInsetsListener,\n\tFragment() {\n\n\tvar viewBinding: B? = null\n\t\tprivate set\n\n\tprotected lateinit var exceptionResolver: ExceptionResolver\n\t\tprivate set\n\n\tprotected val actionModeDelegate: ActionModeDelegate\n\t\tget() = (requireActivity() as BaseActivity<*>).actionModeDelegate\n\n\toverride fun onAttach(context: Context) {\n\t\tsuper.onAttach(context)\n\t\tval entryPoint = EntryPointAccessors.fromApplication<BaseActivityEntryPoint>(context)\n\t\texceptionResolver = entryPoint.exceptionResolverFactory.create(this)\n\t}\n\n\tfinal override fun onCreateView(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t\tsavedInstanceState: Bundle?\n\t): View {\n\t\tval binding = onCreateViewBinding(inflater, container)\n\t\tviewBinding = binding\n\t\treturn binding.root\n\t}\n\n\tfinal override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tViewCompat.setOnApplyWindowInsetsListener(view, this)\n\t\tonViewBindingCreated(requireViewBinding(), savedInstanceState)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tviewBinding = null\n\t\tsuper.onDestroyView()\n\t}\n\n\tfun requireViewBinding(): B = checkNotNull(viewBinding) {\n\t\t\"Fragment $this did not return a ViewBinding from onCreateView() or this was called before onCreateView().\"\n\t}\n\n\tprotected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B\n\n\tprotected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseFullscreenActivity.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.graphics.Color\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.WindowManager\nimport androidx.core.content.ContextCompat\nimport androidx.viewbinding.ViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.util.SystemUiController\n\nabstract class BaseFullscreenActivity<B : ViewBinding> :\n\tBaseActivity<B>() {\n\n\tprotected lateinit var systemUiController: SystemUiController\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\twith(window) {\n\t\t\tsystemUiController = SystemUiController(this)\n\t\t\tstatusBarColor = Color.TRANSPARENT\n\t\t\tnavigationBarColor = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {\n\t\t\t\tContextCompat.getColor(this@BaseFullscreenActivity, R.color.dim)\n\t\t\t} else {\n\t\t\t\tColor.TRANSPARENT\n\t\t\t}\n\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n\t\t\t\tattributes.layoutInDisplayCutoutMode =\n\t\t\t\t\tWindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES\n\t\t\t}\n\t\t}\n\t\tsystemUiController.setSystemUiVisible(true)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseListAdapter.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport androidx.recyclerview.widget.AsyncDifferConfig\nimport androidx.recyclerview.widget.AsyncListDiffer.ListListener\nimport com.hannesdorfmann.adapterdelegates4.AdapterDelegate\nimport com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.asExecutor\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.channels.trySendBlocking\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.onStart\nimport org.koitharu.kotatsu.core.util.ContinuationResumeRunnable\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport kotlin.coroutines.suspendCoroutine\n\nopen class BaseListAdapter<T : ListModel> : AsyncListDifferDelegationAdapter<T>(\n\tAsyncDifferConfig.Builder(ListModelDiffCallback<T>())\n\t\t.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())\n\t\t.build(),\n), FlowCollector<List<T>?> {\n\n\toverride suspend fun emit(value: List<T>?) = suspendCoroutine { cont ->\n\t\tsetItems(value.orEmpty(), ContinuationResumeRunnable(cont))\n\t}\n\n\tfun addDelegate(type: ListItemType, delegate: AdapterDelegate<List<T>>): BaseListAdapter<T> {\n\t\tdelegatesManager.addDelegate(type.ordinal, delegate)\n\t\treturn this\n\t}\n\n\tfun addListListener(listListener: ListListener<T>): BaseListAdapter<T> {\n\t\tdiffer.addListListener(listListener)\n\t\treturn this\n\t}\n\n\tfun removeListListener(listListener: ListListener<T>) {\n\t\tdiffer.removeListListener(listListener)\n\t}\n\n\tfun findHeader(position: Int): ListHeader? {\n\t\tval snapshot = items\n\t\tfor (i in (0..position).reversed()) {\n\t\t\tval item = snapshot.getOrNull(i) ?: continue\n\t\t\tif (item is ListHeader) {\n\t\t\t\treturn item\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tfun observeItems(): Flow<List<T>> = callbackFlow {\n\t\tval listListener = ListListener<T> { _, list ->\n\t\t\ttrySendBlocking(list)\n\t\t}\n\t\taddListListener(listListener)\n\t\tawaitClose { removeListListener(listListener) }\n\t}.onStart {\n\t\temit(items)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BasePreferenceFragment.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport android.view.View\nimport androidx.annotation.StringRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.OnApplyWindowInsetsListener\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport androidx.preference.PreferenceScreen\nimport androidx.preference.get\nimport androidx.recyclerview.widget.RecyclerView\nimport dagger.hilt.android.AndroidEntryPoint\nimport dagger.hilt.android.EntryPointAccessors\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.container\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.getThemeDrawable\nimport org.koitharu.kotatsu.core.util.ext.parentView\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.settings.SettingsActivity\nimport javax.inject.Inject\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nabstract class BasePreferenceFragment(@StringRes private val titleId: Int) :\n\tPreferenceFragmentCompat(),\n\tOnApplyWindowInsetsListener,\n\tRecyclerViewOwner {\n\n\tprotected lateinit var exceptionResolver: ExceptionResolver\n\t\tprivate set\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = listView\n\n\toverride fun onAttach(context: Context) {\n\t\tsuper.onAttach(context)\n\t\tval entryPoint = EntryPointAccessors.fromApplication<BaseActivityEntryPoint>(context)\n\t\texceptionResolver = entryPoint.exceptionResolverFactory.create(this)\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tViewCompat.setOnApplyWindowInsetsListener(view, this)\n\t\tval themedContext = (view.parentView ?: view).context\n\t\tview.setBackgroundColor(themedContext.getThemeColor(android.R.attr.colorBackground))\n\t\tlistView.clipToPadding = false\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval isTablet = !resources.getBoolean(R.bool.is_tablet)\n\t\tval isMaster = container?.id == R.id.container_master\n\t\tlistView.setPaddingRelative(\n\t\t\tif (isTablet && !isMaster) 0 else barsInsets.start(v),\n\t\t\t0,\n\t\t\tif (isTablet && isMaster) 0 else barsInsets.end(v),\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tsetTitle(if (titleId != 0) getString(titleId) else null)\n\t\targuments?.getString(SettingsActivity.ARG_PREF_KEY)?.let {\n\t\t\tfocusPreference(it)\n\t\t\targuments?.remove(SettingsActivity.ARG_PREF_KEY)\n\t\t}\n\t}\n\n\tprotected open fun setTitle(title: CharSequence?) {\n\t\t(activity as? SettingsActivity)?.setSectionTitle(title)\n\t}\n\n\tprotected fun getWarningIcon(): Drawable? = context?.let { ctx ->\n\t\tContextCompat.getDrawable(ctx, R.drawable.ic_alert_outline)?.also {\n\t\t\tit.setTint(ContextCompat.getColor(ctx, R.color.warning))\n\t\t}\n\t}\n\n\tprivate fun focusPreference(key: String) {\n\t\tval pref = findPreference<Preference>(key)\n\t\tif (pref == null) {\n\t\t\tscrollToPreference(key)\n\t\t\treturn\n\t\t}\n\t\tscrollToPreference(pref)\n\t\tval prefIndex = preferenceScreen.indexOf(key)\n\t\tval view = if (prefIndex >= 0) {\n\t\t\tlistView.findViewHolderForAdapterPosition(prefIndex)?.itemView ?: return\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t\tview.context.getThemeDrawable(materialR.attr.colorTertiaryContainer)?.let {\n\t\t\tview.background = it\n\t\t}\n\t}\n\n\tprivate fun PreferenceScreen.indexOf(key: String): Int {\n\t\tfor (i in 0 until preferenceCount) {\n\t\t\tif (get(i).key == key) {\n\t\t\t\treturn i\n\t\t\t}\n\t\t}\n\t\treturn -1\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseViewModel.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.core.util.ext.EventFlow\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport kotlin.coroutines.AbstractCoroutineContextElement\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\n\nabstract class BaseViewModel : ViewModel() {\n\n\t@JvmField\n\tprotected val loadingCounter = MutableStateFlow(0)\n\n\t@JvmField\n\tprotected val errorEvent = MutableEventFlow<Throwable>()\n\n\tval onError: EventFlow<Throwable>\n\t\tget() = errorEvent\n\n\tval isLoading: StateFlow<Boolean> = loadingCounter.map { it > 0 }\n\t\t.stateIn(viewModelScope, SharingStarted.Lazily, loadingCounter.value > 0)\n\n\tprotected fun launchJob(\n\t\tcontext: CoroutineContext = EmptyCoroutineContext,\n\t\tstart: CoroutineStart = CoroutineStart.DEFAULT,\n\t\tblock: suspend CoroutineScope.() -> Unit\n\t): Job = viewModelScope.launch(context.withDefaultExceptionHandler(), start, block)\n\n\tprotected fun launchLoadingJob(\n\t\tcontext: CoroutineContext = EmptyCoroutineContext,\n\t\tstart: CoroutineStart = CoroutineStart.DEFAULT,\n\t\tblock: suspend CoroutineScope.() -> Unit\n\t): Job = viewModelScope.launch(context.withDefaultExceptionHandler(), start) {\n\t\tloadingCounter.increment()\n\t\ttry {\n\t\t\tblock()\n\t\t} finally {\n\t\t\tloadingCounter.decrement()\n\t\t}\n\t}\n\n\tprotected fun <T> Flow<T>.withLoading() = onStart {\n\t\tloadingCounter.increment()\n\t}.onCompletion {\n\t\tloadingCounter.decrement()\n\t}\n\n\tprotected fun <T> Flow<T>.withErrorHandling() = catch { error ->\n\t\terror.printStackTraceDebug()\n\t\terrorEvent.call(error)\n\t}\n\n\tprotected inline fun <T> withLoading(block: () -> T): T = try {\n\t\tloadingCounter.increment()\n\t\tblock()\n\t} finally {\n\t\tloadingCounter.decrement()\n\t}\n\n\tprotected fun MutableStateFlow<Int>.increment() = update { it + 1 }\n\n\tprotected fun MutableStateFlow<Int>.decrement() = update { it - 1 }\n\n\tprivate fun CoroutineContext.withDefaultExceptionHandler() =\n\t\tif (this[CoroutineExceptionHandler.Key] is EventExceptionHandler) {\n\t\t\tthis\n\t\t} else {\n\t\t\tthis + EventExceptionHandler(errorEvent)\n\t\t}\n\n\tprotected object SkipErrors : AbstractCoroutineContextElement(Key) {\n\n\t\tprivate object Key : CoroutineContext.Key<SkipErrors>\n\t}\n\n\tprotected class EventExceptionHandler(\n\t\tprivate val event: MutableEventFlow<Throwable>,\n\t) : AbstractCoroutineContextElement(CoroutineExceptionHandler),\n\t\tCoroutineExceptionHandler {\n\n\t\toverride fun handleException(context: CoroutineContext, exception: Throwable) {\n\t\t\texception.printStackTraceDebug()\n\t\t\tif (context[SkipErrors.key] == null && exception !is CancellationException) {\n\t\t\t\tevent.call(exception)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/CoroutineIntentService.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.app.Notification\nimport android.app.PendingIntent\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.PatternMatcher\nimport androidx.annotation.AnyThread\nimport androidx.annotation.WorkerThread\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.app.ServiceCompat\nimport androidx.core.content.ContextCompat\nimport androidx.core.net.toUri\nimport androidx.lifecycle.lifecycleScope\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\n\nabstract class CoroutineIntentService : BaseService() {\n\n\tprivate val mutex = Mutex()\n\n\tfinal override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n\t\tsuper.onStartCommand(intent, flags, startId)\n\t\tlaunchCoroutine(intent, startId)\n\t\treturn START_REDELIVER_INTENT\n\t}\n\n\tprivate fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch {\n\t\tval intentJobContext = IntentJobContextImpl(startId, this)\n\t\tmutex.withLock {\n\t\t\ttry {\n\t\t\t\tif (intent != null) {\n\t\t\t\t\twithContext(Dispatchers.Default) {\n\t\t\t\t\t\tintentJobContext.processIntent(intent)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e: CancellationException) {\n\t\t\t\tthrow e\n\t\t\t} catch (e: Throwable) {\n\t\t\t\te.printStackTraceDebug()\n\t\t\t\tintentJobContext.onError(e)\n\t\t\t} finally {\n\t\t\t\tintentJobContext.stop()\n\t\t\t}\n\t\t}\n\t}\n\n\t@WorkerThread\n\tprotected abstract suspend fun IntentJobContext.processIntent(intent: Intent)\n\n\t@AnyThread\n\tprotected abstract fun IntentJobContext.onError(error: Throwable)\n\n\tinterface IntentJobContext : CoroutineScope {\n\n\t\tval startId: Int\n\n\t\tfun getCancelIntent(): PendingIntent?\n\n\t\tfun setForeground(id: Int, notification: Notification, serviceType: Int)\n\t}\n\n\tprotected inner class IntentJobContextImpl(\n\t\toverride val startId: Int,\n\t\tprivate val scope: CoroutineScope,\n\t) : IntentJobContext, CoroutineScope by scope {\n\n\t\tprivate var cancelReceiver: CancelReceiver? = null\n\t\tprivate var isStopped = false\n\t\tprivate var isForeground = false\n\n\t\toverride fun getCancelIntent(): PendingIntent? {\n\t\t\tensureHasCancelReceiver()\n\t\t\treturn PendingIntentCompat.getBroadcast(\n\t\t\t\tapplicationContext,\n\t\t\t\t0,\n\t\t\t\tcreateCancelIntent(this@CoroutineIntentService, startId),\n\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\tfalse,\n\t\t\t)\n\t\t}\n\n\t\toverride fun setForeground(id: Int, notification: Notification, serviceType: Int) {\n\t\t\tServiceCompat.startForeground(this@CoroutineIntentService, id, notification, serviceType)\n\t\t\tisForeground = true\n\t\t}\n\n\t\tfun stop() {\n\t\t\tsynchronized(this) {\n\t\t\t\tcancelReceiver?.let {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tunregisterReceiver(it)\n\t\t\t\t\t} catch (e: IllegalArgumentException) {\n\t\t\t\t\t\te.printStackTraceDebug()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tisStopped = true\n\t\t\t}\n\t\t\tif (isForeground) {\n\t\t\t\tServiceCompat.stopForeground(this@CoroutineIntentService, ServiceCompat.STOP_FOREGROUND_REMOVE)\n\t\t\t}\n\t\t\tstopSelf(startId)\n\t\t}\n\n\t\tprivate fun ensureHasCancelReceiver() {\n\t\t\tif (cancelReceiver == null && !isStopped) {\n\t\t\t\tsynchronized(this) {\n\t\t\t\t\tif (cancelReceiver == null && !isStopped) {\n\t\t\t\t\t\tval job = coroutineContext[Job] ?: return\n\t\t\t\t\t\tCancelReceiver(job).let { receiver ->\n\t\t\t\t\t\t\tContextCompat.registerReceiver(\n\t\t\t\t\t\t\t\tapplicationContext,\n\t\t\t\t\t\t\t\treceiver,\n\t\t\t\t\t\t\t\tcreateIntentFilter(this@CoroutineIntentService, startId),\n\t\t\t\t\t\t\t\tContextCompat.RECEIVER_NOT_EXPORTED,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tcancelReceiver = receiver\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class CancelReceiver(\n\t\tprivate val job: Job\n\t) : BroadcastReceiver() {\n\n\t\toverride fun onReceive(context: Context?, intent: Intent?) {\n\t\t\tjob.cancel()\n\t\t}\n\t}\n\n\tprivate companion object {\n\n\t\tprivate const val SCHEME = \"startid\"\n\t\tprivate const val ACTION_SUFFIX_CANCEL = \".ACTION_CANCEL\"\n\n\t\tfun createIntentFilter(service: CoroutineIntentService, startId: Int): IntentFilter {\n\t\t\tval intentFilter = IntentFilter(cancelAction(service))\n\t\t\tintentFilter.addDataScheme(SCHEME)\n\t\t\tintentFilter.addDataPath(startId.toString(), PatternMatcher.PATTERN_LITERAL)\n\t\t\treturn intentFilter\n\t\t}\n\n\t\tfun createCancelIntent(service: CoroutineIntentService, startId: Int): Intent {\n\t\t\treturn Intent(cancelAction(service))\n\t\t\t\t.setData(\"$SCHEME://$startId\".toUri())\n\t\t}\n\n\t\tprivate fun cancelAction(service: CoroutineIntentService) = service.javaClass.name + ACTION_SUFFIX_CANCEL\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/DefaultActivityLifecycleCallbacks.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.app.Activity\nimport android.app.Application.ActivityLifecycleCallbacks\nimport android.os.Bundle\n\ninterface DefaultActivityLifecycleCallbacks : ActivityLifecycleCallbacks {\n\n\toverride fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit\n\n\toverride fun onActivityStarted(activity: Activity) = Unit\n\n\toverride fun onActivityResumed(activity: Activity) = Unit\n\n\toverride fun onActivityPaused(activity: Activity) = Unit\n\n\toverride fun onActivityStopped(activity: Activity) = Unit\n\n\toverride fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit\n\n\toverride fun onActivityDestroyed(activity: Activity) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/FragmentContainerActivity.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.commit\nimport com.google.android.material.appbar.AppBarLayout\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.consumeSystemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityContainerBinding\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.main.ui.owners.SnackbarOwner\n\n@AndroidEntryPoint\nabstract class FragmentContainerActivity(private val fragmentClass: Class<out Fragment>) :\n\tBaseActivity<ActivityContainerBinding>(),\n\tAppBarOwner,\n\tSnackbarOwner {\n\n\toverride val appBar: AppBarLayout\n\t\tget() = viewBinding.appbar\n\n\toverride val snackbarHost: CoordinatorLayout\n\t\tget() = viewBinding.root\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityContainerBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tval fm = supportFragmentManager\n\t\tif (fm.findFragmentById(R.id.container) == null) {\n\t\t\tfm.commit {\n\t\t\t\tsetReorderingAllowed(true)\n\t\t\t\treplace(R.id.container, fragmentClass, getFragmentExtras())\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = bars.left,\n\t\t\tright = bars.right,\n\t\t\ttop = bars.top,\n\t\t)\n\t\treturn insets.consumeSystemBarsInsets(top = true)\n\t}\n\n\tprotected open fun getFragmentExtras(): Bundle? = intent.extras\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/ReorderableListAdapter.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport androidx.recyclerview.widget.AsyncListDiffer.ListListener\nimport androidx.recyclerview.widget.DiffUtil\nimport com.hannesdorfmann.adapterdelegates4.AdapterDelegate\nimport com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.util.move\nimport java.util.LinkedList\n\nopen class ReorderableListAdapter<T : ListModel> : ListDelegationAdapter<List<T>>(), FlowCollector<List<T>?> {\n\n\tprivate val listListeners = LinkedList<ListListener<T>>()\n\n\toverride suspend fun emit(value: List<T>?) {\n\t\tval oldList = items.orEmpty()\n\t\tval newList = value.orEmpty()\n\t\tval diffResult = withContext(Dispatchers.Default) {\n\t\t\tval diffCallback = DiffCallback(oldList, newList)\n\t\t\tDiffUtil.calculateDiff(diffCallback)\n\t\t}\n\t\tsuper.setItems(newList)\n\t\tdiffResult.dispatchUpdatesTo(this)\n\t\tlistListeners.forEach { it.onCurrentListChanged(oldList, newList) }\n\t}\n\n\t@Deprecated(\n\t\tmessage = \"Use emit() to dispatch list updates\",\n\t\tlevel = DeprecationLevel.ERROR,\n\t\treplaceWith = ReplaceWith(\"emit(items)\"),\n\t)\n\toverride fun setItems(items: List<T>?) = super.setItems(items)\n\n\tfun reorderItems(oldPos: Int, newPos: Int) {\n\t\tval reordered = items?.toMutableList() ?: return\n\t\treordered.move(oldPos, newPos)\n\t\tsuper.setItems(reordered)\n\t\tnotifyItemMoved(oldPos, newPos)\n\t}\n\n\tfun addDelegate(type: ListItemType, delegate: AdapterDelegate<List<T>>): ReorderableListAdapter<T> {\n\t\tdelegatesManager.addDelegate(type.ordinal, delegate)\n\t\treturn this\n\t}\n\n\tfun addListListener(listListener: ListListener<T>) {\n\t\tlistListeners.add(listListener)\n\t}\n\n\tfun removeListListener(listListener: ListListener<T>) {\n\t\tlistListeners.remove(listListener)\n\t}\n\n\tprotected class DiffCallback<T : ListModel>(\n\t\tprivate val oldList: List<T>,\n\t\tprivate val newList: List<T>,\n\t) : DiffUtil.Callback() {\n\n\t\toverride fun getOldListSize(): Int = oldList.size\n\n\t\toverride fun getNewListSize(): Int = newList.size\n\n\t\toverride fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n\t\t\tval oldItem = oldList[oldItemPosition]\n\t\t\tval newItem = newList[newItemPosition]\n\t\t\treturn newItem.areItemsTheSame(oldItem)\n\t\t}\n\n\t\toverride fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n\t\t\tval oldItem = oldList[oldItemPosition]\n\t\t\tval newItem = newList[newItemPosition]\n\t\t\treturn newItem == oldItem\n\t\t}\n\n\t\toverride fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {\n\t\t\tval oldItem = oldList[oldItemPosition]\n\t\t\tval newItem = newList[newItemPosition]\n\t\t\treturn newItem.getChangePayload(oldItem)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/AlertDialogs.kt",
    "content": "package org.koitharu.kotatsu.core.ui.dialog\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.LayoutParams.WRAP_CONTENT\nimport android.view.inputmethod.EditorInfo\nimport android.widget.ArrayAdapter\nimport android.widget.CompoundButton.OnCheckedChangeListener\nimport android.widget.EditText\nimport android.widget.FrameLayout\nimport androidx.annotation.StringRes\nimport androidx.annotation.UiContext\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.widget.AppCompatEditText\nimport androidx.core.view.updatePadding\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.hannesdorfmann.adapterdelegates4.AdapterDelegate\nimport com.hannesdorfmann.adapterdelegates4.AdapterDelegatesManager\nimport com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.databinding.DialogCheckboxBinding\nimport org.koitharu.kotatsu.databinding.ViewDialogAutocompleteBinding\nimport com.google.android.material.R as materialR\n\ninline fun buildAlertDialog(\n    @UiContext context: Context,\n    isCentered: Boolean = false,\n    block: MaterialAlertDialogBuilder.() -> Unit,\n): AlertDialog = MaterialAlertDialogBuilder(\n    context,\n    if (isCentered) materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered else 0,\n).apply(block).create()\n\nfun <B : AlertDialog.Builder> B.setCheckbox(\n    @StringRes textResId: Int,\n    isChecked: Boolean,\n    onCheckedChangeListener: OnCheckedChangeListener\n) = apply {\n    val binding = DialogCheckboxBinding.inflate(LayoutInflater.from(context))\n    binding.checkbox.setText(textResId)\n    binding.checkbox.isChecked = isChecked\n    binding.checkbox.setOnCheckedChangeListener(onCheckedChangeListener)\n    setView(binding.root)\n}\n\nfun <B : AlertDialog.Builder, T> B.setRecyclerViewList(\n    list: List<T>,\n    delegate: AdapterDelegate<List<T>>,\n) = apply {\n    val delegatesManager = AdapterDelegatesManager<List<T>>()\n    delegatesManager.addDelegate(delegate)\n    setRecyclerViewList(ListDelegationAdapter(delegatesManager).also { it.items = list })\n}\n\nfun <B : AlertDialog.Builder, T> B.setRecyclerViewList(\n    list: List<T>,\n    vararg delegates: AdapterDelegate<List<T>>,\n) = apply {\n    val delegatesManager = AdapterDelegatesManager<List<T>>()\n    delegates.forEach { delegatesManager.addDelegate(it) }\n    setRecyclerViewList(ListDelegationAdapter(delegatesManager).also { it.items = list })\n}\n\nfun <B : AlertDialog.Builder> B.setRecyclerViewList(adapter: RecyclerView.Adapter<*>) = apply {\n    val recyclerView = RecyclerView(context)\n    recyclerView.layoutManager = LinearLayoutManager(context)\n    recyclerView.updatePadding(\n        top = context.resources.getDimensionPixelOffset(R.dimen.list_spacing),\n    )\n    recyclerView.clipToPadding = false\n    recyclerView.adapter = adapter\n    setView(recyclerView)\n}\n\nfun <B : AlertDialog.Builder> B.setEditText(\n    inputType: Int,\n    singleLine: Boolean,\n): EditText {\n    val editText = AppCompatEditText(context)\n    editText.inputType = inputType\n    if (singleLine) {\n        editText.setSingleLine()\n        editText.imeOptions = EditorInfo.IME_ACTION_DONE\n    }\n    val layout = FrameLayout(context)\n    val lp = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)\n    val horizontalMargin = context.resources.getDimensionPixelOffset(R.dimen.screen_padding)\n    lp.setMargins(\n        horizontalMargin,\n        context.resources.getDimensionPixelOffset(R.dimen.margin_small),\n        horizontalMargin,\n        0,\n    )\n    layout.addView(editText, lp)\n    setView(layout)\n    return editText\n}\n\nfun <B : AlertDialog.Builder> B.setEditText(\n    entries: List<CharSequence>,\n    inputType: Int,\n    singleLine: Boolean,\n): EditText {\n    if (entries.isEmpty()) {\n        return setEditText(inputType, singleLine)\n    }\n    val binding = ViewDialogAutocompleteBinding.inflate(LayoutInflater.from(context))\n    binding.autoCompleteTextView.setAdapter(\n        ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, entries),\n    )\n    binding.dropdown.setOnClickListener {\n        binding.autoCompleteTextView.showDropDown()\n    }\n    binding.autoCompleteTextView.inputType = inputType\n    if (singleLine) {\n        binding.autoCompleteTextView.setSingleLine()\n        binding.autoCompleteTextView.imeOptions = EditorInfo.IME_ACTION_DONE\n    }\n    setView(binding.root)\n    return binding.autoCompleteTextView\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/BigButtonsAlertDialog.kt",
    "content": "package org.koitharu.kotatsu.core.ui.dialog\n\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.view.LayoutInflater\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport com.google.android.material.button.MaterialButton\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport org.koitharu.kotatsu.databinding.DialogTwoButtonsBinding\n\nclass BigButtonsAlertDialog private constructor(\n\tprivate val delegate: AlertDialog\n) : DialogInterface by delegate {\n\n\tfun show() = delegate.show()\n\n\tclass Builder(context: Context) {\n\n\t\tprivate val binding = DialogTwoButtonsBinding.inflate(LayoutInflater.from(context))\n\n\t\tprivate val delegate = MaterialAlertDialogBuilder(context)\n\t\t\t.setView(binding.root)\n\n\t\tfun setTitle(@StringRes titleResId: Int): Builder {\n\t\t\tbinding.title.setText(titleResId)\n\t\t\treturn this\n\t\t}\n\n\t\tfun setTitle(title: CharSequence): Builder {\n\t\t\tbinding.title.text = title\n\t\t\treturn this\n\t\t}\n\n\t\tfun setIcon(@DrawableRes iconId: Int): Builder {\n\t\t\tbinding.icon.setImageResource(iconId)\n\t\t\treturn this\n\t\t}\n\n\t\tfun setPositiveButton(\n\t\t\t@StringRes textId: Int,\n\t\t\tlistener: DialogInterface.OnClickListener,\n\t\t): Builder {\n\t\t\tinitButton(binding.button1, DialogInterface.BUTTON_POSITIVE, textId, listener)\n\t\t\treturn this\n\t\t}\n\n\t\tfun setNegativeButton(\n\t\t\t@StringRes textId: Int,\n\t\t\tlistener: DialogInterface.OnClickListener? = null\n\t\t): Builder {\n\t\t\tinitButton(binding.button3, DialogInterface.BUTTON_NEGATIVE, textId, listener)\n\t\t\treturn this\n\t\t}\n\n\t\tfun setNeutralButton(\n\t\t\t@StringRes textId: Int,\n\t\t\tlistener: DialogInterface.OnClickListener? = null\n\t\t): Builder {\n\t\t\tinitButton(binding.button2, DialogInterface.BUTTON_NEUTRAL, textId, listener)\n\t\t\treturn this\n\t\t}\n\n\t\tfun create(): BigButtonsAlertDialog {\n\t\t\twith(binding) {\n\t\t\t\tbutton1.adjustCorners(isFirst = true, isLast = button2.isGone && button3.isGone)\n\t\t\t\tbutton2.adjustCorners(isFirst = button1.isGone, isLast = button3.isGone)\n\t\t\t\tbutton3.adjustCorners(isFirst = button1.isGone && button2.isGone, isLast = true)\n\t\t\t}\n\n\t\t\tval dialog = delegate.create()\n\t\t\tbinding.root.tag = dialog\n\t\t\treturn BigButtonsAlertDialog(dialog)\n\t\t}\n\n\t\tprivate fun MaterialButton.adjustCorners(isFirst: Boolean, isLast: Boolean) {\n\t\t\tif (!isVisible) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tshapeAppearanceModel = shapeAppearanceModel.toBuilder().apply {\n\t\t\t\tif (!isFirst) {\n\t\t\t\t\tsetTopLeftCornerSize(0f)\n\t\t\t\t\tsetTopRightCornerSize(0f)\n\t\t\t\t}\n\t\t\t\tif (!isLast) {\n\t\t\t\t\tsetBottomLeftCornerSize(0f)\n\t\t\t\t\tsetBottomRightCornerSize(0f)\n\t\t\t\t}\n\t\t\t}.build()\n\t\t}\n\n\t\tprivate fun initButton(\n\t\t\tbutton: MaterialButton,\n\t\t\twhich: Int,\n\t\t\t@StringRes textId: Int,\n\t\t\tlistener: DialogInterface.OnClickListener?,\n\t\t) {\n\t\t\tbutton.setText(textId)\n\t\t\tbutton.isVisible = true\n\t\t\tbutton.setOnClickListener {\n\t\t\t\tval dialog = binding.root.tag as DialogInterface\n\t\t\t\tlistener?.onClick(dialog, which)\n\t\t\t\tdialog.dismiss()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/ErrorDetailsDialog.kt",
    "content": "package org.koitharu.kotatsu.core.ui.dialog\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.isVisible\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.github.AppUpdateRepository\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.util.ext.copyToClipboard\nimport org.koitharu.kotatsu.core.util.ext.getCauseUrl\nimport org.koitharu.kotatsu.core.util.ext.isHttpUrl\nimport org.koitharu.kotatsu.core.util.ext.isReportable\nimport org.koitharu.kotatsu.core.util.ext.report\nimport org.koitharu.kotatsu.core.util.ext.requireSerializable\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.DialogErrorDetailsBinding\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>(), View.OnClickListener {\n\n\tprivate lateinit var exception: Throwable\n\n\t@Inject\n\tlateinit var appUpdateRepository: AppUpdateRepository\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tval args = requireArguments()\n\t\texception = args.requireSerializable(AppRouter.KEY_ERROR)\n\t}\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogErrorDetailsBinding {\n\t\treturn DialogErrorDetailsBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: DialogErrorDetailsBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.buttonBrowser.setOnClickListener(this)\n\t\tbinding.textViewSummary.text = exception.message\n\t\tval isUrlAvailable = exception.getCauseUrl()?.isHttpUrl() == true\n\t\tbinding.buttonBrowser.isVisible = isUrlAvailable\n\t\tbinding.textViewBrowser.isVisible = isUrlAvailable\n\t\tbinding.textViewDescription.setTextAndVisible(\n\t\t\tif (appUpdateRepository.isUpdateAvailable) {\n\t\t\t\tR.string.error_disclaimer_app_outdated\n\t\t\t} else if (exception.isReportable()) {\n\t\t\t\tR.string.error_disclaimer_report\n\t\t\t} else {\n\t\t\t\t0\n\t\t\t},\n\t\t)\n\t}\n\n\t@Suppress(\"NAME_SHADOWING\")\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\tval builder = super.onBuildDialog(builder)\n\t\t\t.setCancelable(true)\n\t\t\t.setNegativeButton(R.string.close, null)\n\t\t\t.setTitle(R.string.error_details)\n\t\t\t.setNeutralButton(androidx.preference.R.string.copy) { _, _ ->\n\t\t\t\tcontext?.copyToClipboard(getString(R.string.error), exception.stackTraceToString())\n\t\t\t}\n\t\tif (appUpdateRepository.isUpdateAvailable) {\n\t\t\tbuilder.setPositiveButton(R.string.update) { _, _ ->\n\t\t\t\trouter.openAppUpdate()\n\t\t\t\tdismiss()\n\t\t\t}\n\t\t} else if (exception.isReportable()) {\n\t\t\tbuilder.setPositiveButton(R.string.report) { _, _ ->\n\t\t\t\texception.report(silent = true)\n\t\t\t\tdismiss()\n\t\t\t}\n\t\t}\n\t\treturn builder\n\t}\n\n\toverride fun onClick(v: View) {\n\t\trouter.openBrowser(\n\t\t\turl = exception.getCauseUrl() ?: return,\n\t\t\tsource = null,\n\t\t\ttitle = null,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RememberCheckListener.kt",
    "content": "package org.koitharu.kotatsu.core.ui.dialog\n\nimport android.widget.CompoundButton\nimport android.widget.CompoundButton.OnCheckedChangeListener\n\nclass RememberCheckListener(\n\tinitialValue: Boolean,\n) : OnCheckedChangeListener {\n\n\tvar isChecked: Boolean = initialValue\n\t\tprivate set\n\n\toverride fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {\n\t\tthis.isChecked = isChecked\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/dialog/RememberSelectionDialogListener.kt",
    "content": "package org.koitharu.kotatsu.core.ui.dialog\n\nimport android.content.DialogInterface\n\nclass RememberSelectionDialogListener(initialValue: Int) : DialogInterface.OnClickListener {\n\n\tvar selection: Int = initialValue\n\t\tprivate set\n\n\toverride fun onClick(dialog: DialogInterface?, which: Int) {\n\t\tselection = which\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.animation.TimeAnimator\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.drawable.Animatable\nimport androidx.annotation.StyleRes\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport coil3.Image\nimport coil3.asImage\nimport coil3.getExtra\nimport coil3.request.ImageRequest\nimport com.google.android.material.animation.ArgbEvaluatorCompat\nimport com.google.android.material.color.MaterialColors\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceKey\nimport kotlin.math.abs\n\nclass AnimatedFaviconDrawable(\n\tcontext: Context,\n\t@StyleRes styleResId: Int,\n\tname: String,\n) : FaviconDrawable(context, styleResId, name), Animatable, TimeAnimator.TimeListener {\n\n\tprivate val interpolator = FastOutSlowInInterpolator()\n\tprivate val period = context.getAnimationDuration(R.integer.config_longAnimTime) * 2\n\tprivate val timeAnimator = TimeAnimator()\n\n\tprivate var colorHigh = MaterialColors.harmonize(colorForeground, currentBackgroundColor)\n\tprivate var colorLow = ArgbEvaluatorCompat.getInstance().evaluate(0.3f, colorHigh, currentBackgroundColor)\n\n\tinit {\n\t\ttimeAnimator.setTimeListener(this)\n\t\tonStateChange(state)\n\t}\n\n\toverride fun draw(canvas: Canvas) {\n\t\tif (!isRunning && period > 0) {\n\t\t\tupdateColor()\n\t\t\tstart()\n\t\t}\n\t\tsuper.draw(canvas)\n\t}\n\n\toverride fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) {\n\t\tcallback?.also {\n\t\t\tupdateColor()\n\t\t\tit.invalidateDrawable(this)\n\t\t} ?: stop()\n\t}\n\n\toverride fun start() {\n\t\ttimeAnimator.start()\n\t}\n\n\toverride fun stop() {\n\t\ttimeAnimator.end()\n\t}\n\n\toverride fun isRunning(): Boolean = timeAnimator.isStarted\n\n\toverride fun onStateChange(state: IntArray): Boolean {\n\t\tval res = super.onStateChange(state)\n\t\tcolorHigh = MaterialColors.harmonize(currentForegroundColor, currentBackgroundColor)\n\t\tcolorLow = ArgbEvaluatorCompat.getInstance().evaluate(0.3f, colorHigh, currentBackgroundColor)\n\t\tupdateColor()\n\t\treturn res\n\t}\n\n\tprivate fun updateColor() {\n\t\tif (period <= 0f) {\n\t\t\treturn\n\t\t}\n\t\tval ph = period / 2\n\t\tval fraction = abs((System.currentTimeMillis() % period) - ph) / ph.toFloat()\n\t\tcurrentForegroundColor = ArgbEvaluatorCompat.getInstance()\n\t\t\t.evaluate(interpolator.getInterpolation(fraction), colorLow, colorHigh)\n\t}\n\n\tclass Factory(\n\t\t@StyleRes private val styleResId: Int,\n\t) : ((ImageRequest) -> Image?) {\n\n\t\toverride fun invoke(request: ImageRequest): Image? {\n\t\t\tval source = request.getExtra(mangaSourceKey) ?: return null\n\t\t\tval context = request.context\n\t\t\tval title = source.getTitle(context)\n\t\t\treturn AnimatedFaviconDrawable(context, styleResId, title).asImage()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.animation.TimeAnimator\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.ColorFilter\nimport android.graphics.PixelFormat\nimport android.graphics.drawable.Animatable\nimport android.graphics.drawable.Drawable\nimport androidx.core.graphics.ColorUtils\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport com.google.android.material.animation.ArgbEvaluatorCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport kotlin.math.abs\nimport com.google.android.material.R as materialR\n\nclass AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, TimeAnimator.TimeListener {\n\n\tprivate val colorLow = context.getThemeColor(materialR.attr.colorSurfaceContainerLowest)\n\tprivate val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainerHighest)\n\tprivate var currentColor: Int = colorLow\n\tprivate val interpolator = FastOutSlowInInterpolator()\n\tprivate val period = context.getAnimationDuration(R.integer.config_longAnimTime) * 2\n\tprivate val timeAnimator = TimeAnimator()\n\tprivate var currentAlpha: Int = 255\n\n\tinit {\n\t\ttimeAnimator.setTimeListener(this)\n\t\tupdateColor()\n\t}\n\n\toverride fun draw(canvas: Canvas) {\n\t\tif (!isRunning && period > 0) {\n\t\t\tupdateColor()\n\t\t\tstart()\n\t\t}\n\t\tcanvas.drawColor(currentColor)\n\t}\n\n\toverride fun setAlpha(alpha: Int) {\n\t\tcurrentAlpha = alpha\n\t\tupdateColor()\n\t}\n\n\t@Suppress(\"DeprecatedCallableAddReplaceWith\")\n\t@Deprecated(\"Deprecated in Java\")\n\toverride fun getOpacity(): Int = PixelFormat.TRANSLUCENT\n\n\toverride fun getAlpha(): Int = currentAlpha\n\n\toverride fun setColorFilter(colorFilter: ColorFilter?) = Unit\n\n\toverride fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) {\n\t\tcallback?.also {\n\t\t\tupdateColor()\n\t\t\tit.invalidateDrawable(this)\n\t\t} ?: stop()\n\t}\n\n\toverride fun start() {\n\t\ttimeAnimator.start()\n\t}\n\n\toverride fun stop() {\n\t\ttimeAnimator.end()\n\t}\n\n\toverride fun isRunning(): Boolean = timeAnimator.isStarted\n\n\tprivate fun updateColor() {\n\t\tif (period <= 0f) {\n\t\t\treturn\n\t\t}\n\t\tval ph = period / 2\n\t\tval fraction = abs((System.currentTimeMillis() % period) - ph) / ph.toFloat()\n\t\tcurrentColor = ColorUtils.setAlphaComponent(\n\t\t\tArgbEvaluatorCompat.getInstance()\n\t\t\t\t.evaluate(interpolator.getInterpolation(fraction), colorLow, colorHigh),\n\t\t\tcurrentAlpha\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/ChipIconTarget.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.graphics.drawable.Drawable\nimport coil3.target.GenericViewTarget\nimport com.google.android.material.chip.Chip\n\nclass ChipIconTarget(override val view: Chip) : GenericViewTarget<Chip>() {\n\n\toverride var drawable: Drawable?\n\t\tget() = view.chipIcon\n\t\tset(value) {\n\t\t\tview.chipIcon = value\n\t\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoilImageGetter.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.text.Html\nimport androidx.annotation.WorkerThread\nimport coil3.ImageLoader\nimport coil3.executeBlocking\nimport coil3.request.ImageRequest\nimport coil3.request.allowHardware\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport org.koitharu.kotatsu.core.util.ext.drawable\nimport javax.inject.Inject\n\nclass CoilImageGetter @Inject constructor(\n\t@ApplicationContext private val context: Context,\n\tprivate val coil: ImageLoader,\n) : Html.ImageGetter {\n\n\t@WorkerThread\n\toverride fun getDrawable(source: String?): Drawable? {\n\t\treturn coil.executeBlocking(\n\t\t\tImageRequest.Builder(context)\n\t\t\t\t.data(source)\n\t\t\t\t.allowHardware(false)\n\t\t\t\t.build(),\n\t\t).drawable?.apply {\n\t\t\tsetBounds(0, 0, intrinsicHeight, intrinsicHeight)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconDrawable.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Path\nimport android.graphics.Rect\nimport android.graphics.RectF\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport androidx.annotation.StyleRes\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.graphics.withClip\nimport coil3.Image\nimport coil3.asImage\nimport coil3.getExtra\nimport coil3.request.ImageRequest\nimport com.google.android.material.color.MaterialColors\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.util.KotatsuColors\nimport org.koitharu.kotatsu.core.util.ext.hasFocusStateSpecified\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceKey\n\nopen class FaviconDrawable(\n\tcontext: Context,\n\t@StyleRes styleResId: Int,\n\tname: String,\n) : PaintDrawable() {\n\n\toverride val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG)\n\tprotected var currentBackgroundColor = Color.WHITE\n\t\tprivate set\n\tprivate var colorBackground: ColorStateList = ColorStateList.valueOf(currentBackgroundColor)\n\tprotected var colorForeground = Color.DKGRAY\n\tprotected var currentForegroundColor = Color.DKGRAY\n\tprotected var currentStrokeColor = Color.LTGRAY\n\t\tprivate set\n\tprivate var colorStroke: ColorStateList = ColorStateList.valueOf(currentStrokeColor)\n\tprivate val letter = name.take(1).uppercase()\n\tprivate var cornerSize = 0f\n\tprivate var intrinsicSize = -1\n\tprivate val textBounds = Rect()\n\tprivate val tempRect = Rect()\n\tprivate val boundsF = RectF()\n\tprivate val clipPath = Path()\n\n\tinit {\n\t\tcontext.withStyledAttributes(styleResId, R.styleable.FaviconFallbackDrawable) {\n\t\t\tcolorBackground = getColorStateList(R.styleable.FaviconFallbackDrawable_backgroundColor) ?: colorBackground\n\t\t\tcolorStroke = getColorStateList(R.styleable.FaviconFallbackDrawable_strokeColor) ?: colorStroke\n\t\t\tcornerSize = getDimension(R.styleable.FaviconFallbackDrawable_cornerSize, cornerSize)\n\t\t\tpaint.strokeWidth = getDimension(R.styleable.FaviconFallbackDrawable_strokeWidth, 0f) * 2f\n\t\t\tintrinsicSize = getDimensionPixelSize(R.styleable.FaviconFallbackDrawable_drawableSize, intrinsicSize)\n\t\t}\n\t\tpaint.textAlign = Paint.Align.CENTER\n\t\tpaint.isFakeBoldText = true\n\t\tcolorForeground = KotatsuColors.random(name)\n\t\tcurrentForegroundColor = MaterialColors.harmonize(colorForeground, colorBackground.defaultColor)\n\t\tonStateChange(state)\n\t}\n\n\toverride fun draw(canvas: Canvas) {\n\t\tif (cornerSize > 0f) {\n\t\t\tcanvas.withClip(clipPath) {\n\t\t\t\tdoDraw(canvas)\n\t\t\t}\n\t\t} else {\n\t\t\tdoDraw(canvas)\n\t\t}\n\t}\n\n\toverride fun onBoundsChange(bounds: Rect) {\n\t\tsuper.onBoundsChange(bounds)\n\t\tboundsF.set(bounds)\n\t\tval innerWidth = bounds.width() - (paint.strokeWidth * 2f)\n\t\tpaint.textSize = getTextSizeForWidth(innerWidth, letter) * 0.5f\n\t\tpaint.getTextBounds(letter, 0, letter.length, textBounds)\n\t\tclipPath.reset()\n\t\tclipPath.addRoundRect(boundsF, cornerSize, cornerSize, Path.Direction.CW)\n\t\tclipPath.close()\n\t}\n\n\toverride fun getIntrinsicWidth(): Int = intrinsicSize\n\n\toverride fun getIntrinsicHeight(): Int = intrinsicSize\n\n\toverride fun isOpaque(): Boolean = cornerSize == 0f && colorBackground.isOpaque\n\n\toverride fun isStateful(): Boolean = colorStroke.isStateful || colorBackground.isStateful\n\n\t@RequiresApi(Build.VERSION_CODES.S)\n\toverride fun hasFocusStateSpecified(): Boolean =\n\t\tcolorBackground.hasFocusStateSpecified() || colorStroke.hasFocusStateSpecified()\n\n\toverride fun onStateChange(state: IntArray): Boolean {\n\t\tval prevStrokeColor = currentStrokeColor\n\t\tval prevBackgroundColor = currentBackgroundColor\n\t\tcurrentStrokeColor = colorStroke.getColorForState(state, colorStroke.defaultColor)\n\t\tcurrentBackgroundColor = colorBackground.getColorForState(state, colorBackground.defaultColor)\n\t\tif (currentBackgroundColor != prevBackgroundColor) {\n\t\t\tcurrentForegroundColor = MaterialColors.harmonize(colorForeground, currentBackgroundColor)\n\t\t}\n\t\treturn prevBackgroundColor != currentBackgroundColor || prevStrokeColor != currentStrokeColor\n\t}\n\n\tprivate fun doDraw(canvas: Canvas) {\n\t\t// background\n\t\tpaint.color = currentBackgroundColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tcanvas.drawPaint(paint)\n\t\t// letter\n\t\tpaint.color = currentForegroundColor\n\t\tval cx = (boundsF.left + boundsF.right) * 0.6f\n\t\tval ty = boundsF.bottom * 0.7f + textBounds.height() * 0.5f - textBounds.bottom\n\t\tcanvas.drawText(letter, cx, ty, paint)\n\t\tif (paint.strokeWidth > 0f) {\n\t\t\t// stroke\n\t\t\tpaint.color = currentStrokeColor\n\t\t\tpaint.style = Paint.Style.STROKE\n\t\t\tcanvas.drawPath(clipPath, paint)\n\t\t}\n\t}\n\n\tprivate fun getTextSizeForWidth(width: Float, text: String): Float {\n\t\tval testTextSize = 48f\n\t\tpaint.textSize = testTextSize\n\t\tpaint.getTextBounds(text, 0, text.length, tempRect)\n\t\treturn testTextSize * width / tempRect.width()\n\t}\n\n\tclass Factory(\n\t\t@StyleRes private val styleResId: Int,\n\t) : ((ImageRequest) -> Image?) {\n\n\t\toverride fun invoke(request: ImageRequest): Image? {\n\t\t\tval source = request.getExtra(mangaSourceKey) ?: return null\n\t\t\tval context = request.context\n\t\t\tval title = source.getTitle(context)\n\t\t\treturn FaviconDrawable(context, styleResId, title).asImage()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StyleRes\nimport androidx.core.content.withStyledAttributes\nimport coil3.Image\nimport coil3.asImage\nimport coil3.request.Disposable\nimport coil3.request.ImageRequest\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler.Companion.suppressCaptchaErrors\nimport org.koitharu.kotatsu.core.image.CoilImageView\nimport org.koitharu.kotatsu.core.parser.favicon.faviconUri\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nclass FaviconView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : CoilImageView(context, attrs, defStyleAttr) {\n\n\t@StyleRes\n\tprivate var iconStyle: Int = R.style.FaviconDrawable\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.FaviconView, defStyleAttr) {\n\t\t\ticonStyle = getResourceId(R.styleable.FaviconView_iconStyle, iconStyle)\n\t\t}\n\t\tif (isInEditMode) {\n\t\t\tsetImageDrawable(\n\t\t\t\tFaviconDrawable(\n\t\t\t\t\tcontext = context,\n\t\t\t\t\tstyleResId = iconStyle,\n\t\t\t\t\tname = context.getString(R.string.app_name).random().toString(),\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n\n\tfun setImageAsync(mangaSource: MangaSource): Disposable {\n\t\tval fallbackFactory: (ImageRequest) -> Image? = {\n\t\t\tFaviconDrawable(context, iconStyle, mangaSource.name).asImage()\n\t\t}\n\t\tval placeholderFactory: (ImageRequest) -> Image? = if (context.isAnimationsEnabled) {\n\t\t\t{ AnimatedFaviconDrawable(context, iconStyle, mangaSource.name).asImage() }\n\t\t} else {\n\t\t\tfallbackFactory\n\t\t}\n\t\treturn enqueueRequest(\n\t\t\tnewRequestBuilder()\n\t\t\t\t.data(mangaSource.faviconUri())\n\t\t\t\t.error(fallbackFactory)\n\t\t\t\t.fallback(fallbackFactory)\n\t\t\t\t.placeholder(placeholderFactory)\n\t\t\t\t.mangaSourceExtra(mangaSource)\n\t\t\t\t.suppressCaptchaErrors()\n\t\t\t\t.build(),\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/PaintDrawable.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.graphics.ColorFilter\nimport android.graphics.Paint\nimport android.graphics.PixelFormat\nimport android.graphics.drawable.Drawable\n\n@Suppress(\"OVERRIDE_DEPRECATION\")\nabstract class PaintDrawable : Drawable() {\n\n\tprotected abstract val paint: Paint\n\n\toverride fun setAlpha(alpha: Int) {\n\t\tpaint.alpha = alpha\n\t}\n\n\toverride fun getAlpha(): Int {\n\t\treturn paint.alpha\n\t}\n\n\toverride fun setColorFilter(colorFilter: ColorFilter?) {\n\t\tpaint.colorFilter = colorFilter\n\t}\n\n\toverride fun getColorFilter(): ColorFilter? {\n\t\treturn paint.colorFilter\n\t}\n\n\toverride fun setDither(dither: Boolean) {\n\t\tpaint.isDither = dither\n\t}\n\n\toverride fun setFilterBitmap(filter: Boolean) {\n\t\tpaint.isFilterBitmap = filter\n\t}\n\n\toverride fun isFilterBitmap(): Boolean {\n\t\treturn paint.isFilterBitmap\n\t}\n\n\toverride fun getOpacity(): Int {\n\t\tif (paint.colorFilter != null) {\n\t\t\treturn PixelFormat.TRANSLUCENT\n\t\t}\n\t\treturn when (paint.alpha) {\n\t\t\t0 -> PixelFormat.TRANSPARENT\n\t\t\t255 -> if (isOpaque()) PixelFormat.OPAQUE else PixelFormat.TRANSLUCENT\n\t\t\telse -> PixelFormat.TRANSLUCENT\n\t\t}\n\t}\n\n\tprotected open fun isOpaque() = false\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TextDrawable.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.PointF\nimport android.graphics.Rect\nimport android.os.Build\nimport android.widget.TextView\nimport androidx.annotation.AttrRes\nimport androidx.annotation.RequiresApi\nimport androidx.core.graphics.PaintCompat\nimport com.google.android.material.resources.TextAppearance\nimport org.koitharu.kotatsu.core.util.ext.getThemeResId\nimport org.koitharu.kotatsu.core.util.ext.hasFocusStateSpecified\n\nclass TextDrawable(\n\tval text: String,\n) : PaintDrawable() {\n\n\toverride val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG)\n\tprivate val textBounds = Rect()\n\tprivate val textPoint = PointF()\n\n\tvar textSize: Float\n\t\tget() = paint.textSize\n\t\tset(value) {\n\t\t\tpaint.textSize = value\n\t\t\tmeasureTextBounds()\n\t\t}\n\n\tvar textColor: ColorStateList = ColorStateList.valueOf(Color.BLACK)\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tonStateChange(state)\n\t\t}\n\n\tinit {\n\t\tonStateChange(state)\n\t\tmeasureTextBounds()\n\t}\n\n\toverride fun draw(canvas: Canvas) {\n\t\tcanvas.drawText(text, textPoint.x, textPoint.y, paint)\n\t}\n\n\toverride fun onBoundsChange(bounds: Rect) {\n\t\ttextPoint.set(\n\t\t\tbounds.exactCenterX() - textBounds.exactCenterX(),\n\t\t\tbounds.exactCenterY() - textBounds.exactCenterY(),\n\t\t)\n\t}\n\n\toverride fun getIntrinsicWidth(): Int = textBounds.width()\n\n\toverride fun getIntrinsicHeight(): Int = textBounds.height()\n\n\toverride fun isStateful(): Boolean = textColor.isStateful\n\n\t@RequiresApi(Build.VERSION_CODES.S)\n\toverride fun hasFocusStateSpecified(): Boolean = textColor.hasFocusStateSpecified()\n\n\toverride fun onStateChange(state: IntArray): Boolean {\n\t\tval prevColor = paint.color\n\t\tpaint.color = textColor.getColorForState(state, textColor.defaultColor)\n\t\treturn paint.color != prevColor\n\t}\n\n\tprivate fun measureTextBounds() {\n\t\tpaint.getTextBounds(text, 0, text.length, textBounds)\n\t\tonBoundsChange(bounds)\n\t}\n\n\tcompanion object {\n\n\t\t@SuppressLint(\"RestrictedApi\")\n\t\tfun create(context: Context, text: String, @AttrRes textAppearanceAttr: Int): TextDrawable {\n\t\t\tval drawable = TextDrawable(text)\n\t\t\tval textAppearance = TextAppearance(context, context.getThemeResId(textAppearanceAttr, androidx.appcompat.R.style.TextAppearance_AppCompat))\n\t\t\tdrawable.textSize = textAppearance.textSize\n\t\t\tdrawable.textColor = textAppearance.textColor ?: drawable.textColor\n\t\t\tdrawable.paint.typeface = textAppearance.getFont(context)\n\t\t\tdrawable.paint.letterSpacing = textAppearance.letterSpacing\n\t\t\treturn drawable\n\t\t}\n\n\t\tfun compound(textView: TextView, text: String): TextDrawable? {\n\t\t\tval drawable = TextDrawable(text)\n\t\t\tdrawable.textSize = textView.textSize\n\t\t\tdrawable.textColor = textView.textColors\n\t\t\treturn drawable.takeIf {\n\t\t\t\tPaintCompat.hasGlyph(drawable.paint, text)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TextViewTarget.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.graphics.drawable.Drawable\nimport android.view.Gravity\nimport android.widget.TextView\nimport androidx.annotation.GravityInt\nimport coil3.target.GenericViewTarget\n\nclass TextViewTarget(\n\toverride val view: TextView,\n\t@GravityInt compoundDrawable: Int,\n) : GenericViewTarget<TextView>() {\n\n\tprivate val drawableIndex: Int = when (compoundDrawable) {\n\t\tGravity.START -> 0\n\t\tGravity.TOP -> 2\n\t\tGravity.END -> 3\n\t\tGravity.BOTTOM -> 4\n\t\telse -> -1\n\t}\n\n\toverride var drawable: Drawable?\n\t\tget() = if (drawableIndex != -1) {\n\t\t\tview.compoundDrawablesRelative[drawableIndex]\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t\tset(value) {\n\t\t\tif (drawableIndex == -1) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval drawables = view.compoundDrawablesRelative\n\t\t\tdrawables[drawableIndex] = value\n\t\t\tview.setCompoundDrawablesRelativeWithIntrinsicBounds(\n\t\t\t\tdrawables[0],\n\t\t\t\tdrawables[1],\n\t\t\t\tdrawables[2],\n\t\t\t\tdrawables[3],\n\t\t\t)\n\t\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/ThumbnailTransformation.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.graphics.Bitmap\nimport android.media.ThumbnailUtils\nimport coil3.size.Size\nimport coil3.size.pxOrElse\nimport coil3.transform.Transformation\n\nclass ThumbnailTransformation : Transformation() {\n\n\toverride val cacheKey: String = javaClass.name\n\n\toverride suspend fun transform(input: Bitmap, size: Size): Bitmap {\n\t\treturn ThumbnailUtils.extractThumbnail(\n\t\t\tinput,\n\t\t\tsize.width.pxOrElse { input.width },\n\t\t\tsize.height.pxOrElse { input.height },\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TrimTransformation.kt",
    "content": "package org.koitharu.kotatsu.core.ui.image\n\nimport android.graphics.Bitmap\nimport androidx.core.graphics.get\nimport coil3.size.Size\nimport coil3.transform.Transformation\nimport org.koitharu.kotatsu.reader.domain.EdgeDetector.Companion.isColorTheSame\n\nclass TrimTransformation(\n\tprivate val tolerance: Int = 20,\n) : Transformation() {\n\n\toverride val cacheKey: String = \"${javaClass.name}-$tolerance\"\n\n\toverride suspend fun transform(input: Bitmap, size: Size): Bitmap {\n\t\tvar left = 0\n\t\tvar top = 0\n\t\tvar right = 0\n\t\tvar bottom = 0\n\n\t\t// Left\n\t\tfor (x in 0 until input.width) {\n\t\t\tvar isColBlank = true\n\t\t\tval prevColor = input[x, 0]\n\t\t\tfor (y in 1 until input.height) {\n\t\t\t\tif (!isColorTheSame(input[x, y], prevColor, tolerance)) {\n\t\t\t\t\tisColBlank = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isColBlank) {\n\t\t\t\tleft++\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (left == input.width) {\n\t\t\treturn input\n\t\t}\n\t\t// Right\n\t\tfor (x in (left until input.width).reversed()) {\n\t\t\tvar isColBlank = true\n\t\t\tval prevColor = input[x, 0]\n\t\t\tfor (y in 1 until input.height) {\n\t\t\t\tif (!isColorTheSame(input[x, y], prevColor, tolerance)) {\n\t\t\t\t\tisColBlank = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isColBlank) {\n\t\t\t\tright++\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t// Top\n\t\tfor (y in 0 until input.height) {\n\t\t\tvar isRowBlank = true\n\t\t\tval prevColor = input[0, y]\n\t\t\tfor (x in 1 until input.width) {\n\t\t\t\tif (!isColorTheSame(input[x, y], prevColor, tolerance)) {\n\t\t\t\t\tisRowBlank = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isRowBlank) {\n\t\t\t\ttop++\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t// Bottom\n\t\tfor (y in (top until input.height).reversed()) {\n\t\t\tvar isRowBlank = true\n\t\t\tval prevColor = input[0, y]\n\t\t\tfor (x in 1 until input.width) {\n\t\t\t\tif (!isColorTheSame(input[x, y], prevColor, tolerance)) {\n\t\t\t\t\tisRowBlank = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isRowBlank) {\n\t\t\t\tbottom++\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\treturn if (left != 0 || right != 0 || top != 0 || bottom != 0) {\n\t\t\tBitmap.createBitmap(input, left, top, input.width - left - right, input.height - top - bottom)\n\t\t} else {\n\t\t\tinput\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/AdapterDelegateClickListenerAdapter.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport android.view.View\nimport android.view.View.OnClickListener\nimport android.view.View.OnContextClickListener\nimport android.view.View.OnLongClickListener\nimport androidx.core.util.Function\nimport com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder\n\nclass AdapterDelegateClickListenerAdapter<I, O>(\n\tprivate val adapterDelegate: AdapterDelegateViewBindingViewHolder<out I, *>,\n\tprivate val clickListener: OnListItemClickListener<O>,\n\tprivate val itemMapper: Function<I, O>,\n) : OnClickListener, OnLongClickListener, OnContextClickListener {\n\n\toverride fun onClick(v: View) {\n\t\tclickListener.onItemClick(mappedItem(), v)\n\t}\n\n\toverride fun onLongClick(v: View): Boolean {\n\t\treturn clickListener.onItemLongClick(mappedItem(), v)\n\t}\n\n\toverride fun onContextClick(v: View): Boolean {\n\t\treturn clickListener.onItemContextClick(mappedItem(), v)\n\t}\n\n\tprivate fun mappedItem(): O = itemMapper.apply(adapterDelegate.item)\n\n\tfun attach() = attach(adapterDelegate.itemView)\n\n\tfun attach(itemView: View) {\n\t\titemView.setOnClickListener(this)\n\t\titemView.setOnLongClickListener(this)\n\t\titemView.setOnContextClickListener(this)\n\t}\n\n\tcompanion object {\n\n\t\toperator fun <T> invoke(\n\t\t\tadapterDelegate: AdapterDelegateViewBindingViewHolder<out T, *>,\n\t\t\tclickListener: OnListItemClickListener<T>\n\t\t): AdapterDelegateClickListenerAdapter<T, T> = AdapterDelegateClickListenerAdapter(\n\t\t\tadapterDelegate = adapterDelegate,\n\t\t\tclickListener = clickListener,\n\t\t\titemMapper = { x -> x },\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BaseListSelectionCallback.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport androidx.recyclerview.widget.RecyclerView\n\nabstract class BaseListSelectionCallback(\n\tprotected val recyclerView: RecyclerView,\n) : ListSelectionController.Callback {\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\trecyclerView.invalidateItemDecorations()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/BoundsScrollListener.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\nabstract class BoundsScrollListener(\n\t@JvmField protected val offsetTop: Int,\n\t@JvmField protected val offsetBottom: Int\n) : RecyclerView.OnScrollListener() {\n\n\toverride fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n\t\tsuper.onScrolled(recyclerView, dx, dy)\n\t\tif (recyclerView.hasPendingAdapterUpdates()) {\n\t\t\treturn\n\t\t}\n\t\tval layoutManager = (recyclerView.layoutManager as? LinearLayoutManager) ?: return\n\t\tval firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()\n\t\tif (firstVisibleItemPosition == RecyclerView.NO_POSITION) {\n\t\t\treturn\n\t\t}\n\t\tval visibleItemCount = layoutManager.childCount\n\t\tval totalItemCount = layoutManager.itemCount\n\t\tif (visibleItemCount + firstVisibleItemPosition >= totalItemCount - offsetBottom) {\n\t\t\tonScrolledToEnd(recyclerView)\n\t\t}\n\t\tif (firstVisibleItemPosition <= offsetTop) {\n\t\t\tonScrolledToStart(recyclerView)\n\t\t}\n\t\tonPostScrolled(recyclerView, firstVisibleItemPosition, visibleItemCount)\n\t}\n\n\tabstract fun onScrolledToStart(recyclerView: RecyclerView)\n\n\tabstract fun onScrolledToEnd(recyclerView: RecyclerView)\n\n\tprotected open fun onPostScrolled(\n\t\trecyclerView: RecyclerView,\n\t\tfirstVisibleItemPosition: Int,\n\t\tvisibleItemCount: Int\n\t) = Unit\n\n\tfun invalidate(recyclerView: RecyclerView) {\n\t\tonScrolled(recyclerView, 0, 0)\n\t}\n\n\tfun postInvalidate(recyclerView: RecyclerView) = recyclerView.post {\n\t\tinvalidate(recyclerView)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightGridLayoutManager.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\nclass FitHeightGridLayoutManager : GridLayoutManager {\n\n\tconstructor(context: Context?, spanCount: Int) : super(context, spanCount)\n\n\tconstructor(\n\t\tcontext: Context?,\n\t\tattrs: AttributeSet?,\n\t\tdefStyleAttr: Int,\n\t\tdefStyleRes: Int,\n\t) : super(context, attrs, defStyleAttr, defStyleRes)\n\n\tconstructor(\n\t\tcontext: Context?,\n\t\tspanCount: Int,\n\t\torientation: Int,\n\t\treverseLayout: Boolean,\n\t) : super(context, spanCount, orientation, reverseLayout)\n\n\n\toverride fun layoutDecoratedWithMargins(child: View, left: Int, top: Int, right: Int, bottom: Int) {\n\t\tif (orientation == RecyclerView.VERTICAL && child.layoutParams.height == LayoutParams.MATCH_PARENT) {\n\t\t\tval parentBottom = height - paddingBottom\n\t\t\tval offset = parentBottom - bottom\n\t\t\tsuper.layoutDecoratedWithMargins(child, left, top, right, bottom + offset)\n\t\t} else {\n\t\t\tsuper.layoutDecoratedWithMargins(child, left, top, right, bottom)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/FitHeightLinearLayoutManager.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StyleRes\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.LayoutParams\n\nclass FitHeightLinearLayoutManager : LinearLayoutManager {\n\n\tconstructor(context: Context) : super(context)\n\tconstructor(\n\t\tcontext: Context,\n\t\t@RecyclerView.Orientation orientation: Int,\n\t\treverseLayout: Boolean,\n\t) : super(context, orientation, reverseLayout)\n\n\tconstructor(\n\t\tcontext: Context,\n\t\tattrs: AttributeSet?,\n\t\t@AttrRes defStyleAttr: Int,\n\t\t@StyleRes defStyleRes: Int,\n\t) : super(context, attrs, defStyleAttr, defStyleRes)\n\n\toverride fun layoutDecoratedWithMargins(child: View, left: Int, top: Int, right: Int, bottom: Int) {\n\t\tif (orientation == RecyclerView.VERTICAL && child.layoutParams.height == LayoutParams.MATCH_PARENT) {\n\t\t\tval parentBottom = height - paddingBottom\n\t\t\tval offset = parentBottom - bottom\n\t\t\tsuper.layoutDecoratedWithMargins(child, left, top, right, bottom + offset)\n\t\t} else {\n\t\t\tsuper.layoutDecoratedWithMargins(child, left, top, right, bottom)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/ListSelectionController.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.appcompat.view.ActionMode\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.collection.LongSet\nimport androidx.collection.longSetOf\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.savedstate.SavedStateRegistry\nimport androidx.savedstate.SavedStateRegistryOwner\nimport kotlinx.coroutines.Dispatchers\nimport org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.toLongArray\nimport org.koitharu.kotatsu.core.util.ext.toSet\nimport kotlin.coroutines.EmptyCoroutineContext\n\nprivate const val KEY_SELECTION = \"selection\"\nprivate const val PROVIDER_NAME = \"selection_decoration\"\n\nclass ListSelectionController(\n\tprivate val appCompatDelegate: AppCompatDelegate,\n\tprivate val decoration: AbstractSelectionItemDecoration,\n\tprivate val registryOwner: SavedStateRegistryOwner,\n\tprivate val callback: Callback,\n) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {\n\n\tprivate var actionMode: ActionMode? = null\n\tprivate var focusedItemId: LongSet? = null\n\n\tvar useActionMode: Boolean = true\n\n\tval count: Int\n\t\tget() = if (focusedItemId != null) 1 else decoration.checkedItemsCount\n\n\tinit {\n\t\tregistryOwner.lifecycle.addObserver(StateEventObserver())\n\t}\n\n\tfun snapshot(): Set<Long> = (focusedItemId ?: peekCheckedIds()).toSet()\n\n\tfun peekCheckedIds(): LongSet {\n\t\treturn focusedItemId ?: decoration.checkedItemsIds\n\t}\n\n\tfun clear() {\n\t\tdecoration.clearSelection()\n\t\tnotifySelectionChanged()\n\t}\n\n\tfun addAll(ids: Collection<Long>) {\n\t\tif (ids.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tstartActionMode()\n\t\tdecoration.checkAll(ids)\n\t\tnotifySelectionChanged()\n\t}\n\n\tfun attachToRecyclerView(recyclerView: RecyclerView) {\n\t\trecyclerView.addItemDecoration(decoration)\n\t}\n\n\toverride fun saveState(): Bundle {\n\t\tval bundle = Bundle(1)\n\t\tbundle.putLongArray(KEY_SELECTION, peekCheckedIds().toLongArray())\n\t\treturn bundle\n\t}\n\n\tfun onItemClick(id: Long): Boolean {\n\t\tif (decoration.checkedItemsCount != 0) {\n\t\t\tdecoration.toggleItemChecked(id)\n\t\t\tif (decoration.checkedItemsCount == 0) {\n\t\t\t\tactionMode?.finish()\n\t\t\t} else {\n\t\t\t\tactionMode?.invalidate()\n\t\t\t}\n\t\t\tnotifySelectionChanged()\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tfun onItemLongClick(view: View, id: Long): Boolean {\n\t\treturn if (useActionMode) {\n\t\t\tstartSelection(id)\n\t\t} else {\n\t\t\tonItemContextClick(view, id)\n\t\t}\n\t}\n\n\tfun onItemContextClick(view: View, id: Long): Boolean {\n\t\tfocusedItemId = longSetOf(id)\n\t\tval menu = PopupMenu(view.context, view)\n\t\tcallback.onCreateActionMode(this, menu.menuInflater, menu.menu)\n\t\tcallback.onPrepareActionMode(this, null, menu.menu)\n\t\tmenu.setForceShowIcon(true)\n\t\tif (menu.menu.hasVisibleItems()) {\n\t\t\tmenu.setOnMenuItemClickListener { menuItem ->\n\t\t\t\tcallback.onActionItemClicked(this, null, menuItem)\n\t\t\t}\n\t\t\tmenu.setOnDismissListener {\n\t\t\t\tfocusedItemId = null\n\t\t\t}\n\t\t\tmenu.show()\n\t\t\treturn true\n\t\t} else {\n\t\t\tfocusedItemId = null\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfun startSelection(id: Long): Boolean = startActionMode()?.also {\n\t\tdecoration.setItemIsChecked(id, true)\n\t\tnotifySelectionChanged()\n\t} != null\n\n\toverride fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {\n\t\treturn callback.onCreateActionMode(this, mode.menuInflater, menu)\n\t}\n\n\toverride fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {\n\t\treturn callback.onPrepareActionMode(this, mode, menu)\n\t}\n\n\toverride fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {\n\t\treturn callback.onActionItemClicked(this, mode, item)\n\t}\n\n\toverride fun onDestroyActionMode(mode: ActionMode) {\n\t\tcallback.onDestroyActionMode(this, mode)\n\t\tclear()\n\t\tactionMode = null\n\t}\n\n\tprivate fun startActionMode(): ActionMode? {\n\t\tfocusedItemId = null\n\t\treturn actionMode ?: appCompatDelegate.startSupportActionMode(this).also {\n\t\t\tactionMode = it\n\t\t}\n\t}\n\n\tprivate fun notifySelectionChanged() {\n\t\tval count = decoration.checkedItemsCount\n\t\tcallback.onSelectionChanged(this, count)\n\t\tif (count == 0) {\n\t\t\tactionMode?.finish()\n\t\t} else {\n\t\t\tactionMode?.invalidate()\n\t\t}\n\t}\n\n\tprivate fun restoreState(ids: Collection<Long>) {\n\t\tif (ids.isEmpty() || decoration.checkedItemsCount != 0) {\n\t\t\treturn\n\t\t}\n\t\tdecoration.checkAll(ids)\n\t\tstartActionMode()\n\t\tnotifySelectionChanged()\n\t}\n\n\tinterface Callback {\n\n\t\tfun onSelectionChanged(controller: ListSelectionController, count: Int)\n\n\t\tfun onCreateActionMode(controller: ListSelectionController, menuInflater: MenuInflater, menu: Menu): Boolean\n\n\t\tfun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {\n\t\t\tmode?.title = controller.count.toString()\n\t\t\treturn true\n\t\t}\n\n\t\tfun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean\n\n\t\tfun onDestroyActionMode(controller: ListSelectionController, mode: ActionMode) = Unit\n\t}\n\n\tprivate inner class StateEventObserver : LifecycleEventObserver {\n\n\t\toverride fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {\n\t\t\tif (event == Lifecycle.Event.ON_CREATE) {\n\t\t\t\tsource.lifecycle.removeObserver(this)\n\t\t\t\tval registry = registryOwner.savedStateRegistry\n\t\t\t\tregistry.registerSavedStateProvider(PROVIDER_NAME, this@ListSelectionController)\n\t\t\t\tval state = registry.consumeRestoredStateForKey(PROVIDER_NAME)\n\t\t\t\tif (state != null) {\n\t\t\t\t\tDispatchers.Main.dispatch(EmptyCoroutineContext) { // == Handler.post\n\t\t\t\t\t\tif (source.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {\n\t\t\t\t\t\t\trestoreState(state.getLongArray(KEY_SELECTION)?.toList().orEmpty())\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnListItemClickListener.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport android.view.View\n\nfun interface OnListItemClickListener<I> {\n\n\tfun onItemClick(item: I, view: View)\n\n\tfun onItemLongClick(item: I, view: View): Boolean = false\n\n\tfun onItemContextClick(item: I, view: View): Boolean = onItemLongClick(item, view)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/OnTipCloseListener.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\ninterface OnTipCloseListener<T> {\n\n\tfun onCloseTip(tip: T)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/PaginationScrollListener.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport androidx.recyclerview.widget.RecyclerView\n\nclass PaginationScrollListener(offset: Int, private val callback: Callback) :\n\tBoundsScrollListener(0, offset) {\n\n\toverride fun onScrolledToStart(recyclerView: RecyclerView) = Unit\n\n\toverride fun onScrolledToEnd(recyclerView: RecyclerView) {\n\t\tcallback.onScrolledToEnd()\n\t}\n\n\tinterface Callback {\n\n\t\tfun onScrolledToEnd()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/RecyclerScrollKeeper.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.AdapterDataObserver\n\nclass RecyclerScrollKeeper(\n\tprivate val rv: RecyclerView,\n) : AdapterDataObserver() {\n\n\tprivate val scrollUpRunnable = Runnable {\n\t\t(rv.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(0, 0)\n\t}\n\n\tfun attach() {\n\t\trv.adapter?.registerAdapterDataObserver(this)\n\t}\n\n\toverride fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n\t\tsuper.onItemRangeInserted(positionStart, itemCount)\n\t\tif (positionStart == 0 && isScrolledToTop()) {\n\t\t\tpostScrollUp()\n\t\t}\n\t}\n\n\toverride fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n\t\tsuper.onItemRangeMoved(fromPosition, toPosition, itemCount)\n\t\tif (toPosition == 0 && isScrolledToTop()) {\n\t\t\tpostScrollUp()\n\t\t}\n\t}\n\n\tprivate fun postScrollUp() {\n\t\trv.postDelayed(scrollUpRunnable, 500L)\n\t}\n\n\tprivate fun isScrolledToTop(): Boolean {\n\t\treturn (rv.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() == 0\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/SectionedSelectionController.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list\n\nprivate const val PROVIDER_NAME = \"selection_decoration_sectioned\"\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/AbstractSelectionItemDecoration.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.decor\n\nimport android.graphics.Canvas\nimport android.graphics.Rect\nimport android.graphics.RectF\nimport android.view.View\nimport androidx.collection.LongSet\nimport androidx.collection.MutableLongSet\nimport androidx.core.view.children\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.NO_ID\n\nabstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() {\n\n\tprivate val bounds = Rect()\n\tprivate val boundsF = RectF()\n\tprotected val selection = MutableLongSet()\n\n\tprotected var hasBackground: Boolean = true\n\tprotected var hasForeground: Boolean = false\n\tprotected var isIncludeDecorAndMargins: Boolean = true\n\n\tval checkedItemsCount: Int\n\t\tget() = selection.size\n\n\tval checkedItemsIds: LongSet\n\t\tget() = selection\n\n\tfun toggleItemChecked(id: Long) {\n\t\tif (!selection.remove(id)) {\n\t\t\tselection.add(id)\n\t\t}\n\t}\n\n\tfun setItemIsChecked(id: Long, isChecked: Boolean) {\n\t\tif (isChecked) {\n\t\t\tselection.add(id)\n\t\t} else {\n\t\t\tselection.remove(id)\n\t\t}\n\t}\n\n\tfun checkAll(ids: Collection<Long>) {\n\t\tfor (id in ids) {\n\t\t\tselection.add(id)\n\t\t}\n\t}\n\n\tfun clearSelection() {\n\t\tselection.clear()\n\t}\n\n\toverride fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {\n\t\tif (hasBackground) {\n\t\t\tdoDraw(canvas, parent, state, false)\n\t\t} else {\n\t\t\tsuper.onDraw(canvas, parent, state)\n\t\t}\n\t}\n\n\toverride fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {\n\t\tif (hasForeground) {\n\t\t\tdoDraw(canvas, parent, state, true)\n\t\t} else {\n\t\t\tsuper.onDrawOver(canvas, parent, state)\n\t\t}\n\t}\n\n\tprivate fun doDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State, isOver: Boolean) {\n\t\tval checkpoint = canvas.save()\n\t\tif (parent.clipToPadding) {\n\t\t\tcanvas.clipRect(\n\t\t\t\tparent.paddingLeft, parent.paddingTop, parent.width - parent.paddingRight,\n\t\t\t\tparent.height - parent.paddingBottom,\n\t\t\t)\n\t\t}\n\n\t\tfor (child in parent.children) {\n\t\t\tval itemId = getItemId(parent, child)\n\t\t\tif (itemId != NO_ID && itemId in selection) {\n\t\t\t\tif (isIncludeDecorAndMargins) {\n\t\t\t\t\tparent.getDecoratedBoundsWithMargins(child, bounds)\n\t\t\t\t} else {\n\t\t\t\t\tbounds.set(child.left, child.top, child.right, child.bottom)\n\t\t\t\t}\n\t\t\t\tboundsF.set(bounds)\n\t\t\t\tboundsF.offset(child.translationX, child.translationY)\n\t\t\t\tif (isOver) {\n\t\t\t\t\tonDrawForeground(canvas, parent, child, boundsF, state)\n\t\t\t\t} else {\n\t\t\t\t\tonDrawBackground(canvas, parent, child, boundsF, state)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcanvas.restoreToCount(checkpoint)\n\t}\n\n\tabstract fun getItemId(parent: RecyclerView, child: View): Long\n\n\tprotected open fun onDrawBackground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) = Unit\n\n\tprotected open fun onDrawForeground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/decor/SpacingItemDecoration.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.decor\n\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.annotation.Px\nimport androidx.recyclerview.widget.RecyclerView\n\nclass SpacingItemDecoration(\n    @Px private val spacing: Int,\n    private val withBottomPadding: Boolean,\n) : RecyclerView.ItemDecoration() {\n\n\toverride fun getItemOffsets(\n\t\toutRect: Rect,\n\t\tview: View,\n\t\tparent: RecyclerView,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\toutRect.set(spacing, spacing, spacing, if (withBottomPadding) spacing else 0)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/BubbleAnimator.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.fastscroll\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.view.View\nimport android.view.ViewAnimationUtils\nimport android.view.animation.AccelerateInterpolator\nimport android.view.animation.DecelerateInterpolator\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport org.koitharu.kotatsu.core.util.ext.animatorDurationScale\nimport org.koitharu.kotatsu.core.util.ext.measureWidth\nimport kotlin.math.hypot\n\nclass BubbleAnimator(\n\tprivate val bubble: View,\n) {\n\n\tprivate val animationDuration = (\n\t\tbubble.resources.getInteger(android.R.integer.config_shortAnimTime) *\n\t\t\tbubble.context.animatorDurationScale\n\t\t).toLong()\n\tprivate var animator: Animator? = null\n\tprivate var isHiding = false\n\n\tfun show() {\n\t\tif (bubble.isVisible && !isHiding) {\n\t\t\treturn\n\t\t}\n\t\tisHiding = false\n\t\tanimator?.cancel()\n\t\tanimator = ViewAnimationUtils.createCircularReveal(\n\t\t\tbubble,\n\t\t\tbubble.measureWidth(),\n\t\t\tbubble.measuredHeight,\n\t\t\t0f,\n\t\t\thypot(bubble.width.toDouble(), bubble.height.toDouble()).toFloat(),\n\t\t).apply {\n\t\t\tbubble.isVisible = true\n\t\t\tduration = animationDuration\n\t\t\tinterpolator = DecelerateInterpolator()\n\t\t\tstart()\n\t\t}\n\t}\n\n\tfun hide() {\n\t\tif (!bubble.isVisible || isHiding) {\n\t\t\treturn\n\t\t}\n\t\tanimator?.cancel()\n\t\tisHiding = true\n\t\tanimator = ViewAnimationUtils.createCircularReveal(\n\t\t\tbubble,\n\t\t\tbubble.width,\n\t\t\tbubble.height,\n\t\t\thypot(bubble.width.toDouble(), bubble.height.toDouble()).toFloat(),\n\t\t\t0f,\n\t\t).apply {\n\t\t\tduration = animationDuration\n\t\t\tinterpolator = AccelerateInterpolator()\n\t\t\taddListener(HideListener())\n\t\t\tstart()\n\t\t}\n\t}\n\n\tprivate inner class HideListener : AnimatorListenerAdapter() {\n\n\t\tprivate var isCancelled = false\n\n\t\toverride fun onAnimationCancel(animation: Animator) {\n\t\t\tsuper.onAnimationCancel(animation)\n\t\t\tisCancelled = true\n\t\t}\n\n\t\toverride fun onAnimationEnd(animation: Animator) {\n\t\t\tsuper.onAnimationEnd(animation)\n\t\t\tif (!isCancelled && animation === this@BubbleAnimator.animator) {\n\t\t\t\tbubble.isInvisible = true\n\t\t\t\tisHiding = false\n\t\t\t\tthis@BubbleAnimator.animator = null\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScrollRecyclerView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.fastscroll\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup\nimport androidx.annotation.AttrRes\nimport androidx.core.view.ancestors\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.widget.ViewPager2\nimport org.koitharu.kotatsu.R\n\nclass FastScrollRecyclerView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = androidx.recyclerview.R.attr.recyclerViewStyle,\n) : RecyclerView(context, attrs, defStyleAttr) {\n\n\tval fastScroller = FastScroller(context, attrs)\n\tvar isVP2BugWorkaroundEnabled = false\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tif (value && isAttachedToWindow) {\n\t\t\t\tcheckIfInVP2()\n\t\t\t} else if (!value) {\n\t\t\t\tapplyVP2Workaround = false\n\t\t\t}\n\t\t}\n\tprivate var applyVP2Workaround = false\n\n\tvar isFastScrollerEnabled: Boolean = true\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tfastScroller.isVisible = value && isVisible\n\t\t}\n\n\tinit {\n\t\tfastScroller.id = R.id.fast_scroller\n\t\tfastScroller.layoutParams = ViewGroup.LayoutParams(\n\t\t\tLayoutParams.WRAP_CONTENT,\n\t\t\tLayoutParams.MATCH_PARENT,\n\t\t)\n\t}\n\n\toverride fun setAdapter(adapter: Adapter<*>?) {\n\t\tsuper.setAdapter(adapter)\n\t\tfastScroller.setSectionIndexer(adapter as? FastScroller.SectionIndexer)\n\t}\n\n\toverride fun setVisibility(visibility: Int) {\n\t\tsuper.setVisibility(visibility)\n\t\tfastScroller.visibility = if (isFastScrollerEnabled) visibility else GONE\n\t}\n\n\toverride fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {\n\t\tsuper.setPadding(left, top, right, bottom)\n\t\tfastScroller.setPadding(left, top, right, bottom)\n\t}\n\n\toverride fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {\n\t\tsuper.setPaddingRelative(start, top, end, bottom)\n\t\tfastScroller.setPaddingRelative(start, top, end, bottom)\n\t}\n\n\toverride fun onAttachedToWindow() {\n\t\tsuper.onAttachedToWindow()\n\t\tfastScroller.attachRecyclerView(this)\n\t\tif (isVP2BugWorkaroundEnabled) {\n\t\t\tcheckIfInVP2()\n\t\t}\n\t}\n\n\toverride fun onDetachedFromWindow() {\n\t\tfastScroller.detachRecyclerView()\n\t\tsuper.onDetachedFromWindow()\n\t\tapplyVP2Workaround = false\n\t}\n\n\toverride fun isLayoutRequested(): Boolean {\n\t\treturn if (applyVP2Workaround) false else super.isLayoutRequested()\n\t}\n\n\toverride fun requestLayout() {\n\t\tsuper.requestLayout()\n\t\tif (applyVP2Workaround && parent?.isLayoutRequested == true) {\n\t\t\tparent?.requestLayout()\n\t\t}\n\t}\n\n\tprivate fun checkIfInVP2() {\n\t\tapplyVP2Workaround = ancestors.any { it is ViewPager2 } == true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/FastScroller.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.fastscroll\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.TypedArray\nimport android.graphics.Color\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.LinearLayout\nimport android.widget.RelativeLayout\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.annotation.DimenRes\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.Px\nimport androidx.annotation.StyleableRes\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.constraintlayout.widget.ConstraintSet\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.view.GravityCompat\nimport androidx.core.view.ancestors\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.isLayoutReversed\nimport org.koitharu.kotatsu.databinding.FastScrollerBinding\nimport kotlin.math.roundToInt\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nprivate const val SCROLLBAR_HIDE_DELAY = 1000L\nprivate const val TRACK_SNAP_RANGE = 5\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nclass FastScroller @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = R.attr.fastScrollerStyle,\n) : LinearLayout(context, attrs, defStyleAttr) {\n\n\tenum class BubbleSize(@DrawableRes val drawableId: Int, @DimenRes val textSizeId: Int) {\n\t\tNORMAL(R.drawable.fastscroll_bubble, R.dimen.fastscroll_bubble_text_size),\n\t\tSMALL(R.drawable.fastscroll_bubble_small, R.dimen.fastscroll_bubble_text_size_small)\n\t}\n\n\tprivate val binding = FastScrollerBinding.inflate(LayoutInflater.from(context), this)\n\n\tprivate val scrollbarPaddingEnd = context.resources.getDimension(R.dimen.fastscroll_scrollbar_padding_end)\n\n\t@ColorInt\n\tprivate var bubbleColor = 0\n\n\t@ColorInt\n\tprivate var handleColor = 0\n\n\tprivate var bubbleHeight = 0\n\tprivate var handleHeight = 0\n\tprivate var viewHeight = 0\n\tprivate var offset = 0\n\tprivate var hideScrollbar = true\n\tprivate var showBubble = true\n\tprivate var showBubbleAlways = false\n\tprivate var bubbleSize = BubbleSize.SMALL\n\tprivate var bubbleImage: Drawable? = null\n\tprivate var handleImage: Drawable? = null\n\tprivate var trackImage: Drawable? = null\n\tprivate var recyclerView: RecyclerView? = null\n\tprivate val scrollbarAnimator = ScrollbarAnimator(binding.scrollbar, scrollbarPaddingEnd)\n\tprivate val bubbleAnimator = BubbleAnimator(binding.bubble)\n\n\tprivate var fastScrollListener: FastScrollListener? = null\n\tprivate var sectionIndexer: SectionIndexer? = null\n\n\tprivate val scrollbarHider = Runnable {\n\t\thideBubble()\n\t\thideScrollbar()\n\t}\n\n\tprivate val scrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() {\n\t\toverride fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n\t\t\tif (!binding.thumb.isSelected && isEnabled) {\n\t\t\t\tval y = recyclerView.scrollProportion\n\t\t\t\tsetViewPositions(y)\n\n\t\t\t\tif (showBubbleAlways) {\n\t\t\t\t\tval targetPos = getRecyclerViewTargetPosition(y)\n\t\t\t\t\tsectionIndexer?.let { bindBubble(it.getSectionText(recyclerView.context, targetPos)) }\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\toverride fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n\t\t\tsuper.onScrollStateChanged(recyclerView, newState)\n\n\t\t\tif (isEnabled) {\n\t\t\t\twhen (newState) {\n\t\t\t\t\tRecyclerView.SCROLL_STATE_DRAGGING -> {\n\t\t\t\t\t\thandler.removeCallbacks(scrollbarHider)\n\t\t\t\t\t\tshowScrollbar()\n\t\t\t\t\t\tif (showBubbleAlways && sectionIndexer != null) showBubble()\n\t\t\t\t\t}\n\n\t\t\t\t\tRecyclerView.SCROLL_STATE_IDLE -> if (hideScrollbar && !binding.thumb.isSelected) {\n\t\t\t\t\t\thandler.postDelayed(scrollbarHider, SCROLLBAR_HIDE_DELAY)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate val RecyclerView.scrollProportion: Float\n\t\tget() {\n\t\t\tval rangeDiff = computeVerticalScrollRange() - computeVerticalScrollExtent()\n\t\t\tval proportion = computeVerticalScrollOffset() / if (rangeDiff > 0) rangeDiff.toFloat() else 1f\n\t\t\treturn viewHeight * proportion\n\t\t}\n\n\tval isScrollbarVisible: Boolean\n\t\tget() = binding.scrollbar.isVisible\n\n\tinit {\n\t\tclipChildren = false\n\t\torientation = HORIZONTAL\n\n\t\t@ColorInt var bubbleColor = context.getThemeColor(appcompatR.attr.colorControlNormal, Color.DKGRAY)\n\t\t@ColorInt var handleColor = bubbleColor\n\t\t@ColorInt var trackColor = context.getThemeColor(materialR.attr.colorOutline, Color.LTGRAY)\n\t\t@ColorInt var textColor = context.getThemeColor(android.R.attr.textColorPrimaryInverse, Color.WHITE)\n\n\t\tvar showTrack = false\n\n\t\tcontext.withStyledAttributes(attrs, R.styleable.FastScrollRecyclerView, defStyleAttr) {\n\t\t\tbubbleColor = getColor(R.styleable.FastScrollRecyclerView_bubbleColor, bubbleColor)\n\t\t\thandleColor = getColor(R.styleable.FastScrollRecyclerView_thumbColor, handleColor)\n\t\t\ttrackColor = getColor(R.styleable.FastScrollRecyclerView_trackColor, trackColor)\n\t\t\ttextColor = getColor(R.styleable.FastScrollRecyclerView_bubbleTextColor, textColor)\n\t\t\thideScrollbar = getBoolean(R.styleable.FastScrollRecyclerView_hideScrollbar, hideScrollbar)\n\t\t\tshowBubble = getBoolean(R.styleable.FastScrollRecyclerView_showBubble, showBubble)\n\t\t\tshowBubbleAlways = getBoolean(R.styleable.FastScrollRecyclerView_showBubbleAlways, showBubbleAlways)\n\t\t\tshowTrack = getBoolean(R.styleable.FastScrollRecyclerView_showTrack, showTrack)\n\t\t\tbubbleSize = getBubbleSize(R.styleable.FastScrollRecyclerView_bubbleSize, bubbleSize)\n\t\t\tval textSize = getDimension(R.styleable.FastScrollRecyclerView_bubbleTextSize, bubbleSize.textSize)\n\t\t\tbinding.bubble.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)\n\t\t\toffset = getDimensionPixelOffset(R.styleable.FastScrollRecyclerView_scrollerOffset, offset)\n\t\t}\n\n\t\tsetTrackColor(trackColor)\n\t\tsetHandleColor(handleColor)\n\t\tsetBubbleColor(bubbleColor)\n\t\tsetBubbleTextColor(textColor)\n\t\tsetHideScrollbar(hideScrollbar)\n\t\tsetBubbleVisible(showBubble, showBubbleAlways)\n\t\tsetTrackVisible(showTrack)\n\t}\n\n\toverride fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) {\n\t\tsuper.onSizeChanged(w, h, oldW, oldH)\n\t\tviewHeight = h - paddingTop - paddingBottom\n\t}\n\n\t@SuppressLint(\"ClickableViewAccessibility\")\n\toverride fun onTouchEvent(event: MotionEvent): Boolean {\n\t\tval setYPositions: () -> Unit = {\n\t\t\tval y = event.y\n\t\t\tsetViewPositions(y)\n\t\t\tsetRecyclerViewPosition(y)\n\t\t}\n\n\t\twhen (event.actionMasked) {\n\t\t\tMotionEvent.ACTION_DOWN -> {\n\t\t\t\tif (!isScrollbarVisible || event.x.toInt() !in binding.scrollbar.left..binding.scrollbar.right) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\trequestDisallowInterceptTouchEvent(true)\n\t\t\t\tsetHandleSelected(true)\n\n\t\t\t\thandler.removeCallbacks(scrollbarHider)\n\t\t\t\tshowScrollbar()\n\t\t\t\tif (showBubble && sectionIndexer != null) showBubble()\n\n\t\t\t\tfastScrollListener?.onFastScrollStart(this)\n\n\t\t\t\tsetYPositions()\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tMotionEvent.ACTION_MOVE -> {\n\t\t\t\tsetYPositions()\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tMotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {\n\t\t\t\trequestDisallowInterceptTouchEvent(false)\n\t\t\t\tsetHandleSelected(false)\n\n\t\t\t\tif (hideScrollbar) handler.postDelayed(scrollbarHider, SCROLLBAR_HIDE_DELAY)\n\t\t\t\tif (!showBubbleAlways) hideBubble()\n\n\t\t\t\tfastScrollListener?.onFastScrollStop(this)\n\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\treturn super.onTouchEvent(event)\n\t}\n\n\t/**\n\t * Set the enabled state of this view.\n\t *\n\t * @param enabled True if this view is enabled, false otherwise\n\t */\n\toverride fun setEnabled(enabled: Boolean) {\n\t\tsuper.setEnabled(enabled)\n\t\tisVisible = enabled\n\t}\n\n\t/**\n\t * Set the [ViewGroup.LayoutParams] associated with this view. These supply\n\t * parameters to the *parent* of this view specifying how it should be arranged.\n\t *\n\t * @param params The [ViewGroup.LayoutParams] for this view, cannot be null\n\t */\n\t@Suppress(\"RemoveRedundantQualifierName\")\n\toverride fun setLayoutParams(params: ViewGroup.LayoutParams) {\n\t\tparams.width = LayoutParams.WRAP_CONTENT\n\t\tsuper.setLayoutParams(params)\n\t}\n\n\t/**\n\t * Set the [ViewGroup.LayoutParams] associated with this view. These supply\n\t * parameters to the *parent* of this view specifying how it should be arranged.\n\t *\n\t * @param viewGroup The parent [ViewGroup] for this view, cannot be null\n\t */\n\tfun setLayoutParams(viewGroup: ViewGroup) {\n\t\tval recyclerViewId = recyclerView?.id ?: NO_ID\n\t\tval offsetTop = resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_top)\n\t\tval offsetBottom = resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_bottom)\n\n\t\trequire(recyclerViewId != NO_ID) { \"RecyclerView must have a view ID\" }\n\n\t\twhen (viewGroup) {\n\t\t\tis ConstraintLayout -> {\n\t\t\t\tval endId = if (recyclerView?.parent === parent) recyclerViewId else ConstraintSet.PARENT_ID\n\t\t\t\tval startId = id\n\n\t\t\t\tConstraintSet().apply {\n\t\t\t\t\tclone(viewGroup)\n\t\t\t\t\tconnect(startId, ConstraintSet.TOP, endId, ConstraintSet.TOP)\n\t\t\t\t\tconnect(startId, ConstraintSet.BOTTOM, endId, ConstraintSet.BOTTOM)\n\t\t\t\t\tconnect(startId, ConstraintSet.END, endId, ConstraintSet.END)\n\t\t\t\t\tapplyTo(viewGroup)\n\t\t\t\t}\n\n\t\t\t\tupdateLayoutParams<ConstraintLayout.LayoutParams> {\n\t\t\t\t\theight = 0\n\t\t\t\t\tmarginStart = offset\n\t\t\t\t\tmarginEnd = offset\n\t\t\t\t\ttopMargin = offsetTop\n\t\t\t\t\tbottomMargin = offsetBottom\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tis CoordinatorLayout -> updateLayoutParams<CoordinatorLayout.LayoutParams> {\n\t\t\t\theight = LayoutParams.MATCH_PARENT\n\t\t\t\tanchorGravity = GravityCompat.END\n\t\t\t\tanchorId = recyclerViewId\n\t\t\t\tmarginStart = offset\n\t\t\t\tmarginEnd = offset\n\t\t\t\ttopMargin = offsetTop\n\t\t\t\tbottomMargin = offsetBottom\n\t\t\t}\n\n\t\t\tis FrameLayout -> updateLayoutParams<FrameLayout.LayoutParams> {\n\t\t\t\theight = LayoutParams.MATCH_PARENT\n\t\t\t\tgravity = GravityCompat.END\n\t\t\t\tmarginStart = offset\n\t\t\t\tmarginEnd = offset\n\t\t\t\ttopMargin = offsetTop\n\t\t\t\tbottomMargin = offsetBottom\n\t\t\t}\n\n\t\t\tis RelativeLayout -> updateLayoutParams<RelativeLayout.LayoutParams> {\n\t\t\t\theight = 0\n\t\t\t\taddRule(RelativeLayout.ALIGN_TOP, recyclerViewId)\n\t\t\t\taddRule(RelativeLayout.ALIGN_BOTTOM, recyclerViewId)\n\t\t\t\taddRule(RelativeLayout.ALIGN_END, recyclerViewId)\n\t\t\t\tmarginStart = offset\n\t\t\t\tmarginEnd = offset\n\t\t\t\ttopMargin = offsetTop\n\t\t\t\tbottomMargin = offsetBottom\n\t\t\t}\n\n\t\t\telse -> throw IllegalArgumentException(\"Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout\")\n\t\t}\n\n\t\tupdateViewHeights()\n\t}\n\n\t/**\n\t * Set the [RecyclerView] associated with this [FastScroller]. This allows the\n\t * FastScroller to set its layout parameters and listen for scroll changes.\n\t *\n\t * @param recyclerView The [RecyclerView] to attach, cannot be null\n\t * @see detachRecyclerView\n\t */\n\tfun attachRecyclerView(recyclerView: RecyclerView) {\n\t\tif (this.recyclerView != null) {\n\t\t\tdetachRecyclerView()\n\t\t}\n\t\tthis.recyclerView = recyclerView\n\n\t\tif (parent is ViewGroup) {\n\t\t\tsetLayoutParams(parent as ViewGroup)\n\t\t} else {\n\t\t\tval viewGroup = findValidParent(recyclerView)\n\t\t\tif (viewGroup != null) {\n\t\t\t\tviewGroup.addView(this)\n\t\t\t\tsetLayoutParams(viewGroup)\n\t\t\t}\n\t\t}\n\n\t\trecyclerView.addOnScrollListener(scrollListener)\n\n\t\t// set initial positions for bubble and thumb\n\t\tpost { setViewPositions(this.recyclerView?.scrollProportion ?: 0f) }\n\t}\n\n\t/**\n\t * Clears references to the attached [RecyclerView] and stops listening for scroll changes.\n\t *\n\t * @see attachRecyclerView\n\t */\n\tfun detachRecyclerView() {\n\t\trecyclerView?.removeOnScrollListener(scrollListener)\n\t\trecyclerView = null\n\t}\n\n\t/**\n\t * Set a new [FastScrollListener] that will listen to fast scroll events.\n\t *\n\t * @param fastScrollListener The new [FastScrollListener] to set, or null to set none\n\t */\n\tfun setFastScrollListener(fastScrollListener: FastScrollListener?) {\n\t\tthis.fastScrollListener = fastScrollListener\n\t}\n\n\t/**\n\t * Set a new [SectionIndexer] that provides section text for this [FastScroller].\n\t *\n\t * @param sectionIndexer The new [SectionIndexer] to set, or null to set none\n\t */\n\tfun setSectionIndexer(sectionIndexer: SectionIndexer?) {\n\t\tthis.sectionIndexer = sectionIndexer\n\t}\n\n\t/**\n\t * Hide the scrollbar when not scrolling.\n\t *\n\t * @param hideScrollbar True to hide the scrollbar, false to show\n\t */\n\tfun setHideScrollbar(hideScrollbar: Boolean) {\n\t\tif (this.hideScrollbar != hideScrollbar) {\n\t\t\tthis.hideScrollbar = hideScrollbar\n\t\t\tbinding.scrollbar.isGone = hideScrollbar\n\t\t}\n\t}\n\n\t/**\n\t * Show the scroll track while scrolling.\n\t *\n\t * @param visible True to show scroll track, false to hide\n\t */\n\tfun setTrackVisible(visible: Boolean) {\n\t\tbinding.track.isVisible = visible\n\t}\n\n\t/**\n\t * Set the color of the scroll track.\n\t *\n\t * @param color The color for the scroll track\n\t */\n\tfun setTrackColor(@ColorInt color: Int) {\n\t\tif (trackImage == null) {\n\t\t\ttrackImage = ContextCompat.getDrawable(context, R.drawable.fastscroll_track)\n\t\t}\n\n\t\ttrackImage?.let {\n\t\t\tit.setTint(color)\n\t\t\tbinding.track.setImageDrawable(it)\n\t\t}\n\t}\n\n\t/**\n\t * Set the color of the scroll thumb.\n\t *\n\t * @param color The color for the scroll thumb\n\t */\n\tfun setHandleColor(@ColorInt color: Int) {\n\t\thandleColor = color\n\n\t\tif (handleImage == null) {\n\t\t\thandleImage = ContextCompat.getDrawable(context, R.drawable.fastscroll_handle)\n\t\t}\n\n\t\thandleImage?.let {\n\t\t\tit.setTint(handleColor)\n\t\t\tbinding.thumb.setImageDrawable(it)\n\t\t}\n\t}\n\n\t/**\n\t * Show the section bubble while scrolling.\n\t *\n\t * @param visible True to show the bubble, false to hide\n\t * @param always  True to always show the bubble, false to only show on thumb touch\n\t */\n\t@JvmOverloads\n\tfun setBubbleVisible(visible: Boolean, always: Boolean = false) {\n\t\tshowBubble = visible\n\t\tshowBubbleAlways = visible && always\n\t}\n\n\t/**\n\t * Set the background color of the section bubble.\n\t *\n\t * @param color The background color for the section bubble\n\t */\n\tfun setBubbleColor(@ColorInt color: Int) {\n\t\tbubbleColor = color\n\n\t\tif (bubbleImage == null) {\n\t\t\tbubbleImage = ContextCompat.getDrawable(context, bubbleSize.drawableId)\n\t\t}\n\n\t\tbubbleImage?.let {\n\t\t\tit.setTint(bubbleColor)\n\t\t\tbinding.bubble.background = it\n\t\t}\n\t}\n\n\t/**\n\t * Set the text color of the section bubble.\n\t *\n\t * @param color The text color for the section bubble\n\t */\n\tfun setBubbleTextColor(@ColorInt color: Int) = binding.bubble.setTextColor(color)\n\n\t/**\n\t * Set the scaled pixel text size of the section bubble.\n\t *\n\t * @param size The scaled pixel text size for the section bubble\n\t */\n\tfun setBubbleTextSize(size: Int) {\n\t\tbinding.bubble.textSize = size.toFloat()\n\t}\n\n\tprivate fun getRecyclerViewTargetPosition(y: Float) = recyclerView?.let { recyclerView ->\n\t\tval itemCount = recyclerView.adapter?.itemCount ?: 0\n\n\t\tval proportion = when {\n\t\t\tbinding.thumb.y == 0f -> 0f\n\t\t\tbinding.thumb.y + handleHeight >= viewHeight - TRACK_SNAP_RANGE -> 1f\n\t\t\telse -> y / viewHeight.toFloat()\n\t\t}\n\n\t\tvar scrolledItemCount = (proportion * itemCount).roundToInt()\n\n\t\tif (recyclerView.layoutManager.isLayoutReversed) {\n\t\t\tscrolledItemCount = itemCount - scrolledItemCount\n\t\t}\n\n\t\tif (itemCount > 0) scrolledItemCount.coerceIn(0, itemCount - 1) else 0\n\t} ?: 0\n\n\tprivate fun setRecyclerViewPosition(y: Float) {\n\t\tval layoutManager = recyclerView?.layoutManager ?: return\n\t\tval targetPos = getRecyclerViewTargetPosition(y)\n\t\tlayoutManager.scrollToPosition(targetPos)\n\t\tif (showBubble) sectionIndexer?.let { bindBubble(it.getSectionText(context, targetPos)) }\n\t}\n\n\tprivate fun setViewPositions(y: Float) {\n\t\tbubbleHeight = binding.bubble.measuredHeight\n\t\thandleHeight = binding.thumb.measuredHeight\n\n\t\tval bubbleHandleHeight = bubbleHeight + handleHeight / 2f\n\n\t\tif (showBubble && viewHeight >= bubbleHandleHeight) {\n\t\t\tbinding.bubble.y = (y - bubbleHeight).coerceIn(0f, viewHeight - bubbleHandleHeight)\n\t\t}\n\n\t\tif (viewHeight >= handleHeight) {\n\t\t\tbinding.thumb.y = (y - handleHeight / 2).coerceIn(0f, viewHeight - handleHeight.toFloat())\n\t\t}\n\t}\n\n\tprivate fun updateViewHeights() {\n\t\tval measureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)\n\t\tbinding.bubble.measure(measureSpec, measureSpec)\n\t\tbubbleHeight = binding.bubble.measuredHeight\n\t\tbinding.thumb.measure(measureSpec, measureSpec)\n\t\thandleHeight = binding.thumb.measuredHeight\n\t}\n\n\tprivate fun showBubble() {\n\t\tbubbleAnimator.show()\n\t}\n\n\tprivate fun hideBubble() {\n\t\tbubbleAnimator.hide()\n\t}\n\n\tprivate fun showScrollbar() {\n\t\tif (recyclerView?.run { canScrollVertically(1) || canScrollVertically(-1) } == true) {\n\t\t\tscrollbarAnimator.show()\n\t\t}\n\t}\n\n\tprivate fun hideScrollbar() {\n\t\tscrollbarAnimator.hide()\n\t}\n\n\tprivate fun setHandleSelected(selected: Boolean) {\n\t\tbinding.thumb.isSelected = selected\n\t\thandleImage?.setTint(if (selected) bubbleColor else handleColor)\n\t}\n\n\tprivate fun TypedArray.getBubbleSize(@StyleableRes index: Int, defaultValue: BubbleSize): BubbleSize {\n\t\tval ordinal = getInt(index, -1)\n\t\treturn BubbleSize.entries.getOrNull(ordinal) ?: defaultValue\n\t}\n\n\tprivate fun findValidParent(view: View): ViewGroup? = view.ancestors.firstNotNullOfOrNull { p ->\n\t\tif (p is FrameLayout || p is ConstraintLayout || p is CoordinatorLayout || p is RelativeLayout) {\n\t\t\tp\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate fun bindBubble(text: CharSequence?) {\n\t\tbinding.bubble.text = text\n\t\tbinding.bubble.alpha = if (text.isNullOrEmpty()) 0f else 1f\n\t}\n\n\tprivate val BubbleSize.textSize\n\t\t@Px get() = resources.getDimension(textSizeId)\n\n\tinterface FastScrollListener {\n\n\t\tfun onFastScrollStart(fastScroller: FastScroller)\n\n\t\tfun onFastScrollStop(fastScroller: FastScroller)\n\t}\n\n\tinterface SectionIndexer {\n\n\t\tfun getSectionText(context: Context, position: Int): CharSequence?\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/fastscroll/ScrollbarAnimator.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.fastscroll\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.view.View\nimport android.view.ViewPropertyAnimator\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.animatorDurationScale\n\nclass ScrollbarAnimator(\n\tprivate val scrollbar: View,\n\tprivate val scrollbarPaddingEnd: Float,\n) {\n\n\tprivate val animationDuration = (\n\t\tscrollbar.resources.getInteger(R.integer.config_defaultAnimTime) *\n\t\t\tscrollbar.context.animatorDurationScale\n\t\t).toLong()\n\tprivate var animator: ViewPropertyAnimator? = null\n\tprivate var isHiding = false\n\n\tfun show() {\n\t\tif (scrollbar.isVisible && !isHiding) {\n\t\t\treturn\n\t\t}\n\t\tisHiding = false\n\t\tanimator?.cancel()\n\t\tscrollbar.translationX = scrollbarPaddingEnd\n\t\tscrollbar.isVisible = true\n\t\tanimator = scrollbar\n\t\t\t.animate()\n\t\t\t.translationX(0f)\n\t\t\t.alpha(1f)\n\t\t\t.setListener(null)\n\t\t\t.setDuration(animationDuration)\n\t}\n\n\tfun hide() {\n\t\tif (!scrollbar.isVisible || isHiding) {\n\t\t\treturn\n\t\t}\n\t\tanimator?.cancel()\n\t\tisHiding = true\n\t\tanimator = scrollbar.animate().apply {\n\t\t\ttranslationX(scrollbarPaddingEnd)\n\t\t\talpha(0f)\n\t\t\tduration = animationDuration\n\t\t\tsetListener(HideListener(this))\n\t\t}\n\t}\n\n\tprivate inner class HideListener(\n\t\tprivate val viewPropertyAnimator: ViewPropertyAnimator,\n\t) : AnimatorListenerAdapter() {\n\n\t\tprivate var isCancelled = false\n\n\t\toverride fun onAnimationCancel(animation: Animator) {\n\t\t\tsuper.onAnimationCancel(animation)\n\t\t\tisCancelled = true\n\t\t}\n\n\t\toverride fun onAnimationEnd(animation: Animator) {\n\t\t\tsuper.onAnimationEnd(animation)\n\t\t\tif (!isCancelled && this@ScrollbarAnimator.animator === viewPropertyAnimator) {\n\t\t\t\tscrollbar.isInvisible = true\n\t\t\t\tisHiding = false\n\t\t\t\tthis@ScrollbarAnimator.animator = null\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/lifecycle/LifecycleAwareViewHolder.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.lifecycle\n\nimport android.view.View\nimport androidx.annotation.CallSuper\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.LifecycleRegistry\nimport androidx.recyclerview.widget.RecyclerView\n\nabstract class LifecycleAwareViewHolder(\n\titemView: View,\n\tprivate val parentLifecycleOwner: LifecycleOwner,\n) : RecyclerView.ViewHolder(itemView), LifecycleOwner {\n\n\t@Suppress(\"LeakingThis\")\n\tfinal override val lifecycle = LifecycleRegistry(this)\n\tprivate var isCurrent = false\n\n\tinit {\n\t\titemView.post {\n\t\t\tparentLifecycleOwner.lifecycle.addObserver(ParentLifecycleObserver())\n\t\t}\n\t}\n\n\tfun setIsCurrent(value: Boolean) {\n\t\tisCurrent = value\n\t\tdispatchResumed()\n\t}\n\n\t@CallSuper\n\topen fun onCreate() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)\n\n\t@CallSuper\n\topen fun onStart() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)\n\n\t@CallSuper\n\topen fun onResume() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)\n\n\t@CallSuper\n\topen fun onPause() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)\n\n\t@CallSuper\n\topen fun onStop() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)\n\n\t@CallSuper\n\topen fun onDestroy() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)\n\n\tprivate fun dispatchResumed() {\n\t\tval isParentResumed = parentLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)\n\t\tif (isCurrent && isParentResumed) {\n\t\t\tif (!isResumed()) {\n\t\t\t\tonResume()\n\t\t\t}\n\t\t} else {\n\t\t\tif (isResumed()) {\n\t\t\t\tonPause()\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected fun isResumed(): Boolean {\n\t\treturn lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)\n\t}\n\n\tprivate inner class ParentLifecycleObserver : DefaultLifecycleObserver {\n\n\t\toverride fun onCreate(owner: LifecycleOwner) = this@LifecycleAwareViewHolder.onCreate()\n\n\t\toverride fun onStart(owner: LifecycleOwner) = this@LifecycleAwareViewHolder.onStart()\n\n\t\toverride fun onResume(owner: LifecycleOwner) = this@LifecycleAwareViewHolder.dispatchResumed()\n\n\t\toverride fun onPause(owner: LifecycleOwner) = this@LifecycleAwareViewHolder.dispatchResumed()\n\n\t\toverride fun onStop(owner: LifecycleOwner) = this@LifecycleAwareViewHolder.onStop()\n\n\t\toverride fun onDestroy(owner: LifecycleOwner) {\n\t\t\tthis@LifecycleAwareViewHolder.onDestroy()\n\t\t\towner.lifecycle.removeObserver(this)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/lifecycle/PagerLifecycleDispatcher.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.lifecycle\n\nimport android.view.View\nimport androidx.core.view.children\nimport androidx.core.view.isEmpty\nimport androidx.viewpager2.widget.ViewPager2\nimport org.koitharu.kotatsu.core.util.ext.recyclerView\n\nclass PagerLifecycleDispatcher(\n\tprivate val pager: ViewPager2,\n) : ViewPager2.OnPageChangeCallback() {\n\n\tprivate var pendingUpdate: OneShotLayoutListener? = null\n\n\toverride fun onPageSelected(position: Int) {\n\t\tsetResumedPage(position)\n\t}\n\n\tfun invalidate() {\n\t\tsetResumedPage(pager.currentItem)\n\t}\n\n\tfun postInvalidate() = pager.post {\n\t\tinvalidate()\n\t}\n\n\tprivate fun setResumedPage(position: Int) {\n\t\tpendingUpdate?.cancel()\n\t\tpendingUpdate = null\n\t\tvar hasResumedItem = false\n\t\tval rv = pager.recyclerView ?: return\n\t\tif (rv.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tfor (child in rv.children) {\n\t\t\tval wh = rv.getChildViewHolder(child) ?: continue\n\t\t\tval isCurrent = wh.absoluteAdapterPosition == position\n\t\t\t(wh as? LifecycleAwareViewHolder)?.setIsCurrent(isCurrent)\n\t\t\tif (isCurrent) {\n\t\t\t\thasResumedItem = true\n\t\t\t}\n\t\t}\n\t\tif (!hasResumedItem) {\n\t\t\trv.addOnLayoutChangeListener(OneShotLayoutListener(rv, position).also { pendingUpdate = it })\n\t\t}\n\t}\n\n\tprivate inner class OneShotLayoutListener(\n\t\tprivate val view: View,\n\t\tprivate val targetPosition: Int,\n\t) : View.OnLayoutChangeListener {\n\n\t\toverride fun onLayoutChange(\n\t\t\tv: View?,\n\t\t\tleft: Int,\n\t\t\ttop: Int,\n\t\t\tright: Int,\n\t\t\tbottom: Int,\n\t\t\toldLeft: Int,\n\t\t\toldTop: Int,\n\t\t\toldRight: Int,\n\t\t\toldBottom: Int\n\t\t) {\n\t\t\tview.removeOnLayoutChangeListener(this)\n\t\t\tsetResumedPage(targetPosition)\n\t\t}\n\n\t\tfun cancel() {\n\t\t\tview.removeOnLayoutChangeListener(this)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/list/lifecycle/RecyclerViewLifecycleDispatcher.kt",
    "content": "package org.koitharu.kotatsu.core.ui.list.lifecycle\n\nimport androidx.core.view.children\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.NO_POSITION\n\nclass RecyclerViewLifecycleDispatcher : RecyclerView.OnScrollListener() {\n\n\tprivate var prevFirst = NO_POSITION\n\tprivate var prevLast = NO_POSITION\n\n\toverride fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n\t\tsuper.onScrolled(recyclerView, dx, dy)\n\t\tinvalidate(recyclerView)\n\t}\n\n\tfun invalidate(recyclerView: RecyclerView) {\n\t\tval lm = recyclerView.layoutManager as? LinearLayoutManager ?: return\n\t\tval first = lm.findFirstVisibleItemPosition()\n\t\tval last = lm.findLastVisibleItemPosition()\n\t\tif (first == prevFirst && last == prevLast) {\n\t\t\treturn\n\t\t}\n\t\tprevFirst = first\n\t\tprevLast = last\n\t\tif (first == NO_POSITION || last == NO_POSITION) {\n\t\t\treturn\n\t\t}\n\t\tfor (child in recyclerView.children) {\n\t\t\tval wh = recyclerView.getChildViewHolder(child) ?: continue\n\t\t\t(wh as? LifecycleAwareViewHolder)?.setIsCurrent(wh.absoluteAdapterPosition in first..last)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/DateTimeAgo.kt",
    "content": "package org.koitharu.kotatsu.core.ui.model\n\nimport android.content.Context\nimport android.text.format.DateUtils\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.toMillis\nimport java.time.LocalDate\n\nsealed class DateTimeAgo {\n\n\tabstract fun format(context: Context): String\n\n\tobject JustNow : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn context.getString(R.string.just_now)\n\t\t}\n\n\t\toverride fun toString() = \"just_now\"\n\n\t\toverride fun equals(other: Any?): Boolean = other === JustNow\n\t}\n\n\tdata class MinutesAgo(val minutes: Int) : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn context.resources.getQuantityStringSafe(\n\t\t\t\tR.plurals.minutes_ago,\n\t\t\t\tminutes,\n\t\t\t\tminutes,\n\t\t\t)\n\t\t}\n\n\t\toverride fun toString() = \"minutes_ago_$minutes\"\n\t}\n\n\tdata class HoursAgo(val hours: Int) : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn context.resources.getQuantityStringSafe(\n\t\t\t\tR.plurals.hours_ago,\n\t\t\t\thours,\n\t\t\t\thours,\n\t\t\t)\n\t\t}\n\n\t\toverride fun toString() = \"hours_ago_$hours\"\n\t}\n\n\tobject Today : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn context.getString(R.string.today)\n\t\t}\n\n\t\toverride fun toString() = \"today\"\n\n\t\toverride fun equals(other: Any?): Boolean = other === Today\n\t}\n\n\tobject Yesterday : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn context.getString(R.string.yesterday)\n\t\t}\n\n\t\toverride fun toString() = \"yesterday\"\n\n\t\toverride fun equals(other: Any?): Boolean = other === Yesterday\n\t}\n\n\tdata class DaysAgo(val days: Int) : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn context.resources.getQuantityStringSafe(R.plurals.days_ago, days, days)\n\t\t}\n\n\t\toverride fun toString() = \"days_ago_$days\"\n\t}\n\n\tdata class MonthsAgo(val months: Int) : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn if (months == 0) {\n\t\t\t\tcontext.getString(R.string.this_month)\n\t\t\t} else {\n\t\t\t\tcontext.resources.getQuantityStringSafe(\n\t\t\t\t\tR.plurals.months_ago,\n\t\t\t\t\tmonths,\n\t\t\t\t\tmonths,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tdata class Absolute(private val date: LocalDate) : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn if (date == EPOCH_DATE) {\n\t\t\t\tcontext.getString(R.string.unknown)\n\t\t\t} else {\n\t\t\t\tDateUtils.formatDateTime(context, date.toMillis(), DateUtils.FORMAT_SHOW_DATE)\n\t\t\t}\n\t\t}\n\n\t\toverride fun toString() = \"abs_${date.toEpochDay()}\"\n\n\t\tprivate companion object {\n\t\t\tval EPOCH_DATE: LocalDate = LocalDate.of(1970, 1, 1)\n\t\t}\n\t}\n\n\tobject LongAgo : DateTimeAgo() {\n\t\toverride fun format(context: Context): String {\n\t\t\treturn context.getString(R.string.long_ago)\n\t\t}\n\n\t\toverride fun toString() = \"long_ago\"\n\n\t\toverride fun equals(other: Any?): Boolean = other === LongAgo\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/MangaOverride.kt",
    "content": "package org.koitharu.kotatsu.core.ui.model\n\nimport org.koitharu.kotatsu.parsers.model.ContentRating\n\ndata class MangaOverride(\n\tval coverUrl: String?,\n\tval title: String?,\n\tval contentRating: ContentRating?,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/SortOrder.kt",
    "content": "package org.koitharu.kotatsu.core.ui.model\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.SortDirection\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.model.SortOrder.ADDED\nimport org.koitharu.kotatsu.parsers.model.SortOrder.ADDED_ASC\nimport org.koitharu.kotatsu.parsers.model.SortOrder.ALPHABETICAL\nimport org.koitharu.kotatsu.parsers.model.SortOrder.ALPHABETICAL_DESC\nimport org.koitharu.kotatsu.parsers.model.SortOrder.NEWEST\nimport org.koitharu.kotatsu.parsers.model.SortOrder.NEWEST_ASC\nimport org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY\nimport org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY_ASC\nimport org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY_HOUR\nimport org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY_MONTH\nimport org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY_TODAY\nimport org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY_WEEK\nimport org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY_YEAR\nimport org.koitharu.kotatsu.parsers.model.SortOrder.RATING\nimport org.koitharu.kotatsu.parsers.model.SortOrder.RATING_ASC\nimport org.koitharu.kotatsu.parsers.model.SortOrder.RELEVANCE\nimport org.koitharu.kotatsu.parsers.model.SortOrder.UPDATED\nimport org.koitharu.kotatsu.parsers.model.SortOrder.UPDATED_ASC\n\n@get:StringRes\nval SortOrder.titleRes: Int\n\tget() = when (this) {\n\t\tUPDATED -> R.string.updated\n\t\tPOPULARITY -> R.string.popular\n\t\tRATING -> R.string.by_rating\n\t\tNEWEST -> R.string.newest\n\t\tALPHABETICAL -> R.string.by_name\n\t\tALPHABETICAL_DESC -> R.string.by_name_reverse\n\t\tUPDATED_ASC -> R.string.updated_long_ago\n\t\tPOPULARITY_ASC -> R.string.unpopular\n\t\tRATING_ASC -> R.string.low_rating\n\t\tNEWEST_ASC -> R.string.order_oldest\n\t\tADDED -> R.string.recently_added\n\t\tADDED_ASC -> R.string.added_long_ago\n\t\tRELEVANCE -> R.string.by_relevance\n\t\tPOPULARITY_HOUR -> R.string.popular_in_hour\n\t\tPOPULARITY_TODAY -> R.string.popular_today\n\t\tPOPULARITY_WEEK -> R.string.popular_in_week\n\t\tPOPULARITY_MONTH -> R.string.popular_in_month\n\t\tPOPULARITY_YEAR -> R.string.popular_in_year\n\t}\n\nval SortOrder.direction: SortDirection\n\tget() = when (this) {\n\t\tUPDATED_ASC,\n\t\tPOPULARITY_ASC,\n\t\tRATING_ASC,\n\t\tNEWEST_ASC,\n\t\tADDED_ASC,\n\t\tALPHABETICAL -> SortDirection.ASC\n\n\t\tUPDATED,\n\t\tPOPULARITY,\n\t\tPOPULARITY_HOUR,\n\t\tPOPULARITY_TODAY,\n\t\tPOPULARITY_WEEK,\n\t\tPOPULARITY_MONTH,\n\t\tPOPULARITY_YEAR,\n\t\tRATING,\n\t\tNEWEST,\n\t\tADDED,\n\t\tRELEVANCE,\n\t\tALPHABETICAL_DESC -> SortDirection.DESC\n\t}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/AdaptiveSheetBehavior.kt",
    "content": "package org.koitharu.kotatsu.core.ui.sheet\n\nimport android.app.Dialog\nimport android.view.View\nimport android.view.ViewParent\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.ancestors\nimport androidx.fragment.app.DialogFragment\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.sidesheet.SideSheetBehavior\nimport com.google.android.material.sidesheet.SideSheetCallback\nimport com.google.android.material.sidesheet.SideSheetDialog\nimport java.util.LinkedList\n\nsealed class AdaptiveSheetBehavior {\n\n\t@JvmField\n\tprotected val callbacks = LinkedList<AdaptiveSheetCallback>()\n\n\tabstract var state: Int\n\n\tabstract var isDraggable: Boolean\n\n\topen val isHideable: Boolean = true\n\n\tfun addCallback(callback: AdaptiveSheetCallback) {\n\t\tcallbacks.add(callback)\n\t}\n\n\tfun removeCallback(callback: AdaptiveSheetCallback) {\n\t\tcallbacks.remove(callback)\n\t}\n\n\tclass Bottom(\n\t\tprivate val delegate: BottomSheetBehavior<*>,\n\t) : AdaptiveSheetBehavior() {\n\n\t\toverride var state: Int\n\t\t\tget() = delegate.state\n\t\t\tset(value) {\n\t\t\t\tdelegate.state = value\n\t\t\t}\n\n\t\toverride var isDraggable: Boolean\n\t\t\tget() = delegate.isDraggable\n\t\t\tset(value) {\n\t\t\t\tdelegate.isDraggable = value\n\t\t\t}\n\n\t\toverride val isHideable: Boolean\n\t\t\tget() = delegate.isHideable\n\n\t\tvar isFitToContents: Boolean\n\t\t\tget() = delegate.isFitToContents\n\t\t\tset(value) {\n\t\t\t\tdelegate.isFitToContents = value\n\t\t\t}\n\n\t\tinit {\n\t\t\tdelegate.addBottomSheetCallback(\n\t\t\t\tobject : BottomSheetCallback() {\n\t\t\t\t\toverride fun onStateChanged(bottomSheet: View, newState: Int) {\n\t\t\t\t\t\tcallbacks.forEach { it.onStateChanged(bottomSheet, newState) }\n\t\t\t\t\t}\n\n\t\t\t\t\toverride fun onSlide(bottomSheet: View, slideOffset: Float) {\n\t\t\t\t\t\tcallbacks.forEach { it.onSlide(bottomSheet, slideOffset) }\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\tclass Side(\n\t\tprivate val delegate: SideSheetBehavior<*>,\n\t) : AdaptiveSheetBehavior() {\n\n\t\toverride var state: Int\n\t\t\tget() = delegate.state\n\t\t\tset(value) {\n\t\t\t\tdelegate.state = value\n\t\t\t}\n\n\t\toverride var isDraggable: Boolean\n\t\t\tget() = delegate.isDraggable\n\t\t\tset(value) {\n\t\t\t\tdelegate.isDraggable = value\n\t\t\t}\n\n\t\tinit {\n\t\t\tdelegate.addCallback(\n\t\t\t\tobject : SideSheetCallback() {\n\t\t\t\t\toverride fun onStateChanged(sheet: View, newState: Int) {\n\t\t\t\t\t\tcallbacks.forEach { it.onStateChanged(sheet, newState) }\n\t\t\t\t\t}\n\n\t\t\t\t\toverride fun onSlide(sheet: View, slideOffset: Float) {\n\t\t\t\t\t\tcallbacks.forEach { it.onSlide(sheet, slideOffset) }\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tconst val STATE_EXPANDED = SideSheetBehavior.STATE_EXPANDED\n\t\tconst val STATE_COLLAPSED = BottomSheetBehavior.STATE_COLLAPSED\n\t\tconst val STATE_SETTLING = SideSheetBehavior.STATE_SETTLING\n\t\tconst val STATE_DRAGGING = SideSheetBehavior.STATE_DRAGGING\n\t\tconst val STATE_HIDDEN = SideSheetBehavior.STATE_HIDDEN\n\n\t\tfun from(fragment: DialogFragment): AdaptiveSheetBehavior? {\n\t\t\tfrom(fragment.dialog)?.let { return it }\n\t\t\tval rootView = fragment.view ?: return null\n\t\t\tfor (parent in rootView.ancestors) {\n\t\t\t\tfrom(parent)?.let { return it }\n\t\t\t}\n\t\t\treturn null\n\t\t}\n\n\t\tprivate fun from(dialog: Dialog?): AdaptiveSheetBehavior? = when (dialog) {\n\t\t\tis BottomSheetDialog -> Bottom(dialog.behavior)\n\t\t\tis SideSheetDialog -> Side(dialog.behavior)\n\t\t\telse -> null\n\t\t}\n\n\t\tfun from(lp: CoordinatorLayout.LayoutParams): AdaptiveSheetBehavior? =\n\t\t\twhen (val behavior = lp.behavior) {\n\t\t\t\tis BottomSheetBehavior<*> -> Bottom(behavior)\n\t\t\t\tis SideSheetBehavior<*> -> Side(behavior)\n\t\t\t\telse -> null\n\t\t\t}\n\n\t\tprivate fun from(parent: ViewParent): AdaptiveSheetBehavior? {\n\t\t\tval lp = ((parent as? View)?.layoutParams as? CoordinatorLayout.LayoutParams) ?: return null\n\t\t\treturn from(lp)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/AdaptiveSheetCallback.kt",
    "content": "package org.koitharu.kotatsu.core.ui.sheet\n\nimport android.view.View\n\ninterface AdaptiveSheetCallback {\n\n\t/**\n\t * Called when the sheet changes its state.\n\t *\n\t * @param sheet The sheet view.\n\t * @param newState The new state.\n\t */\n\tfun onStateChanged(sheet: View, newState: Int)\n\n\t/**\n\t * Called when the sheet is being dragged.\n\t *\n\t * @param sheet The sheet view.\n\t * @param slideOffset The new offset of this sheet.\n\t */\n\tfun onSlide(sheet: View, slideOffset: Float) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/AdaptiveSheetHeaderBar.kt",
    "content": "package org.koitharu.kotatsu.core.ui.sheet\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.InputDevice\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.View\nimport android.widget.LinearLayout\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StringRes\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.view.ancestors\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.databinding.LayoutSheetHeaderAdaptiveBinding\n\nclass AdaptiveSheetHeaderBar @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : LinearLayout(context, attrs, defStyleAttr), AdaptiveSheetCallback {\n\n\tprivate val binding =\n\t\tLayoutSheetHeaderAdaptiveBinding.inflate(LayoutInflater.from(context), this)\n\tprivate var sheetBehavior: AdaptiveSheetBehavior? = null\n\n\tvar title: CharSequence?\n\t\tget() = binding.shTextViewTitle.text\n\t\tset(value) {\n\t\t\tbinding.shTextViewTitle.text = value\n\t\t}\n\n\tval isTitleVisible: Boolean\n\t\tget() = binding.shLayoutSidesheet.isVisible\n\n\tinit {\n\t\torientation = VERTICAL\n\t\tbinding.shButtonClose.setOnClickListener { dismissSheet() }\n\t\tcontext.withStyledAttributes(\n\t\t\tattrs,\n\t\t\tR.styleable.AdaptiveSheetHeaderBar, defStyleAttr,\n\t\t) {\n\t\t\ttitle = getText(R.styleable.AdaptiveSheetHeaderBar_title)\n\t\t}\n\t}\n\n\toverride fun onAttachedToWindow() {\n\t\tsuper.onAttachedToWindow()\n\t\tif (isInEditMode) {\n\t\t\tval isTabled = resources.getBoolean(R.bool.is_tablet)\n\t\t\tbinding.shDragHandle.isGone = isTabled\n\t\t\tbinding.shLayoutSidesheet.isVisible = isTabled\n\t\t} else {\n\t\t\tsetBottomSheetBehavior(findParentSheetBehavior())\n\t\t}\n\t}\n\n\toverride fun onDetachedFromWindow() {\n\t\tsetBottomSheetBehavior(null)\n\t\tsuper.onDetachedFromWindow()\n\t}\n\n\toverride fun onGenericMotionEvent(event: MotionEvent): Boolean {\n\t\tval behavior = sheetBehavior ?: return super.onGenericMotionEvent(event)\n\t\tif (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {\n\t\t\tif (event.actionMasked == MotionEvent.ACTION_SCROLL) {\n\t\t\t\tif (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0f) {\n\t\t\t\t\tbehavior.state = if (\n\t\t\t\t\t\tbehavior is AdaptiveSheetBehavior.Bottom\n\t\t\t\t\t\t&& behavior.state == AdaptiveSheetBehavior.STATE_EXPANDED\n\t\t\t\t\t) {\n\t\t\t\t\t\tAdaptiveSheetBehavior.STATE_COLLAPSED\n\t\t\t\t\t} else {\n\t\t\t\t\t\tAdaptiveSheetBehavior.STATE_HIDDEN\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tbehavior.state = AdaptiveSheetBehavior.STATE_EXPANDED\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn super.onGenericMotionEvent(event)\n\t}\n\n\toverride fun onStateChanged(sheet: View, newState: Int) {\n\n\t}\n\n\tfun setTitle(@StringRes resId: Int) {\n\t\tbinding.shTextViewTitle.setText(resId)\n\t}\n\n\tprivate fun setBottomSheetBehavior(behavior: AdaptiveSheetBehavior?) {\n\t\tbinding.shDragHandle.isVisible = behavior is AdaptiveSheetBehavior.Bottom\n\t\tbinding.shLayoutSidesheet.isVisible = behavior is AdaptiveSheetBehavior.Side\n\t\tsheetBehavior?.removeCallback(this)\n\t\tsheetBehavior = behavior\n\t\tbehavior?.addCallback(this)\n\t}\n\n\tprivate fun dismissSheet() {\n\t\tsheetBehavior?.state = AdaptiveSheetBehavior.STATE_HIDDEN\n\t}\n\n\tprivate fun findParentSheetBehavior(): AdaptiveSheetBehavior? {\n\t\treturn ancestors.firstNotNullOfOrNull {\n\t\t\t((it as? View)?.layoutParams as? CoordinatorLayout.LayoutParams)\n\t\t\t\t?.let { params -> AdaptiveSheetBehavior.from(params) }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/BaseAdaptiveSheet.kt",
    "content": "package org.koitharu.kotatsu.core.ui.sheet\n\nimport android.app.Dialog\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewGroup.LayoutParams\nimport androidx.activity.ComponentDialog\nimport androidx.activity.OnBackPressedDispatcher\nimport androidx.annotation.CallSuper\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.app.AppCompatDialog\nimport androidx.appcompat.app.AppCompatDialogFragment\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.OnApplyWindowInsetsListener\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.viewbinding.ViewBinding\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetDialog\nimport com.google.android.material.sidesheet.SideSheetDialog\nimport dagger.hilt.android.EntryPointAccessors\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.BaseActivityEntryPoint\nimport org.koitharu.kotatsu.core.ui.util.ActionModeDelegate\nimport com.google.android.material.R as materialR\n\nabstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment(),\n\tOnApplyWindowInsetsListener {\n\n\tprivate var waitingForDismissAllowingStateLoss = false\n\tprivate var isFitToContentsDisabled = false\n\n\tprotected lateinit var exceptionResolver: ExceptionResolver\n\t\tprivate set\n\n\tvar viewBinding: B? = null\n\t\tprivate set\n\n\tprotected val behavior: AdaptiveSheetBehavior?\n\t\tget() = AdaptiveSheetBehavior.from(this)\n\n\tvar actionModeDelegate: ActionModeDelegate? = null\n\t\tprivate set\n\n\tval isExpanded: Boolean\n\t\tget() = behavior?.state == AdaptiveSheetBehavior.STATE_EXPANDED\n\n\tval onBackPressedDispatcher: OnBackPressedDispatcher\n\t\tget() = (dialog as? ComponentDialog)?.onBackPressedDispatcher ?: requireActivity().onBackPressedDispatcher\n\n\tvar isLocked = false\n\t\tprivate set\n\tprivate var lockCounter = 0\n\n\toverride fun onAttach(context: Context) {\n\t\tsuper.onAttach(context)\n\t\tval entryPoint = EntryPointAccessors.fromApplication<BaseActivityEntryPoint>(context)\n\t\texceptionResolver = entryPoint.exceptionResolverFactory.create(this)\n\t}\n\n\tfinal override fun onCreateView(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t\tsavedInstanceState: Bundle?,\n\t): View {\n\t\tval binding = onCreateViewBinding(inflater, container)\n\t\tviewBinding = binding\n\t\treturn binding.root\n\t}\n\n\tfinal override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tViewCompat.setOnApplyWindowInsetsListener(view, this)\n\t\tval binding = requireViewBinding()\n\t\tif (actionModeDelegate == null) {\n\t\t\tactionModeDelegate = (activity as? BaseActivity<*>)?.actionModeDelegate\n\t\t}\n\t\tonViewBindingCreated(binding, savedInstanceState)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tviewBinding = null\n\t\tactionModeDelegate = null\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n\t\tval context = requireContext()\n\t\tval dialog = if (context.resources.getBoolean(R.bool.is_tablet)) {\n\t\t\tSideSheetDialogImpl(context, theme)\n\t\t} else {\n\t\t\tBottomSheetDialogImpl(context, theme)\n\t\t}\n\t\tactionModeDelegate = ActionModeDelegate().also {\n\t\t\tdialog.onBackPressedDispatcher.addCallback(it)\n\t\t}\n\t\treturn dialog\n\t}\n\n\t@CallSuper\n\tprotected open fun dispatchSupportActionModeStarted(mode: ActionMode) {\n\t\tactionModeDelegate?.onSupportActionModeStarted(mode, dialog?.window)\n\t}\n\n\t@CallSuper\n\tprotected open fun dispatchSupportActionModeFinished(mode: ActionMode) {\n\t\tactionModeDelegate?.onSupportActionModeFinished(mode, dialog?.window)\n\t}\n\n\tfun addSheetCallback(callback: AdaptiveSheetCallback, lifecycleOwner: LifecycleOwner): Boolean {\n\t\tval b = behavior ?: return false\n\t\tb.addCallback(callback)\n\t\tval rootView = dialog?.findViewById(materialR.id.design_bottom_sheet)\n\t\t\t?: dialog?.findViewById(materialR.id.coordinator)\n\t\t\t?: view\n\t\tif (rootView != null) {\n\t\t\tcallback.onStateChanged(rootView, b.state)\n\t\t}\n\t\tlifecycleOwner.lifecycle.addObserver(CallbackRemoveObserver(b, callback))\n\t\treturn true\n\t}\n\n\tprotected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B\n\n\tprotected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit\n\n\tfun startSupportActionMode(callback: ActionMode.Callback): ActionMode? {\n\t\tval delegate =\n\t\t\t(dialog as? AppCompatDialog)?.delegate ?: (activity as? AppCompatActivity)?.delegate ?: return null\n\t\treturn delegate.startSupportActionMode(callback)\n\t}\n\n\tprotected fun setExpanded(isExpanded: Boolean, isLocked: Boolean) {\n\t\tthis.isLocked = isLocked\n\t\tif (!isLocked) {\n\t\t\tlockCounter = 0\n\t\t}\n\t\tval b = behavior ?: return\n\t\tif (isExpanded) {\n\t\t\tb.state = BottomSheetBehavior.STATE_EXPANDED\n\t\t}\n\t\tif (b is AdaptiveSheetBehavior.Bottom) {\n\t\t\tb.isFitToContents = !isFitToContentsDisabled && !isExpanded\n\t\t\tval rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet)\n\t\t\trootView?.updateLayoutParams {\n\t\t\t\theight = if (isFitToContentsDisabled || isExpanded) {\n\t\t\t\t\tLayoutParams.MATCH_PARENT\n\t\t\t\t} else {\n\t\t\t\t\tLayoutParams.WRAP_CONTENT\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tb.isDraggable = !isLocked\n\t}\n\n\tprotected fun disableFitToContents() {\n\t\tisFitToContentsDisabled = true\n\t\tval b = behavior as? AdaptiveSheetBehavior.Bottom ?: return\n\t\tb.isFitToContents = false\n\t\tdialog?.findViewById<View>(materialR.id.design_bottom_sheet)?.updateLayoutParams {\n\t\t\theight = LayoutParams.MATCH_PARENT\n\t\t}\n\t}\n\n\t@CallSuper\n\topen fun expandAndLock() {\n\t\tlockCounter++\n\t\tsetExpanded(isExpanded = true, isLocked = true)\n\t}\n\n\t@CallSuper\n\topen fun unlock() {\n\t\tlockCounter--\n\t\tif (lockCounter <= 0) {\n\t\t\tsetExpanded(isExpanded, false)\n\t\t}\n\t}\n\n\tfun requireViewBinding(): B = checkNotNull(viewBinding) {\n\t\t\"Fragment $this did not return a ViewBinding from onCreateView() or this was called before onCreateView().\"\n\t}\n\n\toverride fun dismiss() {\n\t\tif (!tryDismissWithAnimation(false)) {\n\t\t\tsuper.dismiss()\n\t\t}\n\t}\n\n\toverride fun dismissAllowingStateLoss() {\n\t\tif (!tryDismissWithAnimation(true)) {\n\t\t\tsuper.dismissAllowingStateLoss()\n\t\t}\n\t}\n\n\t/**\n\t * Tries to dismiss the dialog fragment with the bottom sheet animation. Returns true if possible,\n\t * false otherwise.\n\t */\n\tprivate fun tryDismissWithAnimation(allowingStateLoss: Boolean): Boolean {\n\t\tval shouldDismissWithAnimation = when (val dialog = dialog) {\n\t\t\tis BottomSheetDialog -> dialog.dismissWithAnimation\n\t\t\tis SideSheetDialog -> dialog.isDismissWithSheetAnimationEnabled\n\t\t\telse -> false\n\t\t}\n\t\tval behavior = behavior ?: return false\n\t\treturn if (shouldDismissWithAnimation && behavior.isHideable) {\n\t\t\tdismissWithAnimation(behavior, allowingStateLoss)\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tprivate fun dismissWithAnimation(behavior: AdaptiveSheetBehavior, allowingStateLoss: Boolean) {\n\t\twaitingForDismissAllowingStateLoss = allowingStateLoss\n\t\tif (behavior.state == AdaptiveSheetBehavior.STATE_HIDDEN) {\n\t\t\tdismissAfterAnimation()\n\t\t} else {\n\t\t\tbehavior.addCallback(SheetDismissCallback())\n\t\t\tbehavior.state = AdaptiveSheetBehavior.STATE_HIDDEN\n\t\t}\n\t}\n\n\tprivate fun dismissAfterAnimation() {\n\t\tif (waitingForDismissAllowingStateLoss) {\n\t\t\tsuper.dismissAllowingStateLoss()\n\t\t} else {\n\t\t\tsuper.dismiss()\n\t\t}\n\t}\n\n\tprivate inner class SheetDismissCallback : AdaptiveSheetCallback {\n\t\toverride fun onStateChanged(sheet: View, newState: Int) {\n\t\t\tif (newState == BottomSheetBehavior.STATE_HIDDEN) {\n\t\t\t\tdismissAfterAnimation()\n\t\t\t}\n\t\t}\n\n\t\toverride fun onSlide(sheet: View, slideOffset: Float) {}\n\t}\n\n\tprivate inner class SideSheetDialogImpl(context: Context, theme: Int) : SideSheetDialog(context, theme) {\n\n\t\toverride fun onSupportActionModeStarted(mode: ActionMode?) {\n\t\t\tsuper.onSupportActionModeStarted(mode)\n\t\t\tif (mode != null) {\n\t\t\t\tdispatchSupportActionModeStarted(mode)\n\t\t\t}\n\t\t}\n\n\t\toverride fun onSupportActionModeFinished(mode: ActionMode?) {\n\t\t\tsuper.onSupportActionModeFinished(mode)\n\t\t\tif (mode != null) {\n\t\t\t\tdispatchSupportActionModeFinished(mode)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate inner class BottomSheetDialogImpl(context: Context, theme: Int) : BottomSheetDialog(context, theme) {\n\n\t\toverride fun onSupportActionModeStarted(mode: ActionMode?) {\n\t\t\tsuper.onSupportActionModeStarted(mode)\n\t\t\tif (mode != null) {\n\t\t\t\tdispatchSupportActionModeStarted(mode)\n\t\t\t}\n\t\t}\n\n\t\toverride fun onSupportActionModeFinished(mode: ActionMode?) {\n\t\t\tsuper.onSupportActionModeFinished(mode)\n\t\t\tif (mode != null) {\n\t\t\t\tdispatchSupportActionModeFinished(mode)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class CallbackRemoveObserver(\n\t\tprivate val behavior: AdaptiveSheetBehavior,\n\t\tprivate val callback: AdaptiveSheetCallback,\n\t) : DefaultLifecycleObserver {\n\n\t\toverride fun onDestroy(owner: LifecycleOwner) {\n\t\t\tsuper.onDestroy(owner)\n\t\t\towner.lifecycle.removeObserver(this)\n\t\t\tbehavior.removeCallback(callback)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/BottomSheetCollapseCallback.kt",
    "content": "package org.koitharu.kotatsu.core.ui.sheet\n\nimport android.annotation.SuppressLint\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.BackEventCompat\nimport androidx.activity.OnBackPressedCallback\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED\nimport com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED\nimport com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED\nimport com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN\n\nclass BottomSheetCollapseCallback(\n\tprivate val sheet: ViewGroup,\n\tprivate val behavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(sheet),\n) : OnBackPressedCallback(behavior.state == STATE_EXPANDED || behavior.state == STATE_HALF_EXPANDED) {\n\n\tinit {\n\t\tbehavior.addBottomSheetCallback(\n\t\t\tobject : BottomSheetBehavior.BottomSheetCallback() {\n\n\t\t\t\t@SuppressLint(\"SwitchIntDef\")\n\t\t\t\toverride fun onStateChanged(view: View, state: Int) = onStateChanged(state)\n\n\t\t\t\toverride fun onSlide(p0: View, p1: Float) = Unit\n\t\t\t},\n\t\t)\n\t\tonStateChanged(behavior.state)\n\t}\n\n\toverride fun handleOnBackPressed() = behavior.handleBackInvoked()\n\n\toverride fun handleOnBackCancelled() = behavior.cancelBackProgress()\n\n\toverride fun handleOnBackProgressed(backEvent: BackEventCompat) = behavior.updateBackProgress(backEvent)\n\n\toverride fun handleOnBackStarted(backEvent: BackEventCompat) = behavior.startBackProgress(backEvent)\n\n\tprivate fun onStateChanged(state: Int) {\n\t\twhen (state) {\n\t\t\tSTATE_EXPANDED,\n\t\t\tSTATE_HALF_EXPANDED -> isEnabled = true\n\n\t\t\tSTATE_COLLAPSED,\n\t\t\tSTATE_HIDDEN -> isEnabled = false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeDelegate.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.graphics.Color\nimport android.view.ViewGroup\nimport android.view.Window\nimport androidx.activity.OnBackPressedCallback\nimport androidx.appcompat.view.ActionMode\nimport androidx.appcompat.widget.ActionBarContextView\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport com.google.android.material.R as materialR\n\nclass ActionModeDelegate : OnBackPressedCallback(false) {\n\n\tprivate var activeActionMode: ActionMode? = null\n\tprivate var listeners: MutableList<ActionModeListener>? = null\n\tprivate var defaultStatusBarColor = Color.TRANSPARENT\n\n\tval isActionModeStarted: Boolean\n\t\tget() = activeActionMode != null\n\n\toverride fun handleOnBackPressed() {\n\t\tfinishActionMode()\n\t}\n\n\tfun onSupportActionModeStarted(mode: ActionMode, window: Window?) {\n\t\tactiveActionMode = mode\n\t\tisEnabled = true\n\t\tlisteners?.forEach { it.onActionModeStarted(mode) }\n\t\tif (window != null) {\n\t\t\tval ctx = window.context\n\t\t\tval actionModeColor = ColorUtils.compositeColors(\n\t\t\t\tContextCompat.getColor(ctx, materialR.color.m3_appbar_overlay_color),\n\t\t\t\tctx.getThemeColor(materialR.attr.colorSurface),\n\t\t\t)\n\t\t\tdefaultStatusBarColor = window.statusBarColor\n\t\t\twindow.statusBarColor = actionModeColor\n\t\t\tval insets = ViewCompat.getRootWindowInsets(window.decorView)\n\t\t\t\t?.getInsets(WindowInsetsCompat.Type.systemBars()) ?: return\n\t\t\twindow.decorView.findViewById<ActionBarContextView?>(androidx.appcompat.R.id.action_mode_bar)?.apply {\n\t\t\t\tsetBackgroundColor(actionModeColor)\n\t\t\t\tupdateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\t\t\ttopMargin = insets.top\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfun onSupportActionModeFinished(mode: ActionMode, window: Window?) {\n\t\tactiveActionMode = null\n\t\tisEnabled = false\n\t\tlisteners?.forEach { it.onActionModeFinished(mode) }\n\t\tif (window != null) {\n\t\t\twindow.statusBarColor = defaultStatusBarColor\n\t\t}\n\t}\n\n\tfun addListener(listener: ActionModeListener) {\n\t\tif (listeners == null) {\n\t\t\tlisteners = ArrayList()\n\t\t}\n\t\tcheckNotNull(listeners).add(listener)\n\t}\n\n\tfun removeListener(listener: ActionModeListener) {\n\t\tlisteners?.remove(listener)\n\t}\n\n\tfun addListener(listener: ActionModeListener, owner: LifecycleOwner) {\n\t\taddListener(listener)\n\t\towner.lifecycle.addObserver(ListenerLifecycleObserver(listener))\n\t}\n\n\tfun finishActionMode() {\n\t\tactiveActionMode?.finish()\n\t}\n\n\tprivate inner class ListenerLifecycleObserver(\n\t\tprivate val listener: ActionModeListener,\n\t) : DefaultLifecycleObserver {\n\n\t\toverride fun onDestroy(owner: LifecycleOwner) {\n\t\t\tsuper.onDestroy(owner)\n\t\t\tremoveListener(listener)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActionModeListener.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport androidx.appcompat.view.ActionMode\n\ninterface ActionModeListener {\n\n\tfun onActionModeStarted(mode: ActionMode)\n\n\tfun onActionModeFinished(mode: ActionMode)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ActivityRecreationHandle.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.app.Activity\nimport android.os.Bundle\nimport androidx.core.app.ActivityCompat\nimport org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks\nimport java.util.WeakHashMap\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass ActivityRecreationHandle @Inject constructor() : DefaultActivityLifecycleCallbacks {\n\n\tprivate val activities = WeakHashMap<Activity, Unit>()\n\n\toverride fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n\t\tactivities[activity] = Unit\n\t}\n\n\toverride fun onActivityDestroyed(activity: Activity) {\n\t\tactivities.remove(activity)\n\t}\n\n\tfun recreateAll() {\n\t\tval snapshot = activities.keys.toList()\n\t\tsnapshot.forEach { ActivityCompat.recreate(it) }\n\t}\n\n\tfun recreate(cls: Class<out Activity>) {\n\t\tval activity = activities.keys.find { x -> x.javaClass == cls } ?: return\n\t\tActivityCompat.recreate(activity)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/CollapseActionViewCallback.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.view.MenuItem\nimport android.view.MenuItem.OnActionExpandListener\nimport androidx.activity.OnBackPressedCallback\n\nclass CollapseActionViewCallback(\n\tprivate val menuItem: MenuItem\n) : OnBackPressedCallback(menuItem.isActionViewExpanded), OnActionExpandListener {\n\n\toverride fun handleOnBackPressed() {\n\t\tmenuItem.collapseActionView()\n\t}\n\n\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\tisEnabled = true\n\t\treturn true\n\t}\n\n\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean {\n\t\tisEnabled = false\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/DefaultTextWatcher.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.text.Editable\nimport android.text.TextWatcher\n\ninterface DefaultTextWatcher : TextWatcher {\n\n\toverride fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit\n\n\toverride fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit\n\n\toverride fun afterTextChanged(s: Editable?) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/FadingAppbarMediator.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.view.View\nimport com.google.android.material.appbar.AppBarLayout\n\nclass FadingAppbarMediator(\n\tprivate val appBarLayout: AppBarLayout,\n\tprivate val target: View\n) : AppBarLayout.OnOffsetChangedListener {\n\n\tprivate var isBound: Boolean = false\n\n\tfun bind() {\n\t\tif (!isBound) {\n\t\t\tappBarLayout.addOnOffsetChangedListener(this)\n\t\t\tisBound = true\n\t\t}\n\t}\n\n\tfun unbind() {\n\t\tif (isBound) {\n\t\t\tappBarLayout.removeOnOffsetChangedListener(this)\n\t\t\tisBound = false\n\t\t}\n\t\ttarget.alpha = 1f\n\t}\n\n\toverride fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) {\n\t\tval scrollRange = (appBarLayout ?: return).totalScrollRange\n\t\tif (scrollRange <= 0) {\n\t\t\treturn\n\t\t}\n\n\t\ttarget.alpha = 1f + verticalOffset / (scrollRange / 2f)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/MenuInvalidator.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport androidx.core.view.MenuHost\nimport kotlinx.coroutines.flow.FlowCollector\n\nclass MenuInvalidator(\n\tprivate val host: MenuHost,\n) : FlowCollector<Any?> {\n\n\toverride suspend fun emit(value: Any?) = host.invalidateMenu()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/OptionsMenuBadgeHelper.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport androidx.annotation.IdRes\nimport androidx.appcompat.widget.Toolbar\nimport com.google.android.material.badge.BadgeDrawable\nimport com.google.android.material.badge.BadgeUtils\nimport com.google.android.material.badge.ExperimentalBadgeUtils\n\n@androidx.annotation.OptIn(ExperimentalBadgeUtils::class)\nclass OptionsMenuBadgeHelper(\n\tprivate val toolbar: Toolbar,\n\t@IdRes private val itemId: Int,\n) {\n\n\tprivate var badge: BadgeDrawable? = null\n\n\tfun setBadgeVisible(isVisible: Boolean) {\n\t\tif (isVisible) {\n\t\t\tshowBadge()\n\t\t} else {\n\t\t\thideBadge()\n\t\t}\n\t}\n\n\tprivate fun hideBadge() {\n\t\tbadge?.let {\n\t\t\tBadgeUtils.detachBadgeDrawable(it, toolbar, itemId)\n\t\t}\n\t\tbadge = null\n\t}\n\n\tprivate fun showBadge() {\n\t\tval badgeDrawable = badge ?: BadgeDrawable.create(toolbar.context).also {\n\t\t\tbadge = it\n\t\t}\n\t\tBadgeUtils.attachBadgeDrawable(badgeDrawable, toolbar, itemId)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/PagerNestedScrollHelper.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.view.View\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.ancestors\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.Lifecycle.State.RESUMED\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\n\nclass PagerNestedScrollHelper(\n\tprivate val recyclerView: RecyclerView,\n) : DefaultLifecycleObserver {\n\n\tfun bind(lifecycleOwner: LifecycleOwner) {\n\t\tlifecycleOwner.lifecycle.addObserver(this)\n\t\trecyclerView.isNestedScrollingEnabled = lifecycleOwner.lifecycle.currentState.isAtLeast(RESUMED)\n\t}\n\n\toverride fun onPause(owner: LifecycleOwner) {\n\t\trecyclerView.isNestedScrollingEnabled = false\n\t\tinvalidateBottomSheetScrollTarget()\n\t}\n\n\toverride fun onResume(owner: LifecycleOwner) {\n\t\trecyclerView.isNestedScrollingEnabled = true\n\t}\n\n\toverride fun onDestroy(owner: LifecycleOwner) {\n\t\towner.lifecycle.removeObserver(this)\n\t}\n\n\t/**\n\t * Here we need to invalidate the `nestedScrollingChildRef` of the [BottomSheetBehavior]\n\t */\n\tprivate fun invalidateBottomSheetScrollTarget() {\n\t\tvar handleCoordinator = false\n\t\tfor (parent in recyclerView.ancestors) {\n\t\t\tif (handleCoordinator && parent is CoordinatorLayout) {\n\t\t\t\tparent.requestLayout()\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tval lp = (parent as? View)?.layoutParams ?: continue\n\t\t\tif (lp is CoordinatorLayout.LayoutParams && lp.behavior is BottomSheetBehavior<*>) {\n\t\t\t\thandleCoordinator = true\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/PopupMenuMediator.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.view.MenuProvider\n\nclass PopupMenuMediator(\n\tprivate val provider: MenuProvider,\n) : View.OnLongClickListener, View.OnContextClickListener, PopupMenu.OnMenuItemClickListener,\n\tPopupMenu.OnDismissListener {\n\n\toverride fun onContextClick(v: View): Boolean = onLongClick(v)\n\n\toverride fun onLongClick(v: View): Boolean {\n\t\tval menu = PopupMenu(v.context, v)\n\t\tprovider.onCreateMenu(menu.menu, menu.menuInflater)\n\t\tprovider.onPrepareMenu(menu.menu)\n\t\tif (!menu.menu.hasVisibleItems()) {\n\t\t\treturn false\n\t\t}\n\t\tmenu.setOnMenuItemClickListener(this)\n\t\tmenu.setOnDismissListener(this)\n\t\tmenu.show()\n\t\treturn true\n\t}\n\n\toverride fun onMenuItemClick(item: MenuItem): Boolean {\n\t\treturn provider.onMenuItemSelected(item)\n\t}\n\n\toverride fun onDismiss(menu: PopupMenu) {\n\t\tprovider.onMenuClosed(menu.menu)\n\t}\n\n\tfun attach(view: View) {\n\t\tview.setOnLongClickListener(this)\n\t\tview.setOnContextClickListener(this)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/RecyclerViewOwner.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport androidx.recyclerview.widget.RecyclerView\n\ninterface RecyclerViewOwner {\n\n\tval recyclerView: RecyclerView?\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleAction.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport androidx.annotation.StringRes\n\nclass ReversibleAction(\n\t@StringRes val stringResId: Int,\n\tval handle: ReversibleHandle?,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleActionObserver.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.view.View\nimport com.google.android.material.snackbar.Snackbar\nimport kotlinx.coroutines.flow.FlowCollector\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.findActivity\nimport org.koitharu.kotatsu.main.ui.owners.BottomNavOwner\nimport org.koitharu.kotatsu.main.ui.owners.BottomSheetOwner\n\nclass ReversibleActionObserver(\n\tprivate val snackbarHost: View,\n) : FlowCollector<ReversibleAction> {\n\n\toverride suspend fun emit(value: ReversibleAction) {\n\t\tval handle = value.handle\n\t\tval length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG\n\t\tval snackbar = Snackbar.make(snackbarHost, value.stringResId, length)\n\t\twhen (val activity = snackbarHost.context.findActivity()) {\n\t\t\tis BottomNavOwner -> snackbar.anchorView = activity.bottomNav\n\t\t\tis BottomSheetOwner -> snackbar.anchorView = activity.bottomSheet\n\t\t}\n\t\tif (handle != null) {\n\t\t\tsnackbar.setAction(R.string.undo) { handle.reverseAsync() }\n\t\t}\n\t\tsnackbar.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ReversibleHandle.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.NonCancellable\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\n\nfun interface ReversibleHandle {\n\n\tsuspend fun reverse()\n}\n\nfun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.Default, CoroutineStart.ATOMIC) {\n\trunCatchingCancellable {\n\t\twithContext(NonCancellable) {\n\t\t\treverse()\n\t\t}\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/ShrinkOnScrollBehavior.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior\nimport androidx.core.view.ViewCompat\nimport com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton\n\nopen class ShrinkOnScrollBehavior : Behavior<ExtendedFloatingActionButton> {\n\n\tconstructor() : super()\n\tconstructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)\n\n\toverride fun onStartNestedScroll(\n\t\tcoordinatorLayout: CoordinatorLayout,\n\t\tchild: ExtendedFloatingActionButton,\n\t\tdirectTargetChild: View,\n\t\ttarget: View,\n\t\taxes: Int,\n\t\ttype: Int\n\t): Boolean {\n\t\treturn axes == ViewCompat.SCROLL_AXIS_VERTICAL\n\t}\n\n\toverride fun onNestedScroll(\n\t\tcoordinatorLayout: CoordinatorLayout,\n\t\tchild: ExtendedFloatingActionButton,\n\t\ttarget: View,\n\t\tdxConsumed: Int,\n\t\tdyConsumed: Int,\n\t\tdxUnconsumed: Int,\n\t\tdyUnconsumed: Int,\n\t\ttype: Int,\n\t\tconsumed: IntArray\n\t) {\n\t\tif (dyConsumed > 0) {\n\t\t\tif (child.isExtended) {\n\t\t\t\tchild.shrink()\n\t\t\t}\n\t\t} else if (dyConsumed < 0) {\n\t\t\tif (!child.isExtended) {\n\t\t\t\tchild.extend()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/SpanSizeResolver.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.view.View\nimport androidx.annotation.Px\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.parsers.util.toIntUp\nimport kotlin.math.abs\n\nclass SpanSizeResolver(\n\tprivate val recyclerView: RecyclerView,\n\t@Px private val minItemWidth: Int,\n) : View.OnLayoutChangeListener {\n\n\tfun attach() {\n\t\trecyclerView.addOnLayoutChangeListener(this)\n\t}\n\n\tfun detach() {\n\t\trecyclerView.removeOnLayoutChangeListener(this)\n\t}\n\n\toverride fun onLayoutChange(\n\t\tv: View?,\n\t\tleft: Int,\n\t\ttop: Int,\n\t\tright: Int,\n\t\tbottom: Int,\n\t\toldLeft: Int,\n\t\toldTop: Int,\n\t\toldRight: Int,\n\t\toldBottom: Int,\n\t) {\n\t\tinvalidateInternal(abs(right - left))\n\t}\n\n\tfun invalidate() {\n\t\tinvalidateInternal(recyclerView.width)\n\t}\n\n\tprivate fun invalidateInternal(width: Int) {\n\t\tif (width <= 0) {\n\t\t\treturn\n\t\t}\n\t\tval lm = recyclerView.layoutManager as? GridLayoutManager ?: return\n\t\tval estimatedCount = (width / minItemWidth.toFloat()).toIntUp()\n\t\tif (lm.spanCount != estimatedCount) {\n\t\t\tlm.spanCount = estimatedCount\n\t\t\tlm.spanSizeLookup?.run {\n\t\t\t\tinvalidateSpanGroupIndexCache()\n\t\t\t\tinvalidateSpanIndexCache()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/SystemUiController.kt",
    "content": "package org.koitharu.kotatsu.core.ui.util\n\nimport android.os.Build\nimport android.view.View\nimport android.view.Window\nimport android.view.WindowInsets\nimport android.view.WindowInsetsController\nimport androidx.annotation.RequiresApi\n\nsealed class SystemUiController(\n\tprotected val window: Window,\n) {\n\n\tabstract fun setSystemUiVisible(value: Boolean)\n\n\t@RequiresApi(Build.VERSION_CODES.S)\n\tprivate class Api30Impl(window: Window) : SystemUiController(window) {\n\n\t\tprivate val insetsController = checkNotNull(window.decorView.windowInsetsController)\n\n\t\toverride fun setSystemUiVisible(value: Boolean) {\n\t\t\tif (value) {\n\t\t\t\tinsetsController.show(WindowInsets.Type.systemBars())\n\t\t\t\tinsetsController.systemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT\n\t\t\t} else {\n\t\t\t\tinsetsController.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE\n\t\t\t\tinsetsController.hide(WindowInsets.Type.systemBars())\n\t\t\t}\n\t\t}\n\t}\n\n\t@Suppress(\"DEPRECATION\")\n\tprivate class LegacyImpl(window: Window) : SystemUiController(window) {\n\n\t\toverride fun setSystemUiVisible(value: Boolean) {\n\t\t\tval flags = window.decorView.systemUiVisibility\n\t\t\twindow.decorView.systemUiVisibility = if (value) {\n\t\t\t\t(flags and LEGACY_FLAGS_HIDDEN.inv()) or LEGACY_FLAGS_VISIBLE\n\t\t\t} else {\n\t\t\t\t(flags and LEGACY_FLAGS_VISIBLE.inv()) or LEGACY_FLAGS_HIDDEN\n\t\t\t}\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\t@Suppress(\"DEPRECATION\")\n\t\tprivate const val LEGACY_FLAGS_VISIBLE = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or\n\t\t\tView.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or\n\t\t\tView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\n\n\t\t@Suppress(\"DEPRECATION\")\n\t\tprivate const val LEGACY_FLAGS_HIDDEN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or\n\t\t\tView.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or\n\t\t\tView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or\n\t\t\tView.SYSTEM_UI_FLAG_HIDE_NAVIGATION or\n\t\t\tView.SYSTEM_UI_FLAG_FULLSCREEN or\n\t\t\tView.SYSTEM_UI_FLAG_IMMERSIVE_STICKY\n\n\t\toperator fun invoke(window: Window): SystemUiController =\n\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n\t\t\t\tApi30Impl(window)\n\t\t\t} else {\n\t\t\t\tLegacyImpl(window)\n\t\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/BadgeView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.os.Parcelable.Creator\nimport android.util.AttributeSet\nimport androidx.core.content.withStyledAttributes\nimport androidx.customview.view.AbsSavedState\nimport com.google.android.material.shape.MaterialShapeDrawable\nimport com.google.android.material.shape.ShapeAppearanceModel\nimport com.google.android.material.textview.MaterialTextView\nimport org.koitharu.kotatsu.R\n\nclass BadgeView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null\n) : MaterialTextView(context, attrs, R.attr.badgeViewStyle) {\n\n\tprivate var maxCharacterCount = Int.MAX_VALUE\n\n\tvar number: Int = 0\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tupdateText()\n\t\t}\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.BadgeView, R.attr.badgeViewStyle) {\n\t\t\tmaxCharacterCount = getInt(R.styleable.BadgeView_maxCharacterCount, maxCharacterCount)\n\t\t\tnumber = getInt(R.styleable.BadgeView_number, number)\n\t\t\tval shape = ShapeAppearanceModel.builder(\n\t\t\t\tcontext,\n\t\t\t\tgetResourceId(R.styleable.BadgeView_shapeAppearance, 0),\n\t\t\t\t0,\n\t\t\t).build()\n\t\t\tbackground = MaterialShapeDrawable(shape).also { bg ->\n\t\t\t\tbg.fillColor = getColorStateList(R.styleable.BadgeView_backgroundColor)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onSaveInstanceState(): Parcelable? {\n\t\tval superState = super.onSaveInstanceState() ?: return null\n\t\treturn SavedState(superState, number)\n\t}\n\n\toverride fun onRestoreInstanceState(state: Parcelable?) {\n\t\tif (state is SavedState) {\n\t\t\tsuper.onRestoreInstanceState(state.superState)\n\t\t\tnumber = state.number\n\t\t} else {\n\t\t\tsuper.onRestoreInstanceState(state)\n\t\t}\n\t}\n\n\tprivate fun updateText() {\n\t\tif (number <= 0) {\n\t\t\ttext = null\n\t\t\treturn\n\t\t}\n\t\tval numberString = number.toString()\n\t\ttext = if (numberString.length > maxCharacterCount) {\n\t\t\tbuildString(maxCharacterCount) {\n\t\t\t\trepeat(maxCharacterCount - 1) { append('9') }\n\t\t\t\tappend('+')\n\t\t\t}\n\t\t} else {\n\t\t\tnumberString\n\t\t}\n\t}\n\n\tprivate class SavedState : AbsSavedState {\n\n\t\tval number: Int\n\n\t\tconstructor(superState: Parcelable, number: Int) : super(superState) {\n\t\t\tthis.number = number\n\t\t}\n\n\t\tconstructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) {\n\t\t\tnumber = source.readInt()\n\t\t}\n\n\t\toverride fun writeToParcel(out: Parcel, flags: Int) {\n\t\t\tsuper.writeToParcel(out, flags)\n\t\t\tout.writeInt(number)\n\t\t}\n\n\t\tcompanion object {\n\t\t\t@Suppress(\"unused\")\n\t\t\t@JvmField\n\t\t\tval CREATOR: Creator<SavedState> = object : Creator<SavedState> {\n\t\t\t\toverride fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader)\n\n\t\t\t\toverride fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CheckableImageButton.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.os.Parcelable.Creator\nimport android.util.AttributeSet\nimport android.widget.Checkable\nimport androidx.annotation.AttrRes\nimport androidx.appcompat.widget.AppCompatImageButton\nimport androidx.core.os.ParcelCompat\nimport androidx.customview.view.AbsSavedState\n\nclass CheckableImageButton @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : AppCompatImageButton(context, attrs, defStyleAttr), Checkable {\n\n\tprivate var isCheckedInternal = false\n\tprivate var isBroadcasting = false\n\n\tvar onCheckedChangeListener: OnCheckedChangeListener? = null\n\n\toverride fun isChecked() = isCheckedInternal\n\n\toverride fun toggle() {\n\t\tisChecked = !isCheckedInternal\n\t}\n\n\toverride fun setChecked(checked: Boolean) {\n\t\tif (checked != isCheckedInternal) {\n\t\t\tisCheckedInternal = checked\n\t\t\trefreshDrawableState()\n\t\t\tif (!isBroadcasting) {\n\t\t\t\tisBroadcasting = true\n\t\t\t\tonCheckedChangeListener?.onCheckedChanged(this, checked)\n\t\t\t\tisBroadcasting = false\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCreateDrawableState(extraSpace: Int): IntArray {\n\t\tval state = super.onCreateDrawableState(extraSpace + 1)\n\t\tif (isCheckedInternal) {\n\t\t\tmergeDrawableStates(state, intArrayOf(android.R.attr.state_checked))\n\t\t}\n\t\treturn state\n\t}\n\n\toverride fun onSaveInstanceState(): Parcelable? {\n\t\tval superState = super.onSaveInstanceState() ?: return null\n\t\treturn SavedState(superState, isChecked)\n\t}\n\n\toverride fun onRestoreInstanceState(state: Parcelable?) {\n\t\tif (state is SavedState) {\n\t\t\tsuper.onRestoreInstanceState(state.superState)\n\t\t\tisChecked = state.isChecked\n\t\t} else {\n\t\t\tsuper.onRestoreInstanceState(state)\n\t\t}\n\t}\n\n\tfun interface OnCheckedChangeListener {\n\n\t\tfun onCheckedChanged(view: CheckableImageButton, isChecked: Boolean)\n\t}\n\n\tprivate class SavedState : AbsSavedState {\n\n\t\tval isChecked: Boolean\n\n\t\tconstructor(superState: Parcelable, checked: Boolean) : super(superState) {\n\t\t\tisChecked = checked\n\t\t}\n\n\t\tconstructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) {\n\t\t\tisChecked = ParcelCompat.readBoolean(source)\n\t\t}\n\n\t\toverride fun writeToParcel(out: Parcel, flags: Int) {\n\t\t\tsuper.writeToParcel(out, flags)\n\t\t\tParcelCompat.writeBoolean(out, isChecked)\n\t\t}\n\n\t\tcompanion object {\n\t\t\t@Suppress(\"unused\")\n\t\t\t@JvmField\n\t\t\tval CREATOR: Creator<SavedState> = object : Creator<SavedState> {\n\t\t\t\toverride fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader)\n\n\t\t\t\toverride fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CheckableImageView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.os.Parcelable.Creator\nimport android.util.AttributeSet\nimport android.widget.Checkable\nimport androidx.annotation.AttrRes\nimport androidx.appcompat.widget.AppCompatImageView\nimport androidx.core.os.ParcelCompat\nimport androidx.customview.view.AbsSavedState\n\nclass CheckableImageView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : AppCompatImageView(context, attrs, defStyleAttr), Checkable {\n\n\tprivate var isCheckedInternal = false\n\tprivate var isBroadcasting = false\n\n\tvar onCheckedChangeListener: OnCheckedChangeListener? = null\n\n\toverride fun isChecked() = isCheckedInternal\n\n\toverride fun toggle() {\n\t\tisChecked = !isCheckedInternal\n\t}\n\n\toverride fun setChecked(checked: Boolean) {\n\t\tif (checked != isCheckedInternal) {\n\t\t\tisCheckedInternal = checked\n\t\t\trefreshDrawableState()\n\t\t\tif (!isBroadcasting) {\n\t\t\t\tisBroadcasting = true\n\t\t\t\tonCheckedChangeListener?.onCheckedChanged(this, checked)\n\t\t\t\tisBroadcasting = false\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCreateDrawableState(extraSpace: Int): IntArray {\n\t\tval state = super.onCreateDrawableState(extraSpace + 1)\n\t\tif (isCheckedInternal) {\n\t\t\tmergeDrawableStates(state, intArrayOf(android.R.attr.state_checked))\n\t\t}\n\t\treturn state\n\t}\n\n\toverride fun onSaveInstanceState(): Parcelable? {\n\t\tval superState = super.onSaveInstanceState() ?: return null\n\t\treturn SavedState(superState, isChecked)\n\t}\n\n\toverride fun onRestoreInstanceState(state: Parcelable?) {\n\t\tif (state is SavedState) {\n\t\t\tsuper.onRestoreInstanceState(state.superState)\n\t\t\tisChecked = state.isChecked\n\t\t} else {\n\t\t\tsuper.onRestoreInstanceState(state)\n\t\t}\n\t}\n\n\tfun interface OnCheckedChangeListener {\n\n\t\tfun onCheckedChanged(view: CheckableImageView, isChecked: Boolean)\n\t}\n\n\tprivate class SavedState : AbsSavedState {\n\n\t\tval isChecked: Boolean\n\n\t\tconstructor(superState: Parcelable, checked: Boolean) : super(superState) {\n\t\t\tisChecked = checked\n\t\t}\n\n\t\tconstructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) {\n\t\t\tisChecked = ParcelCompat.readBoolean(source)\n\t\t}\n\n\t\toverride fun writeToParcel(out: Parcel, flags: Int) {\n\t\t\tsuper.writeToParcel(out, flags)\n\t\t\tParcelCompat.writeBoolean(out, isChecked)\n\t\t}\n\n\t\tcompanion object {\n\t\t\t@Suppress(\"unused\")\n\t\t\t@JvmField\n\t\t\tval CREATOR: Creator<SavedState> = object : Creator<SavedState> {\n\t\t\t\toverride fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader)\n\n\t\t\t\toverride fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.text.style.ForegroundColorSpan\nimport android.text.style.RelativeSizeSpan\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.annotation.ColorRes\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.inSpans\nimport androidx.core.view.children\nimport androidx.lifecycle.findViewTreeLifecycleOwner\nimport coil3.ImageLoader\nimport coil3.request.Disposable\nimport coil3.request.ImageRequest\nimport coil3.request.allowRgb565\nimport coil3.request.crossfade\nimport coil3.request.error\nimport coil3.request.fallback\nimport coil3.request.lifecycle\nimport coil3.request.placeholder\nimport coil3.request.transformations\nimport coil3.transform.RoundedCornersTransformation\nimport com.google.android.material.chip.Chip\nimport com.google.android.material.chip.ChipDrawable\nimport com.google.android.material.chip.ChipGroup\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.image.ChipIconTarget\nimport org.koitharu.kotatsu.core.util.ext.enqueueWith\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.setProgressIcon\nimport org.koitharu.kotatsu.parsers.util.ifZero\nimport javax.inject.Inject\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nclass ChipsView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = materialR.attr.chipGroupStyle,\n) : ChipGroup(context, attrs, defStyleAttr) {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate var isLayoutSuppressedCompat = false\n\tprivate var isLayoutCalledOnSuppressed = false\n\tprivate val chipOnClickListener = InternalChipClickListener()\n\tprivate val chipOnCloseListener = OnClickListener {\n\t\tval chip = it as Chip\n\t\tval data = it.tag\n\t\tonChipCloseClickListener?.onChipCloseClick(chip, data) ?: onChipClickListener?.onChipClick(chip, data)\n\t}\n\tprivate val chipOnLongClickListener = OnLongClickListener {\n\t\tval chip = it as Chip\n\t\tval data = it.tag\n\t\tonChipLongClickListener?.onChipLongClick(chip, data) ?: false\n\t}\n\tprivate val chipStyle: Int\n\tprivate val iconsVisible: Boolean\n\tvar onChipClickListener: OnChipClickListener? = null\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tval isChipClickable = value != null\n\t\t\tchildren.forEach { it.isClickable = isChipClickable }\n\t\t}\n\tvar onChipCloseClickListener: OnChipCloseClickListener? = null\n\n\tvar onChipLongClickListener: OnChipLongClickListener? = null\n\n\tinit {\n\t\tval ta = context.obtainStyledAttributes(attrs, R.styleable.ChipsView, defStyleAttr, 0)\n\t\tchipStyle = ta.getResourceId(R.styleable.ChipsView_chipStyle, R.style.Widget_Kotatsu_Chip)\n\t\ticonsVisible = ta.getBoolean(R.styleable.ChipsView_chipIconVisible, true)\n\t\tta.recycle()\n\n\t\tif (isInEditMode) {\n\t\t\tsetChips(\n\t\t\t\tList(5) {\n\t\t\t\t\tChipModel(title = \"Chip $it\")\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun requestLayout() {\n\t\tif (isLayoutSuppressedCompat) {\n\t\t\tisLayoutCalledOnSuppressed = true\n\t\t} else {\n\t\t\tsuper.requestLayout()\n\t\t}\n\t}\n\n\tfun setChips(items: Collection<ChipModel>) {\n\t\tsuppressLayoutCompat(true)\n\t\ttry {\n\t\t\tfor ((i, model) in items.withIndex()) {\n\t\t\t\tval chip = getChildAt(i) as DataChip? ?: addChip()\n\t\t\t\tchip.bind(model)\n\t\t\t}\n\t\t\tif (childCount > items.size) {\n\t\t\t\tremoveViews(items.size, childCount - items.size)\n\t\t\t}\n\t\t} finally {\n\t\t\tsuppressLayoutCompat(false)\n\t\t}\n\t}\n\n\tprivate fun addChip() = DataChip(context).also { addView(it) }\n\n\tprivate fun suppressLayoutCompat(suppress: Boolean) {\n\t\tisLayoutSuppressedCompat = suppress\n\t\tif (!suppress) {\n\t\t\tif (isLayoutCalledOnSuppressed) {\n\t\t\t\trequestLayout()\n\t\t\t\tisLayoutCalledOnSuppressed = false\n\t\t\t}\n\t\t}\n\t}\n\n\tdata class ChipModel(\n\t\tval title: CharSequence? = null,\n\t\t@StringRes val titleResId: Int = 0,\n\t\t@DrawableRes val icon: Int = 0,\n\t\tval iconData: Any? = null,\n\t\t@ColorRes val tint: Int = 0,\n\t\tval counter: Int = 0,\n\t\tval isChecked: Boolean = false,\n\t\tval isLoading: Boolean = false,\n\t\tval isDropdown: Boolean = false,\n\t\tval isCloseable: Boolean = false,\n\t\tval data: Any? = null,\n\t)\n\n\tprivate inner class DataChip(context: Context) : Chip(context) {\n\n\t\tprivate var model: ChipModel? = null\n\t\tprivate var imageRequest: Disposable? = null\n\n\t\tprivate val defaultStrokeColor = chipStrokeColor\n\t\tprivate val defaultTextColor = textColors\n\n\t\tinit {\n\t\t\tval drawable = ChipDrawable.createFromAttributes(context, null, 0, chipStyle)\n\t\t\tsetChipDrawable(drawable)\n\t\t\tisChipIconVisible = false\n\t\t\tsetOnCloseIconClickListener(chipOnCloseListener)\n\t\t\tsetEnsureMinTouchTargetSize(false)\n\t\t\tsetOnClickListener(chipOnClickListener)\n\t\t\tsetOnLongClickListener(chipOnLongClickListener)\n\t\t\tisElegantTextHeight = false\n\t\t}\n\n\t\tfun bind(model: ChipModel) {\n\t\t\tif (this.model == model) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.model = model\n\n\t\t\tif (model.counter > 0) {\n\t\t\t\ttext = buildSpannedString {\n\t\t\t\t\tif (model.titleResId == 0) {\n\t\t\t\t\t\tappend(model.title)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tappend(context.getString(model.titleResId))\n\t\t\t\t\t}\n\t\t\t\t\tappend(' ')\n\t\t\t\t\tappend(' ')\n\t\t\t\t\tinSpans(\n\t\t\t\t\t\tForegroundColorSpan(\n\t\t\t\t\t\t\tcontext.getThemeColor(\n\t\t\t\t\t\t\t\tandroid.R.attr.textColorSecondary,\n\t\t\t\t\t\t\t\tColor.LTGRAY,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tRelativeSizeSpan(0.74f),\n\t\t\t\t\t) {\n\t\t\t\t\t\tappend(model.counter.toString())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (model.titleResId == 0) {\n\t\t\t\ttext = model.title\n\t\t\t} else {\n\t\t\t\tsetText(model.titleResId)\n\t\t\t}\n\t\t\tisClickable = onChipClickListener != null\n\t\t\tif (model.isChecked) {\n\t\t\t\tisCheckable = true\n\t\t\t\tisChecked = true\n\t\t\t} else {\n\t\t\t\tisChecked = false\n\t\t\t\tisCheckable = false\n\t\t\t}\n\t\t\tif (model.tint == 0) {\n\t\t\t\tchipStrokeColor = defaultStrokeColor\n\t\t\t\tsetTextColor(defaultTextColor)\n\t\t\t} else {\n\t\t\t\tval tint = ContextCompat.getColorStateList(context, model.tint)\n\t\t\t\tchipStrokeColor = tint\n\t\t\t\tsetTextColor(tint)\n\t\t\t}\n\t\t\tbindIcon(model)\n\t\t\tisCheckedIconVisible = model.isChecked\n\t\t\tisCloseIconVisible = if (model.isCloseable || model.isDropdown) {\n\t\t\t\tsetCloseIconResource(\n\t\t\t\t\tif (model.isDropdown) R.drawable.ic_expand_more else materialR.drawable.ic_m3_chip_close,\n\t\t\t\t)\n\t\t\t\ttrue\n\t\t\t} else {\n\t\t\t\tfalse\n\t\t\t}\n\t\t\ttag = model.data\n\t\t}\n\n\t\toverride fun toggle() = Unit\n\n\t\tprivate fun bindIcon(model: ChipModel) {\n\t\t\twhen {\n\t\t\t\tmodel.isChecked -> disposeIcon()\n\n\t\t\t\tmodel.isLoading -> {\n\t\t\t\t\timageRequest?.dispose()\n\t\t\t\t\timageRequest = null\n\t\t\t\t\tisChipIconVisible = true\n\t\t\t\t\tsetProgressIcon()\n\t\t\t\t}\n\n\t\t\t\t!iconsVisible -> disposeIcon()\n\n\t\t\t\tmodel.iconData != null -> {\n\t\t\t\t\tval placeholder = model.icon.ifZero { materialR.drawable.navigation_empty_icon }\n\t\t\t\t\timageRequest = ImageRequest.Builder(context)\n\t\t\t\t\t\t.data(model.iconData)\n\t\t\t\t\t\t.crossfade(false)\n\t\t\t\t\t\t.size(resources.getDimensionPixelSize(materialR.dimen.m3_chip_icon_size))\n\t\t\t\t\t\t.target(ChipIconTarget(this))\n\t\t\t\t\t\t.placeholder(placeholder)\n\t\t\t\t\t\t.fallback(placeholder)\n\t\t\t\t\t\t.lifecycle(this@ChipsView.findViewTreeLifecycleOwner())\n\t\t\t\t\t\t.error(placeholder)\n\t\t\t\t\t\t.transformations(RoundedCornersTransformation(resources.getDimension(R.dimen.chip_icon_corner)))\n\t\t\t\t\t\t.allowRgb565(true)\n\t\t\t\t\t\t.enqueueWith(coil)\n\t\t\t\t\tisChipIconVisible = true\n\t\t\t\t}\n\n\t\t\t\tmodel.icon != 0 -> {\n\t\t\t\t\timageRequest?.dispose()\n\t\t\t\t\timageRequest = null\n\t\t\t\t\tsetChipIconResource(model.icon)\n\t\t\t\t\tisChipIconVisible = true\n\t\t\t\t}\n\n\t\t\t\telse -> disposeIcon()\n\t\t\t}\n\t\t}\n\n\t\tprivate fun disposeIcon() {\n\t\t\timageRequest?.dispose()\n\t\t\timageRequest = null\n\t\t\tchipIcon = null\n\t\t\tisChipIconVisible = false\n\t\t}\n\t}\n\n\tprivate inner class InternalChipClickListener : OnClickListener {\n\t\toverride fun onClick(v: View?) {\n\t\t\tval chip = v as? DataChip ?: return\n\t\t\tonChipClickListener?.onChipClick(chip, chip.tag)\n\t\t}\n\t}\n\n\tfun interface OnChipClickListener {\n\n\t\tfun onChipClick(chip: Chip, data: Any?)\n\t}\n\n\tfun interface OnChipCloseClickListener {\n\n\t\tfun onChipCloseClick(chip: Chip, data: Any?)\n\t}\n\n\tfun interface OnChipLongClickListener {\n\n\t\tfun onChipLongClick(chip: Chip, data: Any?): Boolean\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CubicSlider.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.collection.MutableScatterMap\nimport com.google.android.material.slider.Slider\nimport kotlin.math.cbrt\nimport kotlin.math.pow\n\nclass CubicSlider @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n) : Slider(context, attrs) {\n\n\tprivate val changeListeners = MutableScatterMap<OnChangeListener, OnChangeListenerMapper>(1)\n\n\toverride fun setValue(value: Float) {\n\t\tsuper.setValue(value.unmap())\n\t}\n\n\toverride fun getValue(): Float {\n\t\treturn super.getValue().map()\n\t}\n\n\toverride fun getValueFrom(): Float {\n\t\treturn super.getValueFrom().map()\n\t}\n\n\toverride fun setValueFrom(valueFrom: Float) {\n\t\tsuper.setValueFrom(valueFrom.unmap())\n\t}\n\n\toverride fun getValueTo(): Float {\n\t\treturn super.getValueTo().map()\n\t}\n\n\toverride fun setValueTo(valueTo: Float) {\n\t\tsuper.setValueTo(valueTo.unmap())\n\t}\n\n\toverride fun addOnChangeListener(listener: OnChangeListener) {\n\t\tval mapper = OnChangeListenerMapper(listener)\n\t\tsuper.addOnChangeListener(mapper)\n\t\tchangeListeners[listener] = mapper\n\t}\n\n\toverride fun removeOnChangeListener(listener: OnChangeListener) {\n\t\tchangeListeners.remove(listener)?.let {\n\t\t\tsuper.removeOnChangeListener(it)\n\t\t}\n\t}\n\n\toverride fun clearOnChangeListeners() {\n\t\tsuper.clearOnChangeListeners()\n\t\tchangeListeners.clear()\n\t}\n\n\tprivate fun Float.map(): Float {\n\t\treturn this.pow(3)\n\t}\n\n\tprivate fun Float.unmap(): Float {\n\t\treturn cbrt(this)\n\t}\n\n\tprivate inner class OnChangeListenerMapper(\n\t\tprivate val delegate: OnChangeListener,\n\t) : OnChangeListener {\n\n\t\toverride fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {\n\t\t\tdelegate.onValueChange(slider, value.map(), fromUser)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/DotsIndicator.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.core.content.withStyledAttributes\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.AdapterDataObserver\nimport androidx.viewpager2.widget.ViewPager2\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getThemeColorStateList\nimport org.koitharu.kotatsu.core.util.ext.measureDimension\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\nimport org.koitharu.kotatsu.parsers.util.toIntUp\nimport com.google.android.material.R as materialR\n\nclass DotsIndicator @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = R.attr.dotIndicatorStyle,\n) : View(context, attrs, defStyleAttr) {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate var indicatorSize = context.resources.resolveDp(12f)\n\tprivate var dotSpacing = 0f\n\tprivate var smallDotScale = 0.33f\n\tprivate var smallDotAlpha = 0.6f\n\tprivate var positionOffset: Float = 0f\n\tprivate var position: Int = 0\n\tprivate var dotsColor: ColorStateList = ColorStateList.valueOf(Color.DKGRAY)\n\tprivate val inset = context.resources.resolveDp(1f)\n\n\tvar max: Int = 6\n\t\tset(value) {\n\t\t\tif (field != value) {\n\t\t\t\tfield = value\n\t\t\t\trequestLayout()\n\t\t\t\tinvalidate()\n\t\t\t}\n\t\t}\n\tvar progress: Int\n\t\tget() = position\n\t\tset(value) {\n\t\t\tif (position != value) {\n\t\t\t\tposition = value\n\t\t\t\tinvalidate()\n\t\t\t}\n\t\t}\n\n\tinit {\n\t\tpaint.style = Paint.Style.FILL\n\t\tcontext.withStyledAttributes(attrs, R.styleable.DotsIndicator, defStyleAttr) {\n\t\t\tdotsColor = getColorStateList(R.styleable.DotsIndicator_dotColor)\n\t\t\t\t?: context.getThemeColorStateList(materialR.attr.colorOnBackground)\n\t\t\t\t\t?: dotsColor\n\t\t\tpaint.color = dotsColor.getColorForState(drawableState, dotsColor.defaultColor)\n\t\t\tindicatorSize = getDimension(R.styleable.DotsIndicator_dotSize, indicatorSize)\n\t\t\tdotSpacing = getDimension(R.styleable.DotsIndicator_dotSpacing, dotSpacing)\n\t\t\tsmallDotScale = getFloat(R.styleable.DotsIndicator_dotScale, smallDotScale).coerceIn(0f, 1f)\n\t\t\tsmallDotAlpha = getFloat(R.styleable.DotsIndicator_dotAlpha, smallDotAlpha).coerceIn(0f, 1f)\n\t\t\tmax = getInt(R.styleable.DotsIndicator_android_max, max)\n\t\t\tposition = getInt(R.styleable.DotsIndicator_android_progress, position)\n\t\t}\n\t}\n\n\toverride fun onDraw(canvas: Canvas) {\n\t\tsuper.onDraw(canvas)\n\t\tval dotSize = getDotSize()\n\t\tval y = paddingTop + (height - paddingTop - paddingBottom) / 2f\n\t\tvar x = paddingLeft + dotSize / 2f\n\t\tval radius = dotSize / 2f - inset\n\t\tval spacing = (width - paddingLeft - paddingRight) / max.toFloat() - dotSize\n\t\tx += spacing / 2f\n\t\tfor (i in 0 until max) {\n\t\t\tval scale = when (i) {\n\t\t\t\tposition -> (1f - smallDotScale) * (1f - positionOffset) + smallDotScale\n\t\t\t\tposition + 1 -> (1f - smallDotScale) * positionOffset + smallDotScale\n\t\t\t\telse -> smallDotScale\n\t\t\t}\n\t\t\tpaint.alpha = (255 * when (i) {\n\t\t\t\tposition -> (1f - smallDotAlpha) * (1f - positionOffset) + smallDotAlpha\n\t\t\t\tposition + 1 -> (1f - smallDotAlpha) * positionOffset + smallDotAlpha\n\t\t\t\telse -> smallDotAlpha\n\t\t\t}).toInt()\n\t\t\tcanvas.drawCircle(x, y, radius * scale, paint)\n\t\t\tx += spacing + dotSize\n\t\t}\n\t}\n\n\toverride fun drawableStateChanged() {\n\t\tif (dotsColor.isStateful) {\n\t\t\tpaint.color = dotsColor.getColorForState(drawableState, dotsColor.defaultColor)\n\t\t}\n\t\tsuper.drawableStateChanged()\n\t}\n\n\toverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\t\tval dotSize = getDotSize()\n\t\tval desiredHeight = (dotSize + paddingTop + paddingBottom).toIntUp()\n\t\tval desiredWidth = ((dotSize + dotSpacing) * max).toIntUp() + paddingLeft + paddingRight\n\t\tsetMeasuredDimension(\n\t\t\tmeasureDimension(desiredWidth, widthMeasureSpec),\n\t\t\tmeasureDimension(desiredHeight, heightMeasureSpec),\n\t\t)\n\t}\n\n\tfun bindToViewPager(pager: ViewPager2) {\n\t\tpager.registerOnPageChangeCallback(ViewPagerCallback())\n\t\tpager.adapter?.let {\n\t\t\tit.registerAdapterDataObserver(AdapterObserver(it))\n\t\t}\n\t}\n\n\tprivate fun getDotSize() = if (indicatorSize <= 0) {\n\t\t(height - paddingTop - paddingBottom).toFloat()\n\t} else {\n\t\tindicatorSize\n\t}\n\n\tprivate inner class ViewPagerCallback : ViewPager2.OnPageChangeCallback() {\n\n\t\toverride fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {\n\t\t\tsuper.onPageScrolled(position, positionOffset, positionOffsetPixels)\n\t\t\tthis@DotsIndicator.position = position\n\t\t\tthis@DotsIndicator.positionOffset = positionOffset\n\t\t\tinvalidate()\n\t\t}\n\t}\n\n\tprivate inner class AdapterObserver(\n\t\tprivate val adapter: RecyclerView.Adapter<*>,\n\t) : AdapterDataObserver() {\n\n\t\toverride fun onChanged() {\n\t\t\tsuper.onChanged()\n\t\t\tmax = adapter.itemCount\n\t\t}\n\n\t\toverride fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n\t\t\tsuper.onItemRangeInserted(positionStart, itemCount)\n\t\t\tmax = adapter.itemCount\n\t\t}\n\n\t\toverride fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {\n\t\t\tsuper.onItemRangeRemoved(positionStart, itemCount)\n\t\t\tmax = adapter.itemCount\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/HideBottomNavigationOnScrollBehavior.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.animation.DecelerateInterpolator\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.ViewCompat\nimport com.google.android.material.appbar.AppBarLayout\nimport com.google.android.material.navigation.NavigationBarView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport org.koitharu.kotatsu.core.util.ext.measureHeight\n\nclass HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(\n\tcontext: Context? = null,\n\tattrs: AttributeSet? = null,\n) : CoordinatorLayout.Behavior<NavigationBarView>(context, attrs) {\n\n\t@ViewCompat.NestedScrollType\n\tprivate var lastStartedType: Int = 0\n\n\tprivate var offsetAnimator: ValueAnimator? = null\n\n\tprivate var dyRatio = 1F\n\n\tvar isPinned: Boolean = false\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tif (value) {\n\t\t\t\toffsetAnimator?.cancel()\n\t\t\t\toffsetAnimator = null\n\t\t\t}\n\t\t}\n\n\toverride fun layoutDependsOn(parent: CoordinatorLayout, child: NavigationBarView, dependency: View): Boolean {\n\t\treturn dependency is AppBarLayout\n\t}\n\n\toverride fun onDependentViewChanged(\n\t\tparent: CoordinatorLayout,\n\t\tchild: NavigationBarView,\n\t\tdependency: View,\n\t): Boolean {\n\t\tval appBarSize = dependency.measureHeight()\n\t\tdyRatio = if (appBarSize > 0) {\n\t\t\tchild.measureHeight().toFloat() / appBarSize\n\t\t} else {\n\t\t\t1F\n\t\t}\n\t\treturn false\n\t}\n\n\toverride fun onStartNestedScroll(\n\t\tcoordinatorLayout: CoordinatorLayout,\n\t\tchild: NavigationBarView,\n\t\tdirectTargetChild: View,\n\t\ttarget: View,\n\t\taxes: Int,\n\t\ttype: Int,\n\t): Boolean {\n\t\tif (isPinned || axes != ViewCompat.SCROLL_AXIS_VERTICAL) {\n\t\t\treturn false\n\t\t}\n\t\tlastStartedType = type\n\t\toffsetAnimator?.cancel()\n\t\treturn true\n\t}\n\n\toverride fun onNestedPreScroll(\n\t\tcoordinatorLayout: CoordinatorLayout,\n\t\tchild: NavigationBarView,\n\t\ttarget: View,\n\t\tdx: Int,\n\t\tdy: Int,\n\t\tconsumed: IntArray,\n\t\ttype: Int,\n\t) {\n\t\tsuper.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)\n\t\tif (!isPinned) {\n\t\t\tchild.translationY = (child.translationY + (dy * dyRatio)).coerceIn(0F, child.height.toFloat())\n\t\t}\n\t}\n\n\toverride fun onStopNestedScroll(\n\t\tcoordinatorLayout: CoordinatorLayout,\n\t\tchild: NavigationBarView,\n\t\ttarget: View,\n\t\ttype: Int,\n\t) {\n\t\tif (!isPinned && (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH)) {\n\t\t\tanimateBottomNavigationVisibility(child, child.translationY < child.height / 2)\n\t\t}\n\t}\n\n\tprivate fun animateBottomNavigationVisibility(child: NavigationBarView, isVisible: Boolean) {\n\t\toffsetAnimator?.cancel()\n\t\toffsetAnimator = ValueAnimator().apply {\n\t\t\tinterpolator = DecelerateInterpolator()\n\t\t\tduration = child.context.getAnimationDuration(R.integer.config_shorterAnimTime)\n\t\t\taddUpdateListener {\n\t\t\t\tchild.translationY = it.animatedValue as Float\n\t\t\t}\n\t\t}\n\t\toffsetAnimator?.setFloatValues(\n\t\t\tchild.translationY,\n\t\t\tif (isVisible) 0F else child.height.toFloat(),\n\t\t)\n\t\toffsetAnimator?.start()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/IconsView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.widget.ImageView\nimport android.widget.LinearLayout\nimport androidx.annotation.DrawableRes\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.view.isNotEmpty\nimport androidx.core.view.isVisible\nimport org.koitharu.kotatsu.R\n\nclass IconsView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n) : LinearLayout(context, attrs) {\n\n\tprivate var iconSize = LayoutParams.WRAP_CONTENT\n\tprivate var iconSpacing = 0\n\n\tval iconsCount: Int\n\t\tget() {\n\t\t\tvar count = 0\n\t\t\trepeat(childCount) { i ->\n\t\t\t\tif (getChildAt(i).isVisible) {\n\t\t\t\t\tcount++\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn count\n\t\t}\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.IconsView) {\n\t\t\ticonSize = getDimensionPixelSize(R.styleable.IconsView_iconSize, iconSize)\n\t\t\ticonSpacing = getDimensionPixelOffset(R.styleable.IconsView_iconSpacing, iconSpacing)\n\t\t}\n\t}\n\n\tfun setIcons(icons: Iterable<Drawable>) {\n\t\tvar index = 0\n\t\tfor (icon in icons) {\n\t\t\tval imageView = (getChildAt(index) as ImageView?) ?: addImageView()\n\t\t\timageView.setImageDrawable(icon)\n\t\t\timageView.isVisible = true\n\t\t\tindex++\n\t\t}\n\t\tfor (i in index until childCount) {\n\t\t\tval imageView = getChildAt(i) as? ImageView ?: continue\n\t\t\timageView.setImageDrawable(null)\n\t\t\timageView.isVisible = false\n\t\t}\n\t}\n\n\tfun clearIcons() {\n\t\trepeat(childCount) { i ->\n\t\t\tgetChildAt(i).isVisible = false\n\t\t}\n\t}\n\n\tfun addIcon(drawable: Drawable) {\n\t\tval imageView = getNextImageView()\n\t\timageView.setImageDrawable(drawable)\n\t\timageView.isVisible = true\n\t}\n\n\tfun addIcon(@DrawableRes resId: Int) {\n\t\tval imageView = getNextImageView()\n\t\timageView.setImageResource(resId)\n\t\timageView.isVisible = true\n\t}\n\n\tprivate fun getNextImageView(): ImageView {\n\t\trepeat(childCount) { i ->\n\t\t\tval child = getChildAt(i)\n\t\t\tif (child is ImageView && !child.isVisible) {\n\t\t\t\treturn child\n\t\t\t}\n\t\t}\n\t\treturn addImageView()\n\t}\n\n\tprivate fun addImageView() = ImageView(context).also {\n\t\tit.scaleType = ImageView.ScaleType.FIT_CENTER\n\t\tval lp = LayoutParams(iconSize, iconSize)\n\t\tif (isNotEmpty()) {\n\t\t\tlp.marginStart = iconSpacing\n\t\t}\n\t\taddView(it, lp)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ListItemTextView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.content.res.TypedArray\nimport android.graphics.Color\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.InsetDrawable\nimport android.graphics.drawable.RippleDrawable\nimport android.graphics.drawable.ShapeDrawable\nimport android.graphics.drawable.shapes.RoundRectShape\nimport android.util.AttributeSet\nimport androidx.annotation.AttrRes\nimport androidx.appcompat.widget.AppCompatCheckedTextView\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.withStyledAttributes\nimport com.google.android.material.ripple.RippleUtils\nimport com.google.android.material.shape.MaterialShapeDrawable\nimport com.google.android.material.shape.ShapeAppearanceModel\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\n\n@SuppressLint(\"RestrictedApi\")\nclass ListItemTextView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = R.attr.listItemTextViewStyle,\n) : AppCompatCheckedTextView(context, attrs, defStyleAttr) {\n\n\tprivate var checkedDrawableStart: Drawable? = null\n\tprivate var checkedDrawableEnd: Drawable? = null\n\tprivate var isInitialized = false\n\tprivate var isCheckDrawablesVisible: Boolean = false\n\tprivate var defaultPaddingStart: Int = 0\n\tprivate var defaultPaddingEnd: Int = 0\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.ListItemTextView, defStyleAttr) {\n\t\t\tval itemRippleColor = getRippleColor(context)\n\t\t\tval shape = createShapeDrawable(this)\n\t\t\tval roundCorners = FloatArray(8) { resources.resolveDp(32f) }\n\t\t\tbackground = RippleDrawable(\n\t\t\t\tRippleUtils.sanitizeRippleDrawableColor(itemRippleColor),\n\t\t\t\tshape,\n\t\t\t\tShapeDrawable(RoundRectShape(roundCorners, null, null)),\n\t\t\t)\n\t\t\tcheckedDrawableStart = getDrawable(R.styleable.ListItemTextView_checkedDrawableStart)\n\t\t\tcheckedDrawableEnd = getDrawable(R.styleable.ListItemTextView_checkedDrawableEnd)\n\t\t}\n\t\tcheckedDrawableStart?.setTintList(textColors)\n\t\tcheckedDrawableEnd?.setTintList(textColors)\n\t\tdefaultPaddingStart = paddingStart\n\t\tdefaultPaddingEnd = paddingEnd\n\t\tisInitialized = true\n\t\tadjustCheckDrawables()\n\t}\n\n\toverride fun refreshDrawableState() {\n\t\tsuper.refreshDrawableState()\n\t\tadjustCheckDrawables()\n\t}\n\n\toverride fun setTextColor(colors: ColorStateList?) {\n\t\tcheckedDrawableStart?.setTintList(colors)\n\t\tcheckedDrawableEnd?.setTintList(colors)\n\t\tsuper.setTextColor(colors)\n\t}\n\n\toverride fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {\n\t\tdefaultPaddingStart = start\n\t\tdefaultPaddingEnd = end\n\t\tsuper.setPaddingRelative(start, top, end, bottom)\n\t}\n\n\toverride fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {\n\t\tval isRtl = layoutDirection == LAYOUT_DIRECTION_RTL\n\t\tdefaultPaddingStart = if (isRtl) right else left\n\t\tdefaultPaddingEnd = if (isRtl) left else right\n\t\tsuper.setPadding(left, top, right, bottom)\n\t}\n\n\tprivate fun adjustCheckDrawables() {\n\t\tif (isInitialized && isCheckDrawablesVisible != isChecked) {\n\t\t\tsetCompoundDrawablesRelativeWithIntrinsicBounds(\n\t\t\t\tif (isChecked) checkedDrawableStart else null,\n\t\t\t\tnull,\n\t\t\t\tif (isChecked) checkedDrawableEnd else null,\n\t\t\t\tnull,\n\t\t\t)\n\t\t\tsuper.setPaddingRelative(\n\t\t\t\tif (isChecked && checkedDrawableStart != null) {\n\t\t\t\t\tdefaultPaddingStart + compoundDrawablePadding\n\t\t\t\t} else defaultPaddingStart,\n\t\t\t\tpaddingTop,\n\t\t\t\tif (isChecked && checkedDrawableEnd != null) {\n\t\t\t\t\tdefaultPaddingEnd + compoundDrawablePadding\n\t\t\t\t} else defaultPaddingEnd,\n\t\t\t\tpaddingBottom,\n\t\t\t)\n\t\t\tisCheckDrawablesVisible = isChecked\n\t\t}\n\t}\n\n\tprivate fun createShapeDrawable(ta: TypedArray): InsetDrawable {\n\t\tval shapeAppearance = ShapeAppearanceModel.builder(\n\t\t\tcontext,\n\t\t\tta.getResourceId(R.styleable.ListItemTextView_shapeAppearance, 0),\n\t\t\tta.getResourceId(R.styleable.ListItemTextView_shapeAppearanceOverlay, 0),\n\t\t).build()\n\t\tval shapeDrawable = MaterialShapeDrawable(shapeAppearance)\n\t\tshapeDrawable.fillColor = ta.getColorStateList(R.styleable.ListItemTextView_backgroundFillColor)\n\t\treturn InsetDrawable(\n\t\t\tshapeDrawable,\n\t\t\tta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetLeft, 0),\n\t\t\tta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetTop, 0),\n\t\t\tta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetRight, 0),\n\t\t\tta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetBottom, 0),\n\t\t)\n\t}\n\n\tprivate fun getRippleColor(context: Context): ColorStateList {\n\t\treturn ContextCompat.getColorStateList(context, R.color.selector_overlay)\n\t\t\t?: ColorStateList.valueOf(Color.TRANSPARENT)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/MultilineEllipsizeTextView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.annotation.AttrRes\nimport com.google.android.material.textview.MaterialTextView\n\nclass MultilineEllipsizeTextView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = android.R.attr.textViewStyle,\n) : MaterialTextView(context, attrs, defStyleAttr) {\n\n\toverride fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n\t\tsuper.onSizeChanged(w, h, oldw, oldh)\n\t\tval lh = lineHeight\n\t\tmaxLines = if (lh > 0) h / lh else 1\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/NestedRecyclerView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport androidx.core.content.withStyledAttributes\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\n\nclass NestedRecyclerView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null\n) : RecyclerView(context, attrs) {\n\n\tprivate var maxHeight: Int = 0\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.NestedRecyclerView) {\n\t\t\tmaxHeight = getDimensionPixelSize(R.styleable.NestedRecyclerView_maxHeight, maxHeight)\n\t\t}\n\t}\n\n\t@SuppressLint(\"ClickableViewAccessibility\")\n\toverride fun onTouchEvent(e: MotionEvent?): Boolean {\n\t\tif (e?.actionMasked == MotionEvent.ACTION_UP) {\n\t\t\trequestDisallowInterceptTouchEvent(false)\n\t\t} else {\n\t\t\trequestDisallowInterceptTouchEvent(true)\n\t\t}\n\t\treturn super.onTouchEvent(e)\n\t}\n\n\toverride fun onMeasure(widthSpec: Int, heightSpec: Int) {\n\t\tsuper.onMeasure(\n\t\t\twidthSpec,\n\t\t\tif (maxHeight == 0) {\n\t\t\t\theightSpec\n\t\t\t} else {\n\t\t\t\tMeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)\n\t\t\t},\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SegmentedBarView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.animation.Animator\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Outline\nimport android.graphics.Paint\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewOutlineProvider\nimport androidx.annotation.ColorInt\nimport androidx.annotation.FloatRange\nimport androidx.collection.MutableFloatList\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\nimport org.koitharu.kotatsu.parsers.util.replaceWith\n\nclass SegmentedBarView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = 0,\n) : View(context, attrs, defStyleAttr), ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate val segmentsData = ArrayList<Segment>()\n\tprivate val segmentsSizes = MutableFloatList()\n\tprivate var cornerSize = 0f\n\tprivate var scaleFactor = 1f\n\tprivate var scaleAnimator: ValueAnimator? = null\n\n\tinit {\n\t\tpaint.strokeWidth = context.resources.resolveDp(0f)\n\t\toutlineProvider = OutlineProvider()\n\t\tclipToOutline = true\n\t}\n\n\toverride fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n\t\tsuper.onSizeChanged(w, h, oldw, oldh)\n\t\tcornerSize = h / 2f\n\t\tupdateSizes()\n\t}\n\n\toverride fun onDraw(canvas: Canvas) {\n\t\tif (segmentsSizes.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tval w = width.toFloat()\n\t\tvar x = w - segmentsSizes.last()\n\t\tfor (i in (0 until segmentsData.size).reversed()) {\n\t\t\tval segment = segmentsData[i]\n\t\t\tpaint.color = segment.color\n\t\t\tpaint.style = Paint.Style.FILL\n\t\t\tval segmentWidth = segmentsSizes[i]\n\t\t\tcanvas.drawRoundRect(0f, 0f, x + cornerSize, height.toFloat(), cornerSize, cornerSize, paint)\n\t\t\tpaint.style = Paint.Style.STROKE\n\t\t\tcanvas.drawRoundRect(0f, 0f, x + cornerSize, height.toFloat(), cornerSize, cornerSize, paint)\n\t\t\tx -= segmentWidth\n\t\t}\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRoundRect(0f, 0f, w, height.toFloat(), cornerSize, cornerSize, paint)\n\t}\n\n\toverride fun onAnimationStart(animation: Animator) = Unit\n\n\toverride fun onAnimationEnd(animation: Animator) {\n\t\tif (scaleAnimator === animation) {\n\t\t\tscaleAnimator = null\n\t\t}\n\t}\n\n\toverride fun onAnimationUpdate(animation: ValueAnimator) {\n\t\tscaleFactor = animation.animatedValue as Float\n\t\tupdateSizes()\n\t\tinvalidate()\n\t}\n\n\toverride fun onAnimationCancel(animation: Animator) = Unit\n\n\toverride fun onAnimationRepeat(animation: Animator) = Unit\n\n\tfun animateSegments(value: List<Segment>) {\n\t\tscaleAnimator?.cancel()\n\t\tsegmentsData.replaceWith(value)\n\t\tif (!context.isAnimationsEnabled) {\n\t\t\tscaleAnimator = null\n\t\t\tscaleFactor = 1f\n\t\t\tupdateSizes()\n\t\t\tinvalidate()\n\t\t\treturn\n\t\t}\n\t\tscaleFactor = 0f\n\t\tupdateSizes()\n\t\tinvalidate()\n\t\tval animator = ValueAnimator.ofFloat(0f, 1f)\n\t\tanimator.duration = context.getAnimationDuration(android.R.integer.config_longAnimTime)\n\t\tanimator.interpolator = FastOutSlowInInterpolator()\n\t\tanimator.addUpdateListener(this@SegmentedBarView)\n\t\tanimator.addListener(this@SegmentedBarView)\n\t\tscaleAnimator = animator\n\t\tanimator.start()\n\t}\n\n\tprivate fun updateSizes() {\n\t\tsegmentsSizes.clear()\n\t\tsegmentsSizes.ensureCapacity(segmentsData.size + 1)\n\t\tvar w = width.toFloat()\n\t\tval maxScale = (scaleFactor * (segmentsData.size - 1)).coerceAtLeast(1f)\n\t\tfor ((index, segment) in segmentsData.withIndex()) {\n\t\t\tval scale = (scaleFactor * (index + 1) / maxScale).coerceAtMost(1f)\n\t\t\tval segmentWidth = (w * segment.percent).coerceAtLeast(\n\t\t\t\tif (index == 0) height.toFloat() else cornerSize,\n\t\t\t) * scale\n\t\t\tsegmentsSizes.add(segmentWidth)\n\t\t\tw -= segmentWidth\n\t\t}\n\t\tsegmentsSizes.add(w)\n\t}\n\n\tdata class Segment(\n\t\t@FloatRange(from = 0.0, to = 1.0) val percent: Float,\n\t\t@ColorInt val color: Int,\n\t)\n\n\tprivate class OutlineProvider : ViewOutlineProvider() {\n\t\toverride fun getOutline(view: View, outline: Outline) {\n\t\t\toutline.setRoundRect(0, 0, view.width, view.height, view.height / 2f)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SelectableTextView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.os.Build\nimport android.text.Selection\nimport android.text.Spannable\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.PointerIcon\nimport androidx.annotation.AttrRes\nimport androidx.core.view.PointerIconCompat\nimport com.google.android.material.textview.MaterialTextView\n\nclass SelectableTextView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = android.R.attr.textViewStyle,\n) : MaterialTextView(context, attrs, defStyleAttr) {\n\n\tinit {\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n\t\t\tpointerIcon = PointerIcon.getSystemIcon(context, PointerIconCompat.TYPE_TEXT)\n\t\t}\n\t}\n\n\toverride fun dispatchTouchEvent(event: MotionEvent?): Boolean {\n\t\tfixSelectionRange()\n\t\treturn super.dispatchTouchEvent(event)\n\t}\n\n\t// https://stackoverflow.com/questions/22810147/error-when-selecting-text-from-textview-java-lang-indexoutofboundsexception-se\n\tprivate fun fixSelectionRange() {\n\t\tif (selectionStart < 0 || selectionEnd < 0) {\n\t\t\tval spannableText = text as? Spannable ?: return\n\t\t\tSelection.setSelection(spannableText, spannableText.length)\n\t\t}\n\t}\n\n\toverride fun scrollTo(x: Int, y: Int) {\n\t\tsuper.scrollTo(0, 0)\n\t}\n\n\tfun selectAll() {\n\t\tval spannableText = text as? Spannable ?: return\n\t\tSelection.selectAll(spannableText)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ShapeView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Outline\nimport android.graphics.Paint\nimport android.graphics.Path\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewOutlineProvider\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.graphics.withClip\nimport com.google.android.material.drawable.DrawableUtils\nimport org.koitharu.kotatsu.R\n\nclass ShapeView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = 0,\n) : View(context, attrs, defStyleAttr) {\n\n\tprivate val corners = FloatArray(8)\n\tprivate val outlinePath = Path()\n\tprivate val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG)\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.ShapeView, defStyleAttr) {\n\t\t\tval cornerSize = getDimension(R.styleable.ShapeView_cornerSize, 0f)\n\t\t\tcorners[0] = getDimension(R.styleable.ShapeView_cornerSizeTopLeft, cornerSize)\n\t\t\tcorners[1] = corners[0]\n\t\t\tcorners[2] = getDimension(R.styleable.ShapeView_cornerSizeTopRight, cornerSize)\n\t\t\tcorners[3] = corners[2]\n\t\t\tcorners[4] = getDimension(R.styleable.ShapeView_cornerSizeBottomRight, cornerSize)\n\t\t\tcorners[5] = corners[4]\n\t\t\tcorners[6] = getDimension(R.styleable.ShapeView_cornerSizeBottomLeft, cornerSize)\n\t\t\tcorners[7] = corners[6]\n\t\t\tstrokePaint.color = getColor(R.styleable.ShapeView_strokeColor, Color.TRANSPARENT)\n\t\t\tstrokePaint.strokeWidth = getDimension(R.styleable.ShapeView_strokeWidth, 0f)\n\t\t\tstrokePaint.style = Paint.Style.STROKE\n\t\t}\n\t\toutlineProvider = OutlineProvider()\n\t}\n\n\toverride fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n\t\tsuper.onSizeChanged(w, h, oldw, oldh)\n\t\tif (w != oldw || h != oldh) {\n\t\t\trebuildPath()\n\t\t}\n\t}\n\n\toverride fun draw(canvas: Canvas) {\n\t\tcanvas.withClip(outlinePath) {\n\t\t\tsuper.draw(canvas)\n\t\t}\n\t}\n\n\toverride fun onDraw(canvas: Canvas) {\n\t\tsuper.onDraw(canvas)\n\t\tif (strokePaint.strokeWidth > 0f) {\n\t\t\tcanvas.drawPath(outlinePath, strokePaint)\n\t\t}\n\t}\n\n\tprivate fun rebuildPath() {\n\t\toutlinePath.reset()\n\t\tval w = width\n\t\tval h = height\n\t\tif (w > 0 && h > 0) {\n\t\t\toutlinePath.addRoundRect(0f, 0f, w.toFloat(), h.toFloat(), corners, Path.Direction.CW)\n\t\t}\n\t}\n\n\tprivate inner class OutlineProvider : ViewOutlineProvider() {\n\n\t\t@SuppressLint(\"RestrictedApi\")\n\t\toverride fun getOutline(view: View?, outline: Outline) {\n\t\t\tval corner = corners[0]\n\t\t\tvar isRoundRect = true\n\t\t\tfor (item in corners) {\n\t\t\t\tif (item != corner) {\n\t\t\t\t\tisRoundRect = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isRoundRect) {\n\t\t\t\toutline.setRoundRect(0, 0, width, height, corner)\n\t\t\t} else {\n\t\t\t\tDrawableUtils.setOutlineToPath(outline, outlinePath)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/SlidingBottomNavigationView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.animation.TimeInterpolator\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.ViewPropertyAnimator\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StyleRes\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.isVisible\nimport androidx.customview.view.AbsSavedState\nimport androidx.interpolator.view.animation.FastOutLinearInInterpolator\nimport androidx.interpolator.view.animation.LinearOutSlowInInterpolator\nimport com.google.android.material.bottomnavigation.BottomNavigationMenuView\nimport com.google.android.material.navigation.NavigationBarView\nimport org.koitharu.kotatsu.core.util.ext.applySystemAnimatorScale\nimport org.koitharu.kotatsu.core.util.ext.measureHeight\nimport kotlin.math.max\nimport com.google.android.material.R as materialR\n\nprivate const val STATE_DOWN = 1\nprivate const val STATE_UP = 2\n\nprivate const val SLIDE_UP_ANIMATION_DURATION = 225L\nprivate const val SLIDE_DOWN_ANIMATION_DURATION = 175L\n\nprivate const val MAX_ITEM_COUNT = 6\n\nclass SlidingBottomNavigationView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = materialR.attr.bottomNavigationStyle,\n\t@StyleRes defStyleRes: Int = materialR.style.Widget_Design_BottomNavigationView,\n) : NavigationBarView(context, attrs, defStyleAttr, defStyleRes),\n\tCoordinatorLayout.AttachedBehavior {\n\n\tprivate var currentAnimator: ViewPropertyAnimator? = null\n\n\tprivate var currentState = STATE_UP\n\tprivate var behavior = HideBottomNavigationOnScrollBehavior()\n\n\tvar isPinned: Boolean\n\t\tget() = behavior.isPinned\n\t\tset(value) {\n\t\t\tbehavior.isPinned = value\n\t\t\tif (value) {\n\t\t\t\ttranslationX = 0f\n\t\t\t}\n\t\t}\n\n\tval isShownOrShowing: Boolean\n\t\tget() = isVisible && currentState == STATE_UP\n\n\toverride fun getBehavior(): CoordinatorLayout.Behavior<*> {\n\t\treturn behavior\n\t}\n\n\t/** From BottomNavigationView **/\n\n\t@SuppressLint(\"ClickableViewAccessibility\")\n\toverride fun onTouchEvent(event: MotionEvent): Boolean {\n\t\tsuper.onTouchEvent(event)\n\t\t// Consume all events to avoid views under the BottomNavigationView from receiving touch events.\n\t\treturn true\n\t}\n\n\toverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\t\tval minHeightSpec = makeMinHeightSpec(heightMeasureSpec)\n\t\tsuper.onMeasure(widthMeasureSpec, minHeightSpec)\n\t\tif (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {\n\t\t\tsetMeasuredDimension(\n\t\t\t\tmeasuredWidth,\n\t\t\t\tmax(\n\t\t\t\t\tmeasuredHeight,\n\t\t\t\t\tsuggestedMinimumHeight + paddingTop + paddingBottom,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun makeMinHeightSpec(measureSpec: Int): Int {\n\t\tvar minHeight = suggestedMinimumHeight\n\t\tif (MeasureSpec.getMode(measureSpec) != MeasureSpec.EXACTLY && minHeight > 0) {\n\t\t\tminHeight += paddingTop + paddingBottom\n\n\t\t\treturn MeasureSpec.makeMeasureSpec(\n\t\t\t\tmax(MeasureSpec.getSize(measureSpec), minHeight), MeasureSpec.AT_MOST,\n\t\t\t)\n\t\t}\n\n\t\treturn measureSpec\n\t}\n\n\toverride fun getMaxItemCount(): Int = MAX_ITEM_COUNT\n\n\t@SuppressLint(\"RestrictedApi\")\n\toverride fun createNavigationBarMenuView(context: Context) = BottomNavigationMenuView(context)\n\n\t/** End **/\n\n\toverride fun onSaveInstanceState(): Parcelable {\n\t\tval superState = super.onSaveInstanceState()\n\t\treturn SavedState(superState, currentState, translationY)\n\t}\n\n\toverride fun onRestoreInstanceState(state: Parcelable?) {\n\t\tif (state is SavedState) {\n\t\t\tsuper.onRestoreInstanceState(state.superState)\n\t\t\tsuper.setTranslationY(state.translationY)\n\t\t\tcurrentState = state.currentState\n\t\t} else {\n\t\t\tsuper.onRestoreInstanceState(state)\n\t\t}\n\t}\n\n\toverride fun setTranslationY(translationY: Float) {\n\t\t// Disallow translation change when state down\n\t\tif (currentState != STATE_DOWN) {\n\t\t\tsuper.setTranslationY(translationY)\n\t\t}\n\t}\n\n\toverride fun setMinimumHeight(minHeight: Int) {\n\t\tsuper.setMinimumHeight(minHeight)\n\t\tgetChildAt(0)?.minimumHeight = minHeight\n\t}\n\n\tfun show() {\n\t\tif (currentState == STATE_UP) {\n\t\t\treturn\n\t\t}\n\t\tcurrentAnimator?.cancel()\n\t\tclearAnimation()\n\n\t\tcurrentState = STATE_UP\n\t\tanimateTranslation(\n\t\t\t0F,\n\t\t\tSLIDE_UP_ANIMATION_DURATION,\n\t\t\tLinearOutSlowInInterpolator(),\n\t\t)\n\t}\n\n\tfun hide() {\n\t\tif (currentState == STATE_DOWN) {\n\t\t\treturn\n\t\t}\n\t\tcurrentAnimator?.cancel()\n\t\tclearAnimation()\n\n\t\tcurrentState = STATE_DOWN\n\t\tval target = measureHeight()\n\t\tif (target == 0) {\n\t\t\treturn\n\t\t}\n\t\tanimateTranslation(\n\t\t\ttarget.toFloat(),\n\t\t\tSLIDE_DOWN_ANIMATION_DURATION,\n\t\t\tFastOutLinearInInterpolator(),\n\t\t)\n\t}\n\n\tfun showOrHide(show: Boolean) {\n\t\tif (show) {\n\t\t\tshow()\n\t\t} else {\n\t\t\thide()\n\t\t}\n\t}\n\n\tprivate fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) {\n\t\tcurrentAnimator = animate()\n\t\t\t.translationY(targetY)\n\t\t\t.setInterpolator(interpolator)\n\t\t\t.setDuration(duration)\n\t\t\t.applySystemAnimatorScale(context)\n\t\t\t.setListener(\n\t\t\t\tobject : AnimatorListenerAdapter() {\n\t\t\t\t\toverride fun onAnimationEnd(animation: Animator) {\n\t\t\t\t\t\tcurrentAnimator = null\n\t\t\t\t\t\tpostInvalidate()\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t}\n\n\tinternal class SavedState : AbsSavedState {\n\n\t\tvar currentState = STATE_UP\n\t\tvar translationY = 0F\n\n\t\tconstructor(superState: Parcelable, currentState: Int, translationY: Float) : super(superState) {\n\t\t\tthis.currentState = currentState\n\t\t\tthis.translationY = translationY\n\t\t}\n\n\t\tconstructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {\n\t\t\tcurrentState = source.readInt()\n\t\t\ttranslationY = source.readFloat()\n\t\t}\n\n\t\toverride fun writeToParcel(out: Parcel, flags: Int) {\n\t\t\tsuper.writeToParcel(out, flags)\n\t\t\tout.writeInt(currentState)\n\t\t\tout.writeFloat(translationY)\n\t\t}\n\n\t\tcompanion object {\n\n\t\t\t@Suppress(\"unused\")\n\t\t\t@JvmField\n\t\t\tval CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {\n\t\t\t\toverride fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader)\n\n\t\t\t\toverride fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/StackLayout.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.AttrRes\nimport androidx.core.view.children\nimport androidx.core.view.isEmpty\nimport androidx.core.view.isGone\n\nopen class StackLayout @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : ViewGroup(context, attrs, defStyleAttr) {\n\n\tprivate val visibleChildren = ArrayList<View>()\n\n\toverride fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {\n\t\tval w = r - l - paddingLeft - paddingRight\n\t\tval h = b - t - paddingTop - paddingBottom\n\t\tvisibleChildren.clear()\n\t\tchildren.filterNotTo(visibleChildren) { it.isGone }\n\t\tif (w <= 0 || h <= 0 || visibleChildren.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tval xStep = w / (visibleChildren.size + 1)\n\t\tval yStep = h / (visibleChildren.size + 1)\n\t\tval maxW = w\n\t\tval maxH = h\n\t\tval total = visibleChildren.size\n\t\tfor ((index, child) in visibleChildren.withIndex()) {\n\t\t\tvar cx = paddingLeft + xStep * (total - index)\n\t\t\tvar cy = paddingTop + yStep * (index + 1)\n\t\t\tval rx = child.measuredWidth.coerceAtMost(maxW) / 2\n\t\t\tval ry = child.measuredHeight.coerceAtMost(maxH) / 2\n\t\t\tif (cx < rx) {\n\t\t\t\tcx = rx\n\t\t\t}\n\t\t\tif (cy < ry) {\n\t\t\t\tcy = ry\n\t\t\t}\n\t\t\tif (cx + rx > width) {\n\t\t\t\tcx = width - rx\n\t\t\t}\n\t\t\tif (cy + ry > height) {\n\t\t\t\tcy = height - ry\n\t\t\t}\n\t\t\tchild.layout(cx - rx, cy - ry, cx + rx, cy + ry)\n\t\t}\n\t}\n\n\toverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\t\tmeasureChildren(widthMeasureSpec, heightMeasureSpec)\n\t\tif (isEmpty()) {\n\t\t\tsuper.onMeasure(widthMeasureSpec, heightMeasureSpec)\n\t\t\treturn\n\t\t}\n\n\t\tvar h = 0\n\t\tvar w = 0\n\t\tfor (i in 0 until childCount) {\n\t\t\tval child = getChildAt(i)\n\t\t\tif (child.isGone) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tval mw = child.measuredWidth\n\t\t\tval mh = child.measuredHeight\n\t\t\tif (h == 0 || w == 0) {\n\t\t\t\th = mh\n\t\t\t\tw = mw\n\t\t\t} else {\n\t\t\t\th += mh / 2\n\t\t\t\tw += mw / 2\n\t\t\t}\n\t\t}\n\t\th += paddingTop + paddingBottom\n\t\tw += paddingLeft + paddingRight\n\t\tsetMeasuredDimension(\n\t\t\tresolveSizeAndState(w, widthMeasureSpec, 0),\n\t\t\tresolveSizeAndState(h, heightMeasureSpec, 0),\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/TipView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.graphics.Outline\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewOutlineProvider\nimport android.widget.LinearLayout\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.view.isVisible\nimport androidx.core.view.setPadding\nimport com.google.android.material.shape.MaterialShapeDrawable\nimport com.google.android.material.shape.ShapeAppearanceModel\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.getDrawableCompat\nimport org.koitharu.kotatsu.core.util.ext.getThemeColorStateList\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ViewTipBinding\n\nclass TipView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = R.attr.tipViewStyle,\n) : LinearLayout(context, attrs, defStyleAttr), View.OnClickListener {\n\n\tprivate val binding = ViewTipBinding.inflate(LayoutInflater.from(context), this)\n\n\tvar title: CharSequence?\n\t\tget() = binding.textViewTitle.text\n\t\tset(value) {\n\t\t\tbinding.textViewTitle.text = value\n\t\t}\n\n\tvar text: CharSequence?\n\t\tget() = binding.textViewBody.text\n\t\tset(value) {\n\t\t\tbinding.textViewBody.text = value\n\t\t}\n\n\tvar icon: Drawable?\n\t\tget() = binding.textViewTitle.drawableStart\n\t\tset(value) {\n\t\t\tbinding.textViewTitle.drawableStart = value\n\t\t}\n\n\tvar primaryButtonText: CharSequence?\n\t\tget() = binding.buttonPrimary.textAndVisible\n\t\tset(value) {\n\t\t\tbinding.buttonPrimary.textAndVisible = value\n\t\t}\n\n\tvar secondaryButtonText: CharSequence?\n\t\tget() = binding.buttonSecondary.textAndVisible\n\t\tset(value) {\n\t\t\tbinding.buttonSecondary.textAndVisible = value\n\t\t}\n\n\tvar onButtonClickListener: OnButtonClickListener? = null\n\n\tinit {\n\t\torientation = VERTICAL\n\t\tsetPadding(context.resources.getDimensionPixelOffset(R.dimen.margin_normal))\n\t\tcontext.withStyledAttributes(attrs, R.styleable.TipView, defStyleAttr) {\n\t\t\ttitle = getText(R.styleable.TipView_title)\n\t\t\ttext = getText(R.styleable.TipView_android_text)\n\t\t\ticon = getDrawableCompat(context, R.styleable.TipView_icon)\n\t\t\tprimaryButtonText = getString(R.styleable.TipView_primaryButtonText)\n\t\t\tsecondaryButtonText = getString(R.styleable.TipView_secondaryButtonText)\n\t\t\tval shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyleAttr, 0).build()\n\t\t\tbackground = MaterialShapeDrawable(shapeAppearanceModel).also {\n\t\t\t\tit.fillColor = getColorStateList(R.styleable.TipView_cardBackgroundColor)\n\t\t\t\t\t?: context.getThemeColorStateList(com.google.android.material.R.attr.colorSurfaceContainerHigh)\n\t\t\t\tit.strokeWidth = getDimension(R.styleable.TipView_strokeWidth, 0f)\n\t\t\t\tit.strokeColor = getColorStateList(R.styleable.TipView_strokeColor)\n\t\t\t\tit.elevation = getDimension(R.styleable.TipView_elevation, 0f)\n\t\t\t}\n\t\t\toutlineProvider = OutlineProvider(shapeAppearanceModel)\n\t\t}\n\t\tbinding.buttonPrimary.setOnClickListener(this)\n\t\tbinding.buttonSecondary.setOnClickListener(this)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_primary -> onButtonClickListener?.onPrimaryButtonClick(this)\n\t\t\tR.id.button_secondary -> onButtonClickListener?.onSecondaryButtonClick(this)\n\t\t}\n\t}\n\n\tfun setTitle(@StringRes resId: Int) {\n\t\tbinding.textViewTitle.setText(resId)\n\t}\n\n\tfun setText(@StringRes resId: Int) {\n\t\tbinding.textViewBody.setText(resId)\n\t}\n\n\tfun setPrimaryButtonText(@StringRes resId: Int) {\n\t\tbinding.buttonPrimary.setTextAndVisible(resId)\n\t\tupdateButtonsLayout()\n\t}\n\n\tfun setSecondaryButtonText(@StringRes resId: Int) {\n\t\tbinding.buttonSecondary.setTextAndVisible(resId)\n\t\tupdateButtonsLayout()\n\t}\n\n\tfun setIcon(@DrawableRes resId: Int) {\n\t\ticon = ContextCompat.getDrawable(context, resId)\n\t}\n\n\tprivate fun updateButtonsLayout() {\n\t\tbinding.layoutButtons.isVisible = binding.buttonPrimary.isVisible || binding.buttonSecondary.isVisible\n\t}\n\n\tinterface OnButtonClickListener {\n\n\t\tfun onPrimaryButtonClick(tipView: TipView)\n\n\t\tfun onSecondaryButtonClick(tipView: TipView)\n\t}\n\n\tprivate class OutlineProvider(\n\t\tshapeAppearanceModel: ShapeAppearanceModel,\n\t) : ViewOutlineProvider() {\n\n\t\tprivate val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel)\n\t\toverride fun getOutline(view: View, outline: Outline) {\n\t\t\tshapeDrawable.setBounds(0, 0, view.width, view.height)\n\t\t\tshapeDrawable.getOutline(outline)\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/TouchBlockLayout.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.widget.FrameLayout\n\nclass TouchBlockLayout @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : FrameLayout(context, attrs) {\n\n    var isTouchEventsAllowed = true\n\n    override fun onInterceptTouchEvent(\n        ev: MotionEvent?\n    ): Boolean = if (isTouchEventsAllowed) {\n        super.onInterceptTouchEvent(ev)\n    } else {\n        true\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/TwoLinesItemView.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.content.res.TypedArray\nimport android.graphics.Color\nimport android.graphics.drawable.InsetDrawable\nimport android.graphics.drawable.RippleDrawable\nimport android.graphics.drawable.ShapeDrawable\nimport android.graphics.drawable.shapes.RoundRectShape\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.Checkable\nimport android.widget.LinearLayout\nimport androidx.annotation.AttrRes\nimport androidx.annotation.DrawableRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.widget.ImageViewCompat\nimport androidx.core.widget.TextViewCompat\nimport com.google.android.material.ripple.RippleUtils\nimport com.google.android.material.shape.MaterialShapeDrawable\nimport com.google.android.material.shape.ShapeAppearanceModel\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getDrawableCompat\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ViewTwoLinesItemBinding\n\n@SuppressLint(\"RestrictedApi\")\nclass TwoLinesItemView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : LinearLayout(context, attrs, defStyleAttr), Checkable {\n\n\tprivate val binding = ViewTwoLinesItemBinding.inflate(LayoutInflater.from(context), this)\n\n\tvar title: CharSequence?\n\t\tget() = binding.title.text\n\t\tset(value) {\n\t\t\tbinding.title.text = value\n\t\t}\n\n\tvar subtitle: CharSequence?\n\t\tget() = binding.subtitle.textAndVisible\n\t\tset(value) {\n\t\t\tbinding.subtitle.textAndVisible = value\n\t\t}\n\n\tvar isButtonEnabled: Boolean\n\t\tget() = binding.button.isEnabled\n\t\tset(value) {\n\t\t\tbinding.button.isEnabled = value\n\t\t}\n\n\tinit {\n\t\tvar textColors: ColorStateList? = null\n\t\tcontext.withStyledAttributes(\n\t\t\tset = attrs,\n\t\t\tattrs = R.styleable.TwoLinesItemView,\n\t\t\tdefStyleAttr = defStyleAttr,\n\t\t\tdefStyleRes = R.style.Widget_Kotatsu_TwoLinesItemView,\n\t\t) {\n\t\t\tval itemRippleColor = getRippleColor(context)\n\t\t\tval shape = createShapeDrawable(this)\n\t\t\tval roundCorners = FloatArray(8) { resources.resolveDp(16f) }\n\t\t\tbackground = RippleDrawable(\n\t\t\t\tRippleUtils.sanitizeRippleDrawableColor(itemRippleColor),\n\t\t\t\tshape,\n\t\t\t\tShapeDrawable(RoundRectShape(roundCorners, null, null)),\n\t\t\t)\n\t\t\tval drawablePadding = getDimensionPixelOffset(R.styleable.TwoLinesItemView_android_drawablePadding, 0)\n\t\t\tbinding.layoutText.updateLayoutParams<MarginLayoutParams> { marginStart = drawablePadding }\n\t\t\tsetIconResource(getResourceId(R.styleable.TwoLinesItemView_icon, 0))\n\t\t\tbinding.title.text = getText(R.styleable.TwoLinesItemView_title)\n\t\t\tbinding.subtitle.textAndVisible = getText(R.styleable.TwoLinesItemView_subtitle)\n\t\t\ttextColors = getColorStateList(R.styleable.TwoLinesItemView_android_textColor)\n\t\t\tval textAppearanceFallback = androidx.appcompat.R.style.TextAppearance_AppCompat\n\t\t\tTextViewCompat.setTextAppearance(\n\t\t\t\tbinding.title,\n\t\t\t\tgetResourceId(R.styleable.TwoLinesItemView_titleTextAppearance, textAppearanceFallback),\n\t\t\t)\n\t\t\tTextViewCompat.setTextAppearance(\n\t\t\t\tbinding.subtitle,\n\t\t\t\tgetResourceId(R.styleable.TwoLinesItemView_subtitleTextAppearance, textAppearanceFallback),\n\t\t\t)\n\t\t\tbinding.icon.isChecked = getBoolean(R.styleable.TwoLinesItemView_android_checked, false)\n\t\t\tval button = getDrawableCompat(context, R.styleable.TwoLinesItemView_android_button)\n\t\t\tbinding.button.setImageDrawable(button)\n\t\t\tbinding.button.isVisible = button != null\n\t\t}\n\t\tif (textColors == null) {\n\t\t\ttextColors = binding.title.textColors\n\t\t}\n\t\tbinding.title.setTextColor(textColors)\n\t\tbinding.subtitle.setTextColor(textColors)\n\t\tImageViewCompat.setImageTintList(binding.icon, textColors)\n\t}\n\n\toverride fun isChecked() = binding.icon.isChecked\n\n\toverride fun toggle() = binding.icon.toggle()\n\n\toverride fun setChecked(checked: Boolean) {\n\t\tbinding.icon.isChecked = checked\n\t}\n\n\tfun setOnButtonClickListener(listener: OnClickListener?) = binding.button.setOnClickListener(listener)\n\n\tfun setIconResource(@DrawableRes resId: Int) {\n\t\tbinding.icon.setImageResource(resId)\n\t}\n\n\tprivate fun createShapeDrawable(ta: TypedArray): InsetDrawable {\n\t\tval shapeAppearance = ShapeAppearanceModel.builder(\n\t\t\tcontext,\n\t\t\tta.getResourceId(R.styleable.TwoLinesItemView_shapeAppearance, 0),\n\t\t\tta.getResourceId(R.styleable.TwoLinesItemView_shapeAppearanceOverlay, 0),\n\t\t).build()\n\t\tval shapeDrawable = MaterialShapeDrawable(shapeAppearance)\n\t\tshapeDrawable.fillColor = ta.getColorStateList(R.styleable.TwoLinesItemView_backgroundFillColor)\n\t\treturn InsetDrawable(\n\t\t\tshapeDrawable,\n\t\t\tta.getDimensionPixelOffset(R.styleable.TwoLinesItemView_android_insetLeft, 0),\n\t\t\tta.getDimensionPixelOffset(R.styleable.TwoLinesItemView_android_insetTop, 0),\n\t\t\tta.getDimensionPixelOffset(R.styleable.TwoLinesItemView_android_insetRight, 0),\n\t\t\tta.getDimensionPixelOffset(R.styleable.TwoLinesItemView_android_insetBottom, 0),\n\t\t)\n\t}\n\n\tprivate fun getRippleColor(context: Context): ColorStateList {\n\t\treturn ContextCompat.getColorStateList(context, R.color.selector_overlay)\n\t\t\t?: ColorStateList.valueOf(Color.TRANSPARENT)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/WindowInsetHolder.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.View\nimport android.view.WindowInsets\nimport android.widget.FrameLayout\nimport android.widget.LinearLayout\nimport androidx.annotation.AttrRes\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.WindowInsetsCompat\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.start\n\nclass WindowInsetHolder @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : View(context, attrs, defStyleAttr) {\n\n\tprivate var desiredHeight = 0\n\tprivate var desiredWidth = 0\n\n\toverride fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {\n\t\tval barsInsets = WindowInsetsCompat.toWindowInsetsCompat(insets, this)\n\t\t\t.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tval gravity = getLayoutGravity()\n\t\tval newWidth = when (gravity and Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {\n\t\t\tGravity.START -> barsInsets.start(this)\n\t\t\tGravity.END -> barsInsets.end(this)\n\t\t\telse -> 0\n\t\t}\n\t\tval newHeight = when (gravity and Gravity.VERTICAL_GRAVITY_MASK) {\n\t\t\tGravity.TOP -> barsInsets.top\n\t\t\tGravity.BOTTOM -> barsInsets.bottom\n\t\t\telse -> 0\n\t\t}\n\t\tif (newWidth != desiredWidth || newHeight != desiredHeight) {\n\t\t\tdesiredWidth = newWidth\n\t\t\tdesiredHeight = newHeight\n\t\t\trequestLayout()\n\t\t}\n\t\treturn super.onApplyWindowInsets(insets)\n\t}\n\n\toverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\t\tval widthMode = MeasureSpec.getMode(widthMeasureSpec)\n\t\tval widthSize = MeasureSpec.getSize(widthMeasureSpec)\n\t\tval heightMode = MeasureSpec.getMode(heightMeasureSpec)\n\t\tval heightSize = MeasureSpec.getSize(heightMeasureSpec)\n\n\t\tval width: Int = when (widthMode) {\n\t\t\tMeasureSpec.EXACTLY -> widthSize\n\t\t\tMeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)\n\t\t\telse -> desiredWidth\n\t\t}\n\t\tval height = when (heightMode) {\n\t\t\tMeasureSpec.EXACTLY -> heightSize\n\t\t\tMeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)\n\t\t\telse -> desiredHeight\n\t\t}\n\t\tsetMeasuredDimension(width, height)\n\t}\n\n\tprivate fun getLayoutGravity(): Int {\n\t\treturn when (val lp = layoutParams) {\n\t\t\tis FrameLayout.LayoutParams -> lp.gravity\n\t\t\tis LinearLayout.LayoutParams -> lp.gravity\n\t\t\tis CoordinatorLayout.LayoutParams -> lp.gravity\n\t\t\telse -> Gravity.NO_GRAVITY\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ZoomControl.kt",
    "content": "package org.koitharu.kotatsu.core.ui.widgets\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport com.google.android.material.button.MaterialButtonGroup\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.databinding.ViewZoomBinding\n\nclass ZoomControl @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n) : MaterialButtonGroup(context, attrs), View.OnClickListener {\n\n\tprivate val binding = ViewZoomBinding.inflate(LayoutInflater.from(context), this)\n\n\tvar listener: ZoomControlListener? = null\n\n\tinit {\n\t\tbinding.buttonZoomIn.setOnClickListener(this)\n\t\tbinding.buttonZoomOut.setOnClickListener(this)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_zoom_in -> listener?.onZoomIn()\n\t\t\tR.id.button_zoom_out -> listener?.onZoomOut()\n\t\t}\n\t}\n\n\tinterface ZoomControlListener {\n\n\t\tfun onZoomIn()\n\n\t\tfun onZoomOut()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/AcraCoroutineErrorHandler.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.report\nimport kotlin.coroutines.AbstractCoroutineContextElement\nimport kotlin.coroutines.CoroutineContext\n\nclass AcraCoroutineErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),\n\tCoroutineExceptionHandler {\n\n\toverride fun handleException(context: CoroutineContext, exception: Throwable) {\n\t\texception.printStackTraceDebug()\n\t\texception.report()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/AcraScreenLogger.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Bundle\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks\nimport org.acra.ACRA\nimport org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks\nimport java.time.LocalTime\nimport java.time.temporal.ChronoUnit\nimport java.util.WeakHashMap\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass AcraScreenLogger @Inject constructor() : FragmentLifecycleCallbacks(), DefaultActivityLifecycleCallbacks {\n\n\tprivate val keys = WeakHashMap<Any, String>()\n\n\toverride fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {\n\t\tsuper.onFragmentAttached(fm, f, context)\n\t\tACRA.errorReporter.putCustomData(f.key(), f.arguments.contentToString())\n\t}\n\n\toverride fun onFragmentDetached(fm: FragmentManager, f: Fragment) {\n\t\tsuper.onFragmentDetached(fm, f)\n\t\tACRA.errorReporter.removeCustomData(f.key())\n\t\tkeys.remove(f)\n\t}\n\n\toverride fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n\t\tsuper.onActivityCreated(activity, savedInstanceState)\n\t\tACRA.errorReporter.putCustomData(activity.key(), activity.intent.extras.contentToString())\n\t\t(activity as? FragmentActivity)?.supportFragmentManager?.registerFragmentLifecycleCallbacks(this, true)\n\t}\n\n\toverride fun onActivityDestroyed(activity: Activity) {\n\t\tsuper.onActivityDestroyed(activity)\n\t\tACRA.errorReporter.removeCustomData(activity.key())\n\t\tkeys.remove(activity)\n\t}\n\n\tprivate fun Any.key() = keys.getOrPut(this) {\n\t\tval time = LocalTime.now().truncatedTo(ChronoUnit.SECONDS)\n\t\t\"$time: ${javaClass.simpleName}\"\n\t}\n\n\t@Suppress(\"DEPRECATION\")\n\tprivate fun Bundle?.contentToString() = this?.keySet()?.joinToString { k ->\n\t\tval v = get(k)\n\t\t\"$k=$v\"\n\t} ?: toString()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/AlphanumComparator.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nclass AlphanumComparator : Comparator<String> {\n\n\toverride fun compare(s1: String?, s2: String?): Int {\n\t\tif (s1 == null || s2 == null) {\n\t\t\treturn 0\n\t\t}\n\t\tvar thisMarker = 0\n\t\tvar thatMarker = 0\n\t\tval s1Length = s1.length\n\t\tval s2Length = s2.length\n\t\twhile (thisMarker < s1Length && thatMarker < s2Length) {\n\t\t\tval thisChunk = getChunk(s1, s1Length, thisMarker)\n\t\t\tthisMarker += thisChunk.length\n\t\t\tval thatChunk = getChunk(s2, s2Length, thatMarker)\n\t\t\tthatMarker += thatChunk.length\n\t\t\t// If both chunks contain numeric characters, sort them numerically\n\t\t\tvar result: Int\n\t\t\tif (thisChunk[0].isDigit() && thatChunk[0].isDigit()) { // Simple chunk comparison by length.\n\t\t\t\tval thisChunkLength = thisChunk.length\n\t\t\t\tresult = thisChunkLength - thatChunk.length\n\t\t\t\t// If equal, the first different number counts\n\t\t\t\tif (result == 0) {\n\t\t\t\t\tfor (i in 0 until thisChunkLength) {\n\t\t\t\t\t\tresult = thisChunk[i] - thatChunk[i]\n\t\t\t\t\t\tif (result != 0) {\n\t\t\t\t\t\t\treturn result\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tresult = thisChunk.compareTo(thatChunk)\n\t\t\t}\n\t\t\tif (result != 0) return result\n\t\t}\n\t\treturn s1Length - s2Length\n\t}\n\n\tprivate fun getChunk(s: String, slength: Int, cmarker: Int): String {\n\t\tvar marker = cmarker\n\t\tval chunk = StringBuilder()\n\t\tvar c = s[marker]\n\t\tchunk.append(c)\n\t\tmarker++\n\t\tif (c.isDigit()) {\n\t\t\twhile (marker < slength) {\n\t\t\t\tc = s[marker]\n\t\t\t\tif (!c.isDigit()) break\n\t\t\t\tchunk.append(c)\n\t\t\t\tmarker++\n\t\t\t}\n\t\t} else {\n\t\t\twhile (marker < slength) {\n\t\t\t\tc = s[marker]\n\t\t\t\tif (c.isDigit()) break\n\t\t\t\tchunk.append(c)\n\t\t\t\tmarker++\n\t\t\t}\n\t\t}\n\t\treturn chunk.toString()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/CancellableSource.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.ensureActive\nimport okio.Buffer\nimport okio.ForwardingSource\nimport okio.Source\n\nclass CancellableSource(\n\tprivate val job: Job?,\n\tdelegate: Source,\n) : ForwardingSource(delegate) {\n\n\toverride fun read(sink: Buffer, byteCount: Long): Long {\n\t\tjob?.ensureActive()\n\t\treturn super.read(sink, byteCount)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/CloseableSequence.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\ninterface CloseableSequence<T> : Sequence<T>, AutoCloseable\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeResult.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nclass CompositeResult private constructor(\n\tprivate var successCount: Int,\n\tprivate val errors: List<Throwable>,\n) {\n\n\tval size: Int\n\t\tget() = successCount + errors.size\n\n\tval failures: List<Throwable>\n\t\tget() = errors\n\n\tval isEmpty: Boolean\n\t\tget() = errors.isEmpty() && successCount == 0\n\n\tval isAllSuccess: Boolean\n\t\tget() = errors.isEmpty()\n\n\tval isAllFailed: Boolean\n\t\tget() = successCount == 0 && errors.isNotEmpty()\n\n\toperator fun plus(result: Result<*>): CompositeResult = CompositeResult(\n\t\tsuccessCount = successCount + if (result.isSuccess) 1 else 0,\n\t\terrors = errors + listOfNotNull(result.exceptionOrNull()),\n\t)\n\n\toperator fun plus(other: CompositeResult): CompositeResult = CompositeResult(\n\t\tsuccessCount = successCount + other.successCount,\n\t\terrors = errors + other.errors,\n\t)\n\n\toverride fun equals(other: Any?): Boolean {\n\t\tif (this === other) return true\n\t\tif (javaClass != other?.javaClass) return false\n\n\t\tother as CompositeResult\n\n\t\tif (successCount != other.successCount) return false\n\t\tif (errors != other.errors) return false\n\n\t\treturn true\n\t}\n\n\toverride fun hashCode(): Int {\n\t\tvar result = successCount\n\t\tresult = 31 * result + errors.hashCode()\n\t\treturn result\n\t}\n\n\tcompanion object {\n\n\t\tval EMPTY = CompositeResult(0, emptyList())\n\n\t\tfun success() = CompositeResult(1, emptyList())\n\n\t\tfun failure(error: Throwable) = CompositeResult(0, listOf(error))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ContinuationResumeRunnable.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\n\nclass ContinuationResumeRunnable(\n\tprivate val continuation: Continuation<Unit>,\n) : Runnable {\n\n\toverride fun run() {\n\t\tcontinuation.resume(Unit)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.content.Context\nimport android.text.Editable\nimport android.widget.EditText\nimport androidx.annotation.CallSuper\nimport org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport java.lang.ref.WeakReference\n\nabstract class EditTextValidator : DefaultTextWatcher {\n\n\tprivate var editTextRef: WeakReference<EditText>? = null\n\n\tprotected val context: Context\n\t\tget() = checkNotNull(editTextRef?.get()?.context) {\n\t\t\t\"EditTextValidator is not attached to EditText\"\n\t\t}\n\n\t@CallSuper\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tval editText = editTextRef?.get() ?: return\n\t\tval newText = s?.toString().orEmpty()\n\t\tval result = runCatching {\n\t\t\tvalidate(newText)\n\t\t}.getOrElse { e ->\n\t\t\tValidationResult.Failed(e.getDisplayMessage(editText.resources))\n\t\t}\n\t\teditText.error = when (result) {\n\t\t\tis ValidationResult.Failed -> result.message\n\t\t\tValidationResult.Success -> null\n\t\t}\n\t}\n\n\tfun attachToEditText(editText: EditText) {\n\t\teditTextRef = WeakReference(editText)\n\t\teditText.removeTextChangedListener(this)\n\t\teditText.addTextChangedListener(this)\n\t\tafterTextChanged(editText.text)\n\t}\n\n\tabstract fun validate(text: String): ValidationResult\n\n\tsealed class ValidationResult {\n\n\t\tobject Success : ValidationResult()\n\n\t\tclass Failed(val message: CharSequence) : ValidationResult()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/Event.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport kotlinx.coroutines.flow.FlowCollector\n\nclass Event<T>(\n\tprivate val data: T,\n) {\n\tprivate var isConsumed = false\n\n\tsuspend fun consume(collector: FlowCollector<T>) {\n\t\tif (!isConsumed) {\n\t\t\tisConsumed = true\n\t\t\tcollector.emit(data)\n\t\t}\n\t}\n\n\toverride fun equals(other: Any?): Boolean {\n\t\tif (this === other) return true\n\t\tif (javaClass != other?.javaClass) return false\n\n\t\tother as Event<*>\n\n\t\tif (data != other.data) return false\n\t\treturn isConsumed == other.isConsumed\n\t}\n\n\toverride fun hashCode(): Int {\n\t\tvar result = data?.hashCode() ?: 0\n\t\tresult = 31 * result + isConsumed.hashCode()\n\t\treturn result\n\t}\n\n\toverride fun toString(): String {\n\t\treturn \"Event(data=$data, isConsumed=$isConsumed)\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/FileSize.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.content.Context\nimport org.koitharu.kotatsu.R\nimport java.text.DecimalFormat\nimport kotlin.math.log10\nimport kotlin.math.pow\n\nenum class FileSize(private val multiplier: Int) {\n\n\tBYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024);\n\n\tfun convert(amount: Long, target: FileSize): Long = amount * multiplier / target.multiplier\n\n\tfun format(context: Context, amount: Long): String {\n\t\tval bytes = amount * multiplier\n\t\tval units = context.getString(R.string.text_file_sizes).split('|')\n\t\tif (bytes <= 0) {\n\t\t\treturn \"0 ${units.first()}\"\n\t\t}\n\t\tval digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt()\n\t\treturn buildString {\n\t\t\tappend(\n\t\t\t\tDecimalFormat(\"#,##0.#\").format(\n\t\t\t\t\tbytes / 1024.0.pow(digitGroups.toDouble()),\n\t\t\t\t),\n\t\t\t)\n\t\t\tval unit = units.getOrNull(digitGroups)\n\t\t\tif (unit != null) {\n\t\t\t\tappend(' ')\n\t\t\t\tappend(unit)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/IdlingDetector.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\n\nclass IdlingDetector(\n\tprivate val timeoutMs: Long,\n\tprivate val callback: Callback,\n) : DefaultLifecycleObserver {\n\n\tprivate val handler = Handler(Looper.getMainLooper())\n\tprivate val idleRunnable = Runnable {\n\t\tcallback.onIdle()\n\t}\n\n\tfun bindToLifecycle(owner: LifecycleOwner) {\n\t\towner.lifecycle.addObserver(this)\n\t}\n\n\tfun onUserInteraction() {\n\t\thandler.removeCallbacks(idleRunnable)\n\t\thandler.postDelayed(idleRunnable, timeoutMs)\n\t}\n\n\toverride fun onDestroy(owner: LifecycleOwner) {\n\t\tsuper.onDestroy(owner)\n\t\towner.lifecycle.removeObserver(this)\n\t\thandler.removeCallbacks(idleRunnable)\n\t}\n\n\tfun interface Callback {\n\n\t\tfun onIdle()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/KotatsuColors.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.content.Context\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.core.graphics.ColorUtils\nimport com.google.android.material.R\nimport com.google.android.material.color.MaterialColors\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport kotlin.math.absoluteValue\n\nobject KotatsuColors {\n\n\t@ColorInt\n\t@Deprecated(\"\")\n\tfun segmentColor(context: Context, @AttrRes resId: Int): Int {\n\t\tval colorHex = String.format(\"%06x\", context.getThemeColor(resId))\n\t\tval hue = getHue(colorHex)\n\t\tval color = ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))\n\t\tval backgroundColor = context.getThemeColor(R.attr.colorSurfaceContainerHigh)\n\t\treturn MaterialColors.harmonize(color, backgroundColor)\n\t}\n\n\t@ColorInt\n\tfun segmentColorRandom(context: Context, seed: Any): Int {\n\t\tval color = random(seed)\n\t\tval backgroundColor = context.getThemeColor(R.attr.colorSurfaceContainerHigh)\n\t\treturn MaterialColors.harmonize(color, backgroundColor)\n\t}\n\n\t@ColorInt\n\tfun random(seed: Any): Int {\n\t\tval hue = (seed.hashCode() % 360).absoluteValue.toFloat()\n\t\treturn ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))\n\t}\n\n\t@ColorInt\n\tfun ofManga(context: Context, manga: Manga?): Int {\n\t\tval color = if (manga != null) {\n\t\t\tval hue = (manga.id.absoluteValue % 360).toFloat()\n\t\t\tColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))\n\t\t} else {\n\t\t\tcontext.getThemeColor(R.attr.colorOutline)\n\t\t}\n\t\tval backgroundColor = context.getThemeColor(R.attr.colorSurfaceContainerHigh)\n\t\treturn MaterialColors.harmonize(color, backgroundColor)\n\t}\n\n\tprivate fun getHue(hex: String): Float {\n\t\tval r = (hex.substring(0, 2).toInt(16)).toFloat()\n\t\tval g = (hex.substring(2, 4).toInt(16)).toFloat()\n\t\tval b = (hex.substring(4, 6).toInt(16)).toFloat()\n\n\t\tvar hue = 0F\n\t\tif ((r >= g) && (g >= b)) {\n\t\t\thue = 60 * (g - b) / (r - b)\n\t\t} else if ((g > r) && (r >= b)) {\n\t\t\thue = 60 * (2 - (r - b) / (g - b))\n\t\t}\n\t\treturn hue\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/LocaleComparator.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport androidx.core.os.LocaleListCompat\nimport org.koitharu.kotatsu.core.util.ext.iterator\nimport java.util.Locale\n\nclass LocaleComparator : Comparator<Locale> {\n\n\tprivate val deviceLocales: List<String>\n\n\tinit {\n\t\tval localeList = LocaleListCompat.getAdjustedDefault()\n\t\tdeviceLocales = buildList(localeList.size() + 1) {\n\t\t\tadd(\"\")\n\t\t\tval set = HashSet<String>(localeList.size() + 1)\n\t\t\tset.add(\"\")\n\t\t\tfor (locale in localeList) {\n\t\t\t\tval lang = locale.language\n\t\t\t\tif (set.add(lang)) {\n\t\t\t\t\tadd(lang)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun compare(a: Locale, b: Locale): Int {\n\t\tval indexA = deviceLocales.indexOf(a.language)\n\t\tval indexB = deviceLocales.indexOf(b.language)\n\t\treturn when {\n\t\t\tindexA < 0 && indexB < 0 -> compareValues(a.language, b.language)\n\t\t\tindexA < 0 -> 1\n\t\t\tindexB < 0 -> -1\n\t\t\telse -> compareValues(indexA, indexB)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/LocaleStringComparator.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport androidx.core.os.LocaleListCompat\nimport org.koitharu.kotatsu.core.util.ext.indexOfContains\nimport org.koitharu.kotatsu.core.util.ext.iterator\n\nclass LocaleStringComparator : Comparator<String?> {\n\n\tprivate val deviceLocales: List<String?>\n\n\tinit {\n\t\tval localeList = LocaleListCompat.getAdjustedDefault()\n\t\tdeviceLocales = buildList(localeList.size() + 1) {\n\t\t\tadd(null)\n\t\t\tval set = HashSet<String?>(localeList.size() + 1)\n\t\t\tset.add(null)\n\t\t\tfor (locale in localeList) {\n\t\t\t\tval lang = locale.getDisplayLanguage(locale)\n\t\t\t\tif (set.add(lang)) {\n\t\t\t\t\tadd(lang)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun compare(a: String?, b: String?): Int {\n\t\tval indexA = deviceLocales.indexOfContains(a, true)\n\t\tval indexB = deviceLocales.indexOfContains(b, true)\n\t\treturn when {\n\t\t\tindexA < 0 && indexB < 0 -> compareValues(a, b)\n\t\t\tindexA < 0 -> 1\n\t\t\tindexB < 0 -> -1\n\t\t\telse -> compareValues(indexA, indexB)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/LocaleUtils.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.graphics.Paint\nimport androidx.core.graphics.PaintCompat\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport java.util.Locale\n\nobject LocaleUtils {\n\n\tprivate val paint = Paint()\n\n\tfun getEmojiFlag(locale: Locale): String? {\n\t\tval code = when (val c = locale.country.ifNullOrEmpty { locale.toLanguageTag() }.uppercase(Locale.ENGLISH)) {\n\t\t\t\"EN\" -> \"GB\"\n\t\t\t\"JA\" -> \"JP\"\n\t\t\t\"VI\" -> \"VN\"\n\t\t\t\"ZH\" -> \"CN\"\n\t\t\t\"AR\" -> \"SA\"\n\t\t\telse -> c\n\t\t}\n\t\tval emoji = countryCodeToEmojiFlag(code)\n\t\treturn if (PaintCompat.hasGlyph(paint, emoji)) {\n\t\t\temoji\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate fun countryCodeToEmojiFlag(countryCode: String): String {\n\t\treturn countryCode.map { char ->\n\t\t\tCharacter.codePointAt(\"$char\", 0) - 0x41 + 0x1F1E6\n\t\t}.map { codePoint ->\n\t\t\tCharacter.toChars(codePoint)\n\t\t}.joinToString(separator = \"\") { charArray ->\n\t\t\tString(charArray)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport java.util.concurrent.atomic.AtomicInteger\n\nabstract class MediatorStateFlow<T>(initialValue: T) : StateFlow<T> {\n\n\tprivate val delegate = MutableStateFlow(initialValue)\n\tprivate val collectors = AtomicInteger(0)\n\n\tfinal override val replayCache: List<T>\n\t\tget() = delegate.replayCache\n\n\toverride val value: T\n\t\tget() = delegate.value\n\n\tfinal override suspend fun collect(collector: FlowCollector<T>): Nothing {\n\t\ttry {\n\t\t\tif (collectors.getAndIncrement() == 0) {\n\t\t\t\tonActive()\n\t\t\t}\n\t\t\tdelegate.collect(collector)\n\t\t} finally {\n\t\t\tif (collectors.decrementAndGet() == 0) {\n\t\t\t\tonInactive()\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected fun publishValue(v: T) {\n\t\tdelegate.value = v\n\t}\n\n\tprotected abstract fun onActive()\n\n\tprotected abstract fun onInactive()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/MimeTypes.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.os.Build\nimport android.webkit.MimeTypeMap\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.core.util.ext.MimeType\nimport org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.removeSuffix\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\nimport java.nio.file.Files\nimport coil3.util.MimeTypeMap as CoilMimeTypeMap\n\nobject MimeTypes {\n\n\tfun getMimeTypeFromExtension(fileName: String): MimeType? {\n\t\treturn CoilMimeTypeMap.getMimeTypeFromExtension(getNormalizedExtension(fileName) ?: return null)\n\t\t\t?.toMimeTypeOrNull()\n\t}\n\n\tfun getMimeTypeFromUrl(url: String): MimeType? {\n\t\treturn CoilMimeTypeMap.getMimeTypeFromUrl(url)?.toMimeTypeOrNull()\n\t}\n\n\tfun getExtension(mimeType: MimeType?): String? {\n\t\treturn MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType?.toString() ?: return null)?.nullIfEmpty()\n\t}\n\n\t@Blocking\n\tfun probeMimeType(file: File): MimeType? {\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\trunCatchingCancellable {\n\t\t\t\tFiles.probeContentType(file.toPath())?.toMimeTypeOrNull()\n\t\t\t}.getOrNull()?.let { return it }\n\t\t}\n\t\treturn getMimeTypeFromExtension(file.name)\n\t}\n\n\tfun getNormalizedExtension(name: String): String? = name\n\t\t.lowercase()\n\t\t.removeSuffix('~')\n\t\t.removeSuffix(\".tmp\")\n\t\t.substringAfterLast('.', \"\")\n\t\t.takeIf { it.length in 2..5 }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/MultiMutex.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport androidx.annotation.VisibleForTesting\nimport kotlinx.coroutines.sync.Mutex\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.contracts.InvocationKind\nimport kotlin.contracts.contract\n\nopen class MultiMutex<T : Any> {\n\n\tprivate val delegates = ConcurrentHashMap<T, Mutex>()\n\n\t@VisibleForTesting\n\tval size: Int\n\t\tget() = delegates.count { it.value.isLocked }\n\n\tfun isNotEmpty() = delegates.any { it.value.isLocked }\n\n\tfun isEmpty() = delegates.none { it.value.isLocked }\n\n\tsuspend fun lock(element: T) {\n\t\tval mutex = delegates.computeIfAbsent(element) { Mutex() }\n\t\tmutex.lock()\n\t}\n\n\tfun unlock(element: T) {\n\t\tdelegates[element]?.unlock()\n\t}\n\n\tsuspend inline fun <R> withLock(element: T, block: () -> R): R {\n\t\tcontract {\n\t\t\tcallsInPlace(block, InvocationKind.EXACTLY_ONCE)\n\t\t}\n\t\tlock(element)\n\t\treturn try {\n\t\t\tblock()\n\t\t} finally {\n\t\t\tunlock(element)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/RecyclerViewScrollCallback.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport androidx.annotation.Px\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport java.lang.ref.WeakReference\n\nclass RecyclerViewScrollCallback(\n\trecyclerView: RecyclerView,\n\tprivate val position: Int,\n\t@Px private val offset: Int,\n) : Runnable {\n\n\tprivate val recyclerViewRef = WeakReference(recyclerView)\n\n\toverride fun run() {\n\t\tval rv = recyclerViewRef.get() ?: return\n\t\tval lm = rv.layoutManager ?: return\n\t\trv.stopScroll()\n\t\tif (lm is LinearLayoutManager) {\n\t\t\tlm.scrollToPositionWithOffset(position, offset)\n\t\t} else {\n\t\t\tlm.scrollToPosition(position)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/RetainedLifecycleCoroutineScope.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport dagger.hilt.android.lifecycle.RetainedLifecycle\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.launch\nimport kotlin.coroutines.CoroutineContext\n\nclass RetainedLifecycleCoroutineScope(\n\tval lifecycle: RetainedLifecycle,\n) : CoroutineScope, RetainedLifecycle.OnClearedListener {\n\n\toverride val coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate\n\n\tinit {\n\t\tlaunch(Dispatchers.Main.immediate) {\n\t\t\tlifecycle.addOnClearedListener(this@RetainedLifecycleCoroutineScope)\n\t\t}\n\t}\n\n\toverride fun onCleared() {\n\t\tcoroutineContext.cancel()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.core.app.ShareCompat\nimport androidx.core.content.FileProvider\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.appUrl\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.io.File\n\nprivate const val TYPE_TEXT = \"text/plain\"\nprivate const val TYPE_IMAGE = \"image/*\"\nprivate const val TYPE_CBZ = \"application/x-cbz\"\n\n@Deprecated(\"\")\nclass ShareHelper(private val context: Context) {\n\n\tfun shareMangaLink(manga: Manga) {\n\t\tval text = buildString {\n\t\t\tappend(manga.title)\n\t\t\tappend(\"\\n \\n\")\n\t\t\tappend(manga.publicUrl)\n\t\t\tappend(\"\\n \\n\")\n\t\t\tappend(manga.appUrl)\n\t\t}\n\t\tShareCompat.IntentBuilder(context)\n\t\t\t.setText(text)\n\t\t\t.setType(TYPE_TEXT)\n\t\t\t.setChooserTitle(context.getString(R.string.share_s, manga.title))\n\t\t\t.startChooser()\n\t}\n\n\tfun shareMangaLinks(manga: Collection<Manga>) {\n\t\tif (manga.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tif (manga.size == 1) {\n\t\t\tshareMangaLink(manga.first())\n\t\t\treturn\n\t\t}\n\t\tval text = manga.joinToString(\"\\n \\n\") {\n\t\t\t\"${it.title} - ${it.publicUrl}\"\n\t\t}\n\t\tShareCompat.IntentBuilder(context)\n\t\t\t.setText(text)\n\t\t\t.setType(TYPE_TEXT)\n\t\t\t.setChooserTitle(R.string.share)\n\t\t\t.startChooser()\n\t}\n\n\tfun shareCbz(files: Collection<File>) {\n\t\tif (files.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tval intentBuilder = ShareCompat.IntentBuilder(context)\n\t\t\t.setType(TYPE_CBZ)\n\t\tfor (file in files) {\n\t\t\tval uri = FileProvider.getUriForFile(context, \"${BuildConfig.APPLICATION_ID}.files\", file)\n\t\t\tintentBuilder.addStream(uri)\n\t\t}\n\t\tfiles.singleOrNull()?.let {\n\t\t\tintentBuilder.setChooserTitle(context.getString(R.string.share_s, it.name))\n\t\t} ?: run {\n\t\t\tintentBuilder.setChooserTitle(R.string.share)\n\t\t}\n\t\tintentBuilder.startChooser()\n\t}\n\n\tfun shareImage(uri: Uri) {\n\t\tShareCompat.IntentBuilder(context)\n\t\t\t.setStream(uri)\n\t\t\t.setType(context.contentResolver.getType(uri) ?: TYPE_IMAGE)\n\t\t\t.setChooserTitle(R.string.share_image)\n\t\t\t.startChooser()\n\t}\n\n\tfun getShareTextIntent(text: String): Intent = ShareCompat.IntentBuilder(context)\n\t\t.setText(text)\n\t\t.setType(TYPE_TEXT)\n\t\t.setChooserTitle(R.string.share)\n\t\t.createChooserIntent()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/SynchronizedSieveCache.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport androidx.collection.SieveCache\n\nclass SynchronizedSieveCache<K : Any, V : Any>(\n\tprivate val delegate: SieveCache<K, V>,\n) {\n\n\tconstructor(maxSize: Int) : this(SieveCache<K, V>(maxSize))\n\n\tprivate val lock = Any()\n\n\toperator fun get(key: K): V? = synchronized(lock) {\n\t\tdelegate[key]\n\t}\n\n\tfun put(key: K, value: V): V? = synchronized(lock) {\n\t\tdelegate.put(key, value)\n\t}\n\n\tfun remove(key: K) = synchronized(lock) {\n\t\tdelegate.remove(key)\n\t}\n\n\tfun evictAll() = synchronized(lock) {\n\t\tdelegate.evictAll()\n\t}\n\n\tfun trimToSize(maxSize: Int) = synchronized(lock) {\n\t\tdelegate.trimToSize(maxSize)\n\t}\n\n\tfun removeIf(predicate: (K, V) -> Boolean) = synchronized(lock) {\n\t\tdelegate.removeIf(predicate)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/Throttler.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.os.SystemClock\n\nclass Throttler(\n\tprivate val timeoutMs: Long,\n) {\n\n\tprivate var lastTick = 0L\n\n\tfun throttle(): Boolean {\n\t\tval now = SystemClock.elapsedRealtime()\n\t\treturn if (lastTick + timeoutMs <= now) {\n\t\t\tlastTick = now\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tfun reset() {\n\t\tlastTick = 0L\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ViewBadge.kt",
    "content": "package org.koitharu.kotatsu.core.util\n\nimport android.view.View\nimport androidx.annotation.OptIn\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport com.google.android.material.badge.BadgeDrawable\nimport com.google.android.material.badge.BadgeUtils\nimport com.google.android.material.badge.ExperimentalBadgeUtils\n\n@OptIn(ExperimentalBadgeUtils::class)\nclass ViewBadge(\n\tprivate val anchor: View,\n\tlifecycleOwner: LifecycleOwner,\n) : View.OnLayoutChangeListener, DefaultLifecycleObserver {\n\n\tprivate var badgeDrawable: BadgeDrawable? = null\n\tprivate var maxCharacterCount: Int = -1\n\n\tvar counter: Int\n\t\tget() = badgeDrawable?.number ?: 0\n\t\tset(value) {\n\t\t\tval badge = badgeDrawable ?: initBadge()\n\t\t\tif (maxCharacterCount != 0) {\n\t\t\t\tbadge.number = value\n\t\t\t} else {\n\t\t\t\tbadge.clearNumber()\n\t\t\t}\n\t\t\tbadge.isVisible = value > 0\n\t\t}\n\n\tinit {\n\t\tlifecycleOwner.lifecycle.addObserver(this)\n\t}\n\n\toverride fun onLayoutChange(\n\t\tv: View?,\n\t\tleft: Int,\n\t\ttop: Int,\n\t\tright: Int,\n\t\tbottom: Int,\n\t\toldLeft: Int,\n\t\toldTop: Int,\n\t\toldRight: Int,\n\t\toldBottom: Int,\n\t) {\n\t\tval badge = badgeDrawable ?: return\n\t\tBadgeUtils.setBadgeDrawableBounds(badge, anchor, null)\n\t}\n\n\toverride fun onDestroy(owner: LifecycleOwner) {\n\t\tsuper.onDestroy(owner)\n\t\tclearBadge()\n\t}\n\n\tfun setMaxCharacterCount(value: Int) {\n\t\tmaxCharacterCount = value\n\t\tbadgeDrawable?.let {\n\t\t\tif (value == 0) {\n\t\t\t\tit.clearNumber()\n\t\t\t} else {\n\t\t\t\tit.maxCharacterCount = value\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun initBadge(): BadgeDrawable {\n\t\tval badge = BadgeDrawable.create(anchor.context)\n\t\tif (maxCharacterCount > 0) {\n\t\t\tbadge.maxCharacterCount = maxCharacterCount\n\t\t}\n\t\tanchor.addOnLayoutChangeListener(this)\n\t\tBadgeUtils.attachBadgeDrawable(badge, anchor)\n\t\tbadgeDrawable = badge\n\t\treturn badge\n\t}\n\n\tprivate fun clearBadge() {\n\t\tval badge = badgeDrawable ?: return\n\t\tanchor.removeOnLayoutChangeListener(this)\n\t\tBadgeUtils.detachBadgeDrawable(badge, anchor)\n\t\tbadgeDrawable = null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.Manifest\nimport android.app.Activity\nimport android.app.ActivityManager\nimport android.app.ActivityManager.MemoryInfo\nimport android.app.LocaleConfig\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Context.ACTIVITY_SERVICE\nimport android.content.Context.POWER_SERVICE\nimport android.content.ContextWrapper\nimport android.content.Intent\nimport android.content.OperationApplicationException\nimport android.content.SyncResult\nimport android.content.pm.PackageManager.PERMISSION_GRANTED\nimport android.content.pm.ResolveInfo\nimport android.database.SQLException\nimport android.graphics.Bitmap\nimport android.net.ConnectivityManager\nimport android.os.Build\nimport android.os.PowerManager\nimport android.provider.Settings\nimport android.view.ViewPropertyAnimator\nimport android.webkit.CookieManager\nimport android.webkit.WebView\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.annotation.CheckResult\nimport androidx.annotation.IntegerRes\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.appcompat.app.AppCompatDialog\nimport androidx.core.app.ActivityOptionsCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.content.ContextCompat\nimport androidx.core.os.LocaleListCompat\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.coroutineScope\nimport androidx.webkit.WebViewCompat\nimport androidx.webkit.WebViewFeature\nimport androidx.work.CoroutineWorker\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runInterruptible\nimport okio.IOException\nimport okio.use\nimport org.json.JSONException\nimport org.jsoup.internal.StringUtil.StringJoiner\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.main.ui.MainActivity\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.xmlpull.v1.XmlPullParser\nimport org.xmlpull.v1.XmlPullParserException\nimport java.io.File\nimport java.util.concurrent.TimeUnit\nimport kotlin.math.roundToLong\n\nval Context.activityManager: ActivityManager?\n\tget() = getSystemService(ACTIVITY_SERVICE) as? ActivityManager\n\nval Context.powerManager: PowerManager?\n\tget() = getSystemService(POWER_SERVICE) as? PowerManager\n\nval Context.connectivityManager: ConnectivityManager\n\tget() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager\n\nsuspend fun CoroutineWorker.trySetForeground(): Boolean = runCatchingCancellable {\n\tval info = getForegroundInfo()\n\tsetForeground(info)\n}.isSuccess\n\n@CheckResult\nfun <I> ActivityResultLauncher<I>.resolve(context: Context, input: I): ResolveInfo? {\n\tval pm = context.packageManager\n\tval intent = contract.createIntent(context, input)\n\treturn pm.resolveActivity(intent, 0)\n}\n\n@CheckResult\nfun <I> ActivityResultLauncher<I>.tryLaunch(\n\tinput: I,\n\toptions: ActivityOptionsCompat? = null,\n): Boolean = runCatching {\n\tlaunch(input, options)\n}.onFailure { e ->\n\te.printStackTraceDebug()\n}.isSuccess\n\nfun Lifecycle.postDelayed(delay: Long, runnable: Runnable) {\n\tcoroutineScope.launch {\n\t\tdelay(delay)\n\t\trunnable.run()\n\t}\n}\n\nfun SyncResult.onError(error: Throwable) {\n\twhen (error) {\n\t\tis IOException -> stats.numIoExceptions++\n\t\tis OperationApplicationException,\n\t\tis SQLException -> databaseError = true\n\n\t\tis JSONException -> stats.numParseExceptions++\n\t\telse -> if (BuildConfig.DEBUG) throw error\n\t}\n\terror.printStackTraceDebug()\n}\n\nval Context.animatorDurationScale: Float\n\tget() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f)\n\nval Context.isAnimationsEnabled: Boolean\n\tget() = animatorDurationScale > 0f\n\nfun ViewPropertyAnimator.applySystemAnimatorScale(context: Context): ViewPropertyAnimator = apply {\n\tthis.duration = (this.duration * context.animatorDurationScale).toLong()\n}\n\nfun Context.getAnimationDuration(@IntegerRes resId: Int): Long {\n\treturn (resources.getInteger(resId) * animatorDurationScale).roundToLong()\n}\n\nfun Context.isLowRamDevice(): Boolean {\n\treturn activityManager?.isLowRamDevice == true\n}\n\nfun Context.isPowerSaveMode(): Boolean {\n\treturn powerManager?.isPowerSaveMode == true\n}\n\nval Context.ramAvailable: Long\n\tget() {\n\t\tval result = MemoryInfo()\n\t\tactivityManager?.getMemoryInfo(result)\n\t\treturn result.availMem\n\t}\n\nfun Context.getLocalesConfig(): LocaleListCompat {\n\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n\t\tLocaleConfig(this).supportedLocales?.let {\n\t\t\treturn LocaleListCompat.wrap(it)\n\t\t}\n\t}\n\tval tagsList = StringJoiner(\",\")\n\ttry {\n\t\tval xpp: XmlPullParser = resources.getXml(R.xml.locales_config)\n\t\twhile (xpp.eventType != XmlPullParser.END_DOCUMENT) {\n\t\t\tif (xpp.eventType == XmlPullParser.START_TAG) {\n\t\t\t\tif (xpp.name == \"locale\") {\n\t\t\t\t\ttagsList.add(xpp.getAttributeValue(0))\n\t\t\t\t}\n\t\t\t}\n\t\t\txpp.next()\n\t\t}\n\t} catch (e: XmlPullParserException) {\n\t\te.printStackTraceDebug()\n\t} catch (e: IOException) {\n\t\te.printStackTraceDebug()\n\t}\n\treturn LocaleListCompat.forLanguageTags(tagsList.complete())\n}\n\nfun Context.findActivity(): Activity? = when (this) {\n\tis Activity -> this\n\tis ContextWrapper -> baseContext.findActivity()\n\telse -> null\n}\n\nfun Fragment.findAppCompatDelegate(): AppCompatDelegate? {\n\t((this as? DialogFragment)?.dialog as? AppCompatDialog)?.run {\n\t\treturn delegate\n\t}\n\treturn parentFragment?.findAppCompatDelegate() ?: (activity as? AppCompatActivity)?.delegate\n}\n\nfun Context.checkNotificationPermission(channelId: String?): Boolean {\n\tval hasPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n\t\tContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PERMISSION_GRANTED\n\t} else {\n\t\tNotificationManagerCompat.from(this).areNotificationsEnabled()\n\t}\n\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPermission && channelId != null) {\n\t\tval channel = NotificationManagerCompat.from(this).getNotificationChannel(channelId)\n\t\tif (channel != null && channel.importance == NotificationManagerCompat.IMPORTANCE_NONE) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn hasPermission\n}\n\nsuspend fun Bitmap.compressToPNG(output: File) = runInterruptible(Dispatchers.IO) {\n\toutput.outputStream().use { os ->\n\t\tif (!compress(Bitmap.CompressFormat.PNG, 100, os)) {\n\t\t\tthrow IOException(\"Failed to encode bitmap into PNG format\")\n\t\t}\n\t}\n}\n\nfun Context.ensureRamAtLeast(requiredSize: Long) {\n\tif (ramAvailable < requiredSize) {\n\t\tthrow IllegalStateException(\"Not enough free memory\")\n\t}\n}\n\nfun WebView.configureForParser(userAgentOverride: String?) = with(settings) {\n\tjavaScriptEnabled = true\n\tdomStorageEnabled = true\n\tmediaPlaybackRequiresUserGesture = false\n\tif (WebViewFeature.isFeatureSupported(WebViewFeature.MUTE_AUDIO)) {\n\t\tWebViewCompat.setAudioMuted(this@configureForParser, true)\n\t}\n\tdatabaseEnabled = true\n\tallowContentAccess = false\n\tif (userAgentOverride != null) {\n\t\tuserAgentString = userAgentOverride\n\t}\n\tval cookieManager = CookieManager.getInstance()\n\tcookieManager.setAcceptCookie(true)\n\tcookieManager.setAcceptThirdPartyCookies(this@configureForParser, true)\n}\n\nfun Context.restartApplication() {\n\tval activity = findActivity()\n\tval intent = Intent.makeRestartActivityTask(ComponentName(this, MainActivity::class.java))\n\tstartActivity(intent)\n\tactivity?.finishAndRemoveTask()\n}\n\ninternal inline fun <R> PowerManager?.withPartialWakeLock(tag: String, body: (PowerManager.WakeLock?) -> R): R {\n\tval wakeLock = newPartialWakeLock(tag)\n\treturn try {\n\t\twakeLock?.acquire(TimeUnit.HOURS.toMillis(1))\n\t\tbody(wakeLock)\n\t} finally {\n\t\twakeLock?.release()\n\t}\n}\n\nprivate fun PowerManager?.newPartialWakeLock(tag: String): PowerManager.WakeLock? {\n\treturn if (this != null && isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK)) {\n\t\tnewWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag)\n\t} else {\n\t\tnull\n\t}\n}\n\nfun Context.copyToClipboard(label: String, content: String) {\n\tval clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return\n\tclipboardManager.setPrimaryClip(ClipData.newPlainText(label, content))\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage org.koitharu.kotatsu.core.util.ext\n\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Parcel\nimport android.os.Parcelable\nimport androidx.core.content.IntentCompat\nimport androidx.core.os.BundleCompat\nimport androidx.core.os.ParcelCompat\nimport androidx.lifecycle.SavedStateHandle\nimport org.koitharu.kotatsu.parsers.util.toArraySet\nimport java.io.Serializable\nimport java.util.EnumSet\n\n\n// https://issuetracker.google.com/issues/240585930\n\ninline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): T? {\n\treturn BundleCompat.getParcelable(this, key, T::class.java)\n}\n\ninline fun <reified T : Parcelable> Bundle.requireParcelable(key: String): T = checkNotNull(getParcelableCompat(key)) {\n\t\"Parcelable of type \\\"${T::class.java.name}\\\" not found at \\\"$key\\\"\"\n}\n\ninline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String): T? {\n\treturn IntentCompat.getParcelableExtra(this, key, T::class.java)\n}\n\ninline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: String): T? {\n\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n\t\tgetSerializableExtra(key, T::class.java)\n\t} else {\n\t\tgetSerializableExtra(key) as T?\n\t}\n}\n\ninline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? {\n\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n\t\tgetSerializable(key, T::class.java)\n\t} else {\n\t\tgetSerializable(key) as T?\n\t}\n}\n\ninline fun <reified T : Parcelable> Parcel.readParcelableCompat(): T? {\n\treturn ParcelCompat.readParcelable(this, T::class.java.classLoader, T::class.java)\n}\n\ninline fun <reified T : Serializable> Parcel.readSerializableCompat(): T? {\n\treturn ParcelCompat.readSerializable(this, T::class.java.classLoader, T::class.java)\n}\n\ninline fun <reified T : Serializable> Bundle.requireSerializable(key: String): T {\n\treturn checkNotNull(getSerializableCompat(key)) {\n\t\t\"Serializable of type \\\"${T::class.java.name}\\\" not found at \\\"$key\\\"\"\n\t}\n}\n\nfun <E : Enum<E>> Parcel.writeEnumSet(set: Set<E>?) {\n\tif (set == null) {\n\t\twriteValue(null)\n\t} else {\n\t\tval array = IntArray(set.size)\n\t\tset.forEachIndexed { i, e -> array[i] = e.ordinal }\n\t\twriteIntArray(array)\n\t}\n}\n\ninline fun <reified E : Enum<E>> Parcel.readEnumSet(): Set<E>? = readEnumSet(E::class.java)\n\nfun <E : Enum<E>> Parcel.readEnumSet(cls: Class<E>): Set<E>? {\n\tval array = createIntArray() ?: return null\n\tif (array.isEmpty()) {\n\t\treturn emptySet()\n\t}\n\tval enumValues = cls.enumConstants ?: return null\n\tval set = EnumSet.noneOf(cls)\n\tarray.forEach { e ->\n\t\tset.add(enumValues[e])\n\t}\n\treturn set\n}\n\nfun Parcel.writeStringSet(set: Set<String>?) {\n\twriteStringArray(set?.toTypedArray().orEmpty())\n}\n\nfun Parcel.readStringSet(): Set<String> {\n\treturn this.createStringArray()?.toArraySet().orEmpty()\n}\n\nfun <T> SavedStateHandle.require(key: String): T {\n\treturn checkNotNull(get(key)) {\n\t\t\"Value $key not found in SavedStateHandle or has a wrong type\"\n\t}\n}\n\nfun Parcelable.marshall(): ByteArray {\n\tval parcel = Parcel.obtain()\n\treturn try {\n\t\tthis.writeToParcel(parcel, 0)\n\t\tparcel.marshall()\n\t} finally {\n\t\tparcel.recycle()\n\t}\n}\n\nfun <T : Parcelable> Parcelable.Creator<T>.unmarshall(bytes: ByteArray): T {\n\tval parcel = Parcel.obtain()\n\treturn try {\n\t\tparcel.unmarshall(bytes, 0, bytes.size)\n\t\tparcel.setDataPosition(0)\n\t\tcreateFromParcel(parcel)\n\t} finally {\n\t\tparcel.recycle()\n\t}\n}\n\ninline fun buildBundle(capacity: Int, block: Bundle.() -> Unit): Bundle = Bundle(capacity).apply(block)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.CheckResult\nimport coil3.Extras\nimport coil3.ImageLoader\nimport coil3.asDrawable\nimport coil3.decode.ImageSource\nimport coil3.fetch.FetchResult\nimport coil3.fetch.SourceFetchResult\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.ImageResult\nimport coil3.request.Options\nimport coil3.request.SuccessResult\nimport coil3.request.bitmapConfig\nimport coil3.toBitmap\nimport okio.buffer\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.core.image.RegionBitmapDecoder\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nfun ImageRequest.Builder.enqueueWith(loader: ImageLoader) = loader.enqueue(build())\n\nfun ImageResult.getDrawableOrThrow() = when (this) {\n\tis SuccessResult -> image.asDrawable(request.context.resources)\n\tis ErrorResult -> throw throwable\n}\n\nval ImageResult.drawable: Drawable?\n\tget() = image?.asDrawable(request.context.resources)\n\nfun ImageResult.toBitmapOrNull() = when (this) {\n\tis SuccessResult -> try {\n\t\timage.toBitmap(image.width, image.height, request.bitmapConfig)\n\t} catch (_: Throwable) {\n\t\tnull\n\t}\n\n\tis ErrorResult -> null\n}\n\nfun ImageRequest.Builder.decodeRegion(\n\tscroll: Int = RegionBitmapDecoder.SCROLL_UNDEFINED,\n): ImageRequest.Builder = apply {\n\tdecoderFactory(RegionBitmapDecoder.Factory)\n\textras[RegionBitmapDecoder.regionScrollKey] = scroll\n}\n\nfun ImageRequest.Builder.mangaSourceExtra(source: MangaSource?): ImageRequest.Builder = apply {\n\textras[mangaSourceKey] = source\n}\n\nfun ImageRequest.Builder.mangaExtra(manga: Manga?): ImageRequest.Builder = apply {\n\textras[mangaKey] = manga\n\tmangaSourceExtra(manga?.source)\n}\n\nfun ImageRequest.Builder.bookmarkExtra(bookmark: Bookmark): ImageRequest.Builder = apply {\n\textras[bookmarkKey] = bookmark\n\tmangaSourceExtra(bookmark.manga.source)\n}\n\nsuspend fun ImageLoader.fetch(data: Any, options: Options): FetchResult? {\n\tval mappedData = components.map(data, options)\n\tval fetcher = components.newFetcher(mappedData, options, this)?.first\n\treturn fetcher?.fetch()\n}\n\nval mangaKey = Extras.Key<Manga?>(null)\nval bookmarkKey = Extras.Key<Bookmark?>(null)\nval mangaSourceKey = Extras.Key<MangaSource?>(null)\n\n@CheckResult\nfun SourceFetchResult.copyWithNewSource(): SourceFetchResult = SourceFetchResult(\n\tsource = ImageSource(\n\t\tsource = source.fileSystem.source(source.file()).buffer(),\n\t\tfileSystem = source.fileSystem,\n\t\tmetadata = source.metadata,\n\t),\n\tmimeType = mimeType,\n\tdataSource = dataSource,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport androidx.collection.ArrayMap\nimport androidx.collection.ArraySet\nimport androidx.collection.LongSet\nimport org.koitharu.kotatsu.BuildConfig\nimport java.util.EnumSet\n\nfun <T> Collection<T>.asArrayList(): ArrayList<T> = if (this is ArrayList<*>) {\n\tthis as ArrayList<T>\n} else {\n\tArrayList(this)\n}\n\nfun <E : Enum<E>> Set<E>.asEnumSet(cls: Class<E>): EnumSet<E> = if (this is EnumSet<*>) {\n\tthis as EnumSet<E>\n} else {\n\tEnumSet.noneOf(cls).apply { addAll(this@asEnumSet) }\n}\n\nfun <K, V> Map<K, V>.findKeyByValue(value: V): K? {\n\tfor ((k, v) in entries) {\n\t\tif (v == value) {\n\t\t\treturn k\n\t\t}\n\t}\n\treturn null\n}\n\nfun <T> Sequence<T>.toListSorted(comparator: Comparator<T>): List<T> {\n\treturn toMutableList().apply { sortWith(comparator) }\n}\n\nfun <T> List<T>.takeMostFrequent(limit: Int): List<T> {\n\tval map = ArrayMap<T, Int>(size)\n\tfor (item in this) {\n\t\tmap[item] = map.getOrDefault(item, 0) + 1\n\t}\n\tval entries = map.entries.sortedByDescending { it.value }\n\tval count = minOf(limit, entries.size)\n\treturn buildList(count) {\n\t\trepeat(count) { i ->\n\t\t\tadd(entries[i].key)\n\t\t}\n\t}\n}\n\ninline fun <reified E : Enum<E>> Collection<E>.toEnumSet(): EnumSet<E> = if (isEmpty()) {\n\tEnumSet.noneOf(E::class.java)\n} else {\n\tEnumSet.copyOf(this)\n}\n\nfun <E : Enum<E>> Collection<E>.sortedByOrdinal() = sortedBy { it.ordinal }\n\nfun <T> Iterable<T>.sortedWithSafe(comparator: Comparator<in T>): List<T> = try {\n\tsortedWith(comparator)\n} catch (e: IllegalArgumentException) {\n\tif (BuildConfig.DEBUG) {\n\t\tthrow e\n\t} else {\n\t\ttoList()\n\t}\n}\n\nfun LongSet.toLongArray(): LongArray {\n\tval result = LongArray(size)\n\tvar i = 0\n\tforEach { result[i++] = it }\n\treturn result\n}\n\nfun LongSet.toSet(): Set<Long> = toCollection(ArraySet(size))\n\nfun <R : MutableCollection<Long>> LongSet.toCollection(out: R): R = out.also { result ->\n\tforEach(result::add)\n}\n\nfun <T, R> Collection<T>.mapSortedByCount(isDescending: Boolean = true, mapper: (T) -> R): List<R> {\n\tval grouped = groupBy(mapper).toList()\n\tval sortSelector: (Pair<R, List<T>>) -> Int = { it.second.size }\n\tval sorted = if (isDescending) {\n\t\tgrouped.sortedByDescending(sortSelector)\n\t} else {\n\t\tgrouped.sortedBy(sortSelector)\n\t}\n\treturn sorted.map { it.first }\n}\n\nfun Collection<CharSequence?>.contains(element: CharSequence?, ignoreCase: Boolean): Boolean = any { x ->\n\t(x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase))\n}\n\nfun Collection<CharSequence?>.indexOfContains(element: CharSequence?, ignoreCase: Boolean): Int = indexOfFirst { x ->\n\t(x == null && element == null) || (x != null && element != null && x.contains(element, ignoreCase))\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ContentResolver.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Build\nimport android.os.storage.StorageManager\nimport android.provider.DocumentsContract\nimport android.provider.OpenableColumns\nimport androidx.annotation.RequiresApi\nimport androidx.core.net.toFile\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.removeSuffix\nimport java.io.File\nimport java.lang.reflect.Array as ArrayReflect\n\nprivate const val PRIMARY_VOLUME_NAME = \"primary\"\n\nfun Uri.resolveFile(context: Context): File? {\n\tval volumeId = getVolumeIdFromTreeUri(this) ?: return null\n\tval volumePath = getVolumePath(volumeId, context)?.removeSuffix(File.separatorChar) ?: return null\n\tval documentPath = getDocumentPathFromTreeUri(this)?.removeSuffix(File.separatorChar) ?: return null\n\n\treturn File(\n\t\tif (documentPath.isNotEmpty()) {\n\t\t\tif (documentPath.startsWith(File.separator)) {\n\t\t\t\tvolumePath + documentPath\n\t\t\t} else {\n\t\t\t\tvolumePath + File.separator + documentPath\n\t\t\t}\n\t\t} else {\n\t\t\tvolumePath\n\t\t},\n\t)\n}\n\nfun ContentResolver.getFileDisplayName(uri: Uri): String? = runCatching {\n\tif (uri.isFileUri()) {\n\t\treturn@runCatching uri.toFile().name\n\t}\n\tquery(uri, null, null, null, null)?.use { cursor ->\n\t\tif (cursor.moveToFirst()) {\n\t\t\tcursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n}.onFailure { e ->\n\te.printStackTraceDebug()\n}.getOrNull()\n\nprivate fun getVolumePath(volumeId: String, context: Context): String? {\n\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n\t\tgetVolumePathForAndroid11AndAbove(volumeId, context)\n\t} else {\n\t\tgetVolumePathBeforeAndroid11(volumeId, context)\n\t}\n}\n\n\nprivate fun getVolumePathBeforeAndroid11(volumeId: String, context: Context): String? = runCatching {\n\tval mStorageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager\n\tval storageVolumeClazz = Class.forName(\"android.os.storage.StorageVolume\")\n\tval getVolumeList = mStorageManager.javaClass.getMethod(\"getVolumeList\")\n\tval getUuid = storageVolumeClazz.getMethod(\"getUuid\")\n\tval getPath = storageVolumeClazz.getMethod(\"getPath\")\n\tval isPrimary = storageVolumeClazz.getMethod(\"isPrimary\")\n\tval result = getVolumeList.invoke(mStorageManager)\n\tval length = ArrayReflect.getLength(checkNotNull(result))\n\t(0 until length).firstNotNullOfOrNull { i ->\n\t\tval storageVolumeElement = ArrayReflect.get(result, i)\n\t\tval uuid = getUuid.invoke(storageVolumeElement) as String?\n\t\tval primary = isPrimary.invoke(storageVolumeElement) as Boolean\n\t\twhen {\n\t\t\tprimary && volumeId == PRIMARY_VOLUME_NAME -> getPath.invoke(storageVolumeElement) as String\n\t\t\tuuid == volumeId -> getPath.invoke(storageVolumeElement) as String\n\t\t\telse -> null\n\t\t}\n\t}\n}.onFailure {\n\tit.printStackTraceDebug()\n}.getOrNull()\n\n@RequiresApi(Build.VERSION_CODES.R)\nprivate fun getVolumePathForAndroid11AndAbove(volumeId: String, context: Context): String? = runCatching {\n\tval storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager\n\tstorageManager.storageVolumes.firstNotNullOfOrNull { volume ->\n\t\tif (volume.isPrimary && volumeId == PRIMARY_VOLUME_NAME) {\n\t\t\tvolume.directory?.path\n\t\t} else {\n\t\t\tval uuid = volume.uuid\n\t\t\tif (uuid != null && uuid == volumeId) volume.directory?.path else null\n\t\t}\n\t}\n}.onFailure {\n\tit.printStackTraceDebug()\n}.getOrNull()\n\nprivate fun getVolumeIdFromTreeUri(treeUri: Uri): String? {\n\tval docId = DocumentsContract.getTreeDocumentId(treeUri)\n\tval split = docId.split(\":\".toRegex())\n\treturn split.firstOrNull()?.nullIfEmpty()\n}\n\nprivate fun getDocumentPathFromTreeUri(treeUri: Uri): String? {\n\tval docId = DocumentsContract.getTreeDocumentId(treeUri)\n\tval split: Array<String?> = docId.split(\":\".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()\n\treturn if (split.size >= 2 && split[1] != null) split[1] else File.separator\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coroutines.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.BroadcastReceiver\nimport androidx.lifecycle.ProcessLifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport dagger.hilt.android.lifecycle.RetainedLifecycle\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Deferred\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.joinAll\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.util.AcraCoroutineErrorHandler\nimport org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope\nimport org.koitharu.kotatsu.parsers.util.cancelAll\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.coroutines.cancellation.CancellationException\n\nval processLifecycleScope: CoroutineScope\n\tget() = ProcessLifecycleOwner.get().lifecycleScope + AcraCoroutineErrorHandler()\n\nval RetainedLifecycle.lifecycleScope: RetainedLifecycleCoroutineScope\n\tinline get() = RetainedLifecycleCoroutineScope(this)\n\nfun <T> Deferred<T>.getCompletionResultOrNull(): Result<T>? = if (isCompleted) {\n\tgetCompletionExceptionOrNull()?.let { error ->\n\t\tResult.failure(error)\n\t} ?: Result.success(getCompleted())\n} else {\n\tnull\n}\n\nfun <T> Deferred<T>.peek(): T? = if (isCompleted) {\n\trunCatchingCancellable {\n\t\tgetCompleted()\n\t}.getOrNull()\n} else {\n\tnull\n}\n\n@Suppress(\"SuspendFunctionOnCoroutineScope\")\nsuspend fun CoroutineScope.cancelChildrenAndJoin(cause: CancellationException? = null) {\n\tval jobs = coroutineContext[Job]?.children?.toList() ?: return\n\tjobs.cancelAll(cause)\n\tjobs.joinAll()\n}\n\nfun BroadcastReceiver.goAsync(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) {\n\tval pendingResult = goAsync()\n\tprocessLifecycleScope.launch(context) {\n\t\ttry {\n\t\t\tblock()\n\t\t} finally {\n\t\t\tpendingResult.finish()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Cursor.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.ContentValues\nimport android.database.Cursor\nimport androidx.collection.ArraySet\n\nfun Cursor.getBoolean(columnIndex: Int) = getInt(columnIndex) > 0\n\ninline fun <T> Cursor.map(mapper: (Cursor) -> T): List<T> = mapTo(ArrayList(count), mapper)\n\ninline fun <T> Cursor.mapToSet(mapper: (Cursor) -> T): Set<T> = mapTo(ArraySet(count), mapper)\n\ninline fun <T, C: MutableCollection<in T>> Cursor.mapTo(destination: C, mapper: (Cursor) -> T): C = use { c ->\n\tif (c.moveToFirst()) {\n\t\tdo {\n\t\t\tdestination.add(mapper(c))\n\t\t} while (c.moveToNext())\n\t}\n\tdestination\n}\n\ninline fun buildContentValues(capacity: Int, block: ContentValues.() -> Unit): ContentValues {\n\treturn ContentValues(capacity).apply(block)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Date.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.res.Resources\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.model.DateTimeAgo\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.LocalDateTime\nimport java.time.ZoneId\nimport java.time.temporal.ChronoUnit\nimport java.util.concurrent.TimeUnit\n\nfun calculateTimeAgo(instant: Instant, showMonths: Boolean = false): DateTimeAgo? {\n\tval localDate = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate()\n\tval now = LocalDate.now()\n\tval diffDays = localDate.until(now, ChronoUnit.DAYS)\n\n\treturn when {\n\t\tdiffDays < 0 -> null // in future, probably a bug, not supported\n\t\tdiffDays == 0L -> {\n\t\t\tif (instant.until(Instant.now(), ChronoUnit.MINUTES) < 3) DateTimeAgo.JustNow\n\t\t\telse DateTimeAgo.Today\n\t\t}\n\n\t\tdiffDays == 1L -> DateTimeAgo.Yesterday\n\t\tdiffDays < 6 -> DateTimeAgo.DaysAgo(diffDays.toInt())\n\t\telse -> {\n\t\t\tval diffMonths = localDate.until(now, ChronoUnit.MONTHS)\n\t\t\tif (showMonths && diffMonths <= 6) {\n\t\t\t\tDateTimeAgo.MonthsAgo(diffMonths.toInt())\n\t\t\t} else {\n\t\t\t\tDateTimeAgo.Absolute(localDate)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfun Long.toInstantOrNull() = if (this == 0L) null else Instant.ofEpochMilli(this)\n\nfun Resources.formatDurationShort(millis: Long): String? {\n\tval hours = TimeUnit.MILLISECONDS.toHours(millis).toInt()\n\tval minutes = (TimeUnit.MILLISECONDS.toMinutes(millis) % 60).toInt()\n\tval seconds = (TimeUnit.MILLISECONDS.toSeconds(millis) % 60).toInt()\n\treturn when {\n\t\thours == 0 && minutes == 0 && seconds == 0 -> null\n\t\thours != 0 && minutes != 0 -> getString(R.string.hours_minutes_short, hours, minutes)\n\t\thours != 0 -> getString(R.string.hours_short, hours)\n\t\tminutes != 0 && seconds != 0 -> getString(R.string.minutes_seconds_short, minutes, seconds)\n\t\tminutes != 0 -> getString(R.string.minutes_short, minutes)\n\t\telse -> getString(R.string.seconds_short, seconds)\n\t}\n}\n\nfun LocalDate.toMillis(): Long = atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/EventFlow.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport androidx.annotation.AnyThread\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport org.koitharu.kotatsu.core.util.Event\n\n@Suppress(\"FunctionName\")\nfun <T> MutableEventFlow() = MutableStateFlow<Event<T>?>(null)\n\ntypealias EventFlow<T> = StateFlow<Event<T>?>\n\ntypealias MutableEventFlow<T> = MutableStateFlow<Event<T>?>\n\n@AnyThread\nfun <T> MutableEventFlow<T>.call(data: T) {\n\tvalue = Event(data)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/File.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.os.storage.StorageManager\nimport android.provider.OpenableColumns\nimport androidx.core.database.getStringOrNull\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.fs.FileSequence\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport java.io.BufferedReader\nimport java.io.File\nimport java.nio.file.attribute.BasicFileAttributes\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\nimport kotlin.io.path.ExperimentalPathApi\nimport kotlin.io.path.PathWalkOption\nimport kotlin.io.path.readAttributes\nimport kotlin.io.path.walk\n\nfun File.subdir(name: String) = File(this, name).also {\n\tif (!it.exists()) it.mkdirs()\n}\n\nfun File.takeIfReadable() = takeIf { it.isReadable() }\n\nfun File.takeIfWriteable() = takeIf { it.isWriteable() }\n\nfun File.isNotEmpty() = length() != 0L\n\n@Blocking\nfun ZipFile.readText(entry: ZipEntry) = getInputStream(entry).use { output ->\n\toutput.bufferedReader().use(BufferedReader::readText)\n}\n\nfun File.getStorageName(context: Context): String = runCatching {\n\tval manager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager\n\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n\t\tmanager.getStorageVolume(this)?.getDescription(context)?.let {\n\t\t\treturn@runCatching it\n\t\t}\n\t}\n\twhen {\n\t\tEnvironment.isExternalStorageEmulated(this) -> context.getString(R.string.internal_storage)\n\t\tEnvironment.isExternalStorageRemovable(this) -> context.getString(R.string.external_storage)\n\t\telse -> null\n\t}\n}.getOrNull() ?: context.getString(R.string.other_storage)\n\nfun Uri.toFileOrNull() = if (isFileUri()) path?.let(::File) else null\n\nsuspend fun File.deleteAwait() = runInterruptible(Dispatchers.IO) {\n\tdelete() || deleteRecursively()\n}\n\nfun ContentResolver.resolveName(uri: Uri): String? {\n\tval fallback = uri.lastPathSegment\n\tif (uri.scheme != \"content\") {\n\t\treturn fallback\n\t}\n\tquery(uri, null, null, null, null)?.use {\n\t\tif (it.moveToFirst()) {\n\t\t\tit.getStringOrNull(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))?.let { name ->\n\t\t\t\treturn name\n\t\t\t}\n\t\t}\n\t}\n\treturn fallback\n}\n\nsuspend fun File.computeSize(): Long = runInterruptible(Dispatchers.IO) {\n\twalkCompat(includeDirectories = false).sumOf { it.length() }\n}\n\ninline fun <R> File.withChildren(block: (children: Sequence<File>) -> R): R = FileSequence(this).use(block)\n\nfun FileSequence(dir: File): FileSequence = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\tFileSequence.StreamImpl(dir)\n} else {\n\tFileSequence.ListImpl(dir)\n}\n\nval File.creationTime\n\tget() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\ttoPath().readAttributes<BasicFileAttributes>().creationTime().toMillis()\n\t} else {\n\t\tlastModified()\n\t}\n\n@OptIn(ExperimentalPathApi::class)\nfun File.walkCompat(includeDirectories: Boolean): Sequence<File> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t// Use lazy loading on Android 8.0 and later\n\tval walk = if (includeDirectories) {\n\t\ttoPath().walk(PathWalkOption.INCLUDE_DIRECTORIES)\n\t} else {\n\t\ttoPath().walk()\n\t}\n\twalk.map { it.toFile() }\n} else {\n\t// Directories are excluded by default in Path.walk(), so do it here as well\n\tval walk = walk()\n\tif (includeDirectories) walk else walk.filter { it.isFile }\n}\n\nval File.normalizedExtension: String?\n\tget() = MimeTypes.getNormalizedExtension(name)\n\nfun File.isReadable() = runCatching {\n\tcanRead()\n}.getOrDefault(false)\n\nfun File.isWriteable() = runCatching {\n\tcanWrite()\n}.getOrDefault(false)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Flow.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.os.SystemClock\nimport kotlinx.coroutines.channels.SendChannel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.emitAll\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.firstOrNull\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.transform\nimport kotlinx.coroutines.flow.transformLatest\nimport kotlinx.coroutines.flow.transformWhile\nimport kotlinx.coroutines.flow.update\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.SuspendLazy\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.atomic.AtomicInteger\n\nfun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {\n\tvar isFirstCall = true\n\treturn onEach {\n\t\tif (isFirstCall) {\n\t\t\taction(it)\n\t\t\tisFirstCall = false\n\t\t}\n\t}.onCompletion {\n\t\tisFirstCall = true\n\t}\n}\n\nfun <T> Flow<T>.onEachWhile(action: suspend (T) -> Boolean): Flow<T> {\n\tvar isCalled = false\n\treturn onEach {\n\t\tif (!isCalled) {\n\t\t\tisCalled = action(it)\n\t\t}\n\t}.onCompletion {\n\t\tisCalled = false\n\t}\n}\n\nfun <T> Flow<T>.onEachIndexed(action: suspend (index: Int, T) -> Unit): Flow<T> {\n\tval counter = AtomicInteger(0)\n\treturn transform { value ->\n\t\taction(counter.getAndIncrement(), value)\n\t\treturn@transform emit(value)\n\t}\n}\n\ninline fun <T, R> Flow<List<T>>.mapItems(crossinline transform: (T) -> R): Flow<List<R>> {\n\treturn map { list -> list.map(transform) }\n}\n\nfun <T> Flow<T>.throttle(timeoutMillis: Long): Flow<T> = throttle { timeoutMillis }\n\nfun <T> Flow<T>.throttle(timeoutMillis: (T) -> Long): Flow<T> {\n\tvar lastEmittedAt = 0L\n\treturn transformLatest { value ->\n\t\tval delay = timeoutMillis(value)\n\t\tval now = SystemClock.elapsedRealtime()\n\t\tif (delay > 0L) {\n\t\t\tif (lastEmittedAt + delay < now) {\n\t\t\t\tdelay(lastEmittedAt + delay - now)\n\t\t\t}\n\t\t}\n\t\temit(value)\n\t\tlastEmittedAt = now\n\t}\n}\n\nfun <T> StateFlow<T?>.requireValue(): T = checkNotNull(value) {\n\t\"StateFlow value is null\"\n}\n\nfun <T> Flow<Collection<T>>.flatten(): Flow<T> = flow {\n\tcollect { value ->\n\t\tfor (item in value) {\n\t\t\temit(item)\n\t\t}\n\t}\n}\n\nfun <T> Flow<T>.zipWithPrevious(): Flow<Pair<T?, T>> = flow {\n\tvar previous: T? = null\n\tcollect { value ->\n\t\tval result = previous to value\n\t\tprevious = value\n\t\temit(result)\n\t}\n}\n\nfun tickerFlow(interval: Long, timeUnit: TimeUnit): Flow<Long> = flow {\n\twhile (true) {\n\t\temit(SystemClock.elapsedRealtime())\n\t\tdelay(timeUnit.toMillis(interval))\n\t}\n}\n\nfun <T> Flow<T>.withTicker(interval: Long, timeUnit: TimeUnit) = channelFlow<T> {\n\tonCompletion { cause ->\n\t\tclose(cause)\n\t}.combine(tickerFlow(interval, timeUnit)) { x, _ -> x }\n\t\t.transformWhile<T, Unit> { trySend(it).isSuccess }\n\t\t.collect()\n}\n\n@Suppress(\"UNCHECKED_CAST\")\nfun <T1, T2, T3, T4, T5, T6, R> combine(\n\tflow: Flow<T1>,\n\tflow2: Flow<T2>,\n\tflow3: Flow<T3>,\n\tflow4: Flow<T4>,\n\tflow5: Flow<T5>,\n\tflow6: Flow<T6>,\n\ttransform: suspend (T1, T2, T3, T4, T5, T6) -> R,\n): Flow<R> = combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> ->\n\ttransform(\n\t\targs[0] as T1,\n\t\targs[1] as T2,\n\t\targs[2] as T3,\n\t\targs[3] as T4,\n\t\targs[4] as T5,\n\t\targs[5] as T6,\n\t)\n}\n\n@Suppress(\"UNCHECKED_CAST\")\nfun <T1, T2, T3, T4, T5, T6, T7, R> combine(\n\tflow: Flow<T1>,\n\tflow2: Flow<T2>,\n\tflow3: Flow<T3>,\n\tflow4: Flow<T4>,\n\tflow5: Flow<T5>,\n\tflow6: Flow<T6>,\n\tflow7: Flow<T7>,\n\ttransform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R,\n): Flow<R> = combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->\n\ttransform(\n\t\targs[0] as T1,\n\t\targs[1] as T2,\n\t\targs[2] as T3,\n\t\targs[3] as T4,\n\t\targs[4] as T5,\n\t\targs[5] as T6,\n\t\targs[6] as T7,\n\t)\n}\n\nsuspend fun <T : Any> Flow<T?>.firstNotNull(): T = checkNotNull(first { x -> x != null })\n\nsuspend fun <T : Any> Flow<T?>.firstNotNullOrNull(): T? = firstOrNull { x -> x != null }\n\nfun <T> Flow<Flow<T>>.flattenLatest() = flatMapLatest { it }\n\nfun <T> SuspendLazy<T>.asFlow() = flow { emit(runCatchingCancellable { get() }) }\n\nsuspend fun <T> SendChannel<T>.sendNotNull(item: T?) {\n\tif (item != null) {\n\t\tsend(item)\n\t}\n}\n\nfun <T> MutableStateFlow<List<T>>.append(item: T) {\n\tupdate { list -> list + item }\n}\n\nfun <T> Flow<T>.concat(other: Flow<T>) = flow {\n\temitAll(this@concat)\n\temitAll(other)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/FlowObserver.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.core.util.Event\n\nfun <T> Flow<T>.observe(owner: LifecycleOwner, collector: FlowCollector<T>) {\n\tval start = if (this is StateFlow) CoroutineStart.UNDISPATCHED else CoroutineStart.DEFAULT\n\towner.lifecycleScope.launch(start = start) {\n\t\tcollect(collector)\n\t}\n}\n\nfun <T> Flow<T>.observe(owner: LifecycleOwner, minState: Lifecycle.State, collector: FlowCollector<T>) {\n\towner.lifecycleScope.launch {\n\t\towner.lifecycle.repeatOnLifecycle(minState) {\n\t\t\tcollect(collector)\n\t\t}\n\t}\n}\n\nfun <T> Flow<Event<T>?>.observeEvent(owner: LifecycleOwner, collector: FlowCollector<T>) {\n\tobserveEvent(owner, Lifecycle.State.STARTED, collector)\n}\n\nfun <T> Flow<Event<T>?>.observeEvent(owner: LifecycleOwner, minState: Lifecycle.State, collector: FlowCollector<T>) {\n\towner.lifecycleScope.launch {\n\t\towner.repeatOnLifecycle(minState) {\n\t\t\tcollect {\n\t\t\t\tit?.consume(collector)\n\t\t\t}\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.os.Bundle\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.ancestors\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentContainerView\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.coroutineScope\n\ninline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {\n\tval b = Bundle(size)\n\tb.block()\n\tthis.arguments = b\n\treturn this\n}\n\nval Fragment.viewLifecycleScope\n\tinline get() = viewLifecycleOwner.lifecycle.coroutineScope\n\nfun Fragment.addMenuProvider(provider: MenuProvider) {\n\trequireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.RESUMED)\n}\n\n@Suppress(\"UNCHECKED_CAST\")\ntailrec fun <T> Fragment.findParentCallback(cls: Class<T>): T? {\n\tval parent = parentFragment\n\treturn when {\n\t\tparent == null -> cls.castOrNull(activity)\n\t\tcls.isInstance(parent) -> parent as T\n\t\telse -> parent.findParentCallback(cls)\n\t}\n}\n\nval Fragment.container: FragmentContainerView?\n\tget() = view?.ancestors?.firstNotNullOfOrNull {\n\t\tit as? FragmentContainerView // TODO check if direct parent\n\t}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.res.ColorStateList\nimport android.graphics.Bitmap\nimport android.graphics.Rect\nimport kotlin.math.roundToInt\n\nfun Rect.scale(factor: Double) {\n\tval newWidth = (width() * factor).roundToInt()\n\tval newHeight = (height() * factor).roundToInt()\n\tinset(\n\t\t(width() - newWidth) / 2,\n\t\t(height() - newHeight) / 2,\n\t)\n}\n\ninline fun <R> Bitmap.use(block: (Bitmap) -> R) = try {\n\tblock(this)\n} finally {\n\trecycle()\n}\n\nfun ColorStateList.hasFocusStateSpecified(): Boolean {\n\treturn getColorForState(intArrayOf(android.R.attr.state_focused), defaultColor) != defaultColor\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.Response\nimport okhttp3.internal.closeQuietly\nimport okio.IOException\nimport org.json.JSONObject\nimport org.jsoup.HttpStatusException\nimport java.net.HttpURLConnection\n\nprivate val TYPE_JSON = \"application/json\".toMediaType()\n\nfun JSONObject.toRequestBody() = toString().toRequestBody(TYPE_JSON)\n\nfun Response.parseJsonOrNull(): JSONObject? {\n\treturn try {\n\t\twhen {\n\t\t\t!isSuccessful -> throw IOException(body?.string())\n\t\t\tcode == HttpURLConnection.HTTP_NO_CONTENT -> null\n\t\t\telse -> JSONObject(body?.string() ?: return null)\n\t\t}\n\t} finally {\n\t\tcloseQuietly()\n\t}\n}\n\nfun Response.ensureSuccess() = apply {\n\tif (!isSuccessful || code == HttpURLConnection.HTTP_NO_CONTENT) {\n\t\tcloseQuietly()\n\t\tthrow HttpStatusException(message, code, request.url.toString())\n\t}\n}\n\nfun String.sanitizeHeaderValue(): String {\n\treturn if (all(Char::isValidForHeaderValue)) {\n\t\tthis // fast path\n\t} else {\n\t\tfilter(Char::isValidForHeaderValue)\n\t}\n}\n\nprivate fun Char.isValidForHeaderValue(): Boolean {\n\t// from okhttp3.Headers$Companion.checkValue\n\treturn this == '\\t' || this in '\\u0020'..'\\u007e'\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/IO.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.ContentResolver\nimport android.net.Uri\nimport androidx.annotation.CheckResult\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.withContext\nimport okhttp3.ResponseBody\nimport okio.BufferedSink\nimport okio.BufferedSource\nimport okio.FileSystem\nimport okio.IOException\nimport okio.Path\nimport okio.Source\nimport okio.source\nimport org.koitharu.kotatsu.core.util.CancellableSource\nimport org.koitharu.kotatsu.core.util.progress.ProgressResponseBody\nimport java.io.ByteArrayOutputStream\nimport java.io.InputStream\nimport java.nio.ByteBuffer\n\nfun ResponseBody.withProgress(progressState: MutableStateFlow<Float>): ResponseBody {\n\treturn ProgressResponseBody(this, progressState)\n}\n\nsuspend fun Source.cancellable(): Source {\n\tval job = currentCoroutineContext()[Job]\n\treturn CancellableSource(job, this)\n}\n\nsuspend fun BufferedSink.writeAllCancellable(source: Source) = withContext(Dispatchers.IO) {\n\twriteAll(source.cancellable())\n}\n\nfun BufferedSource.readByteBuffer(): ByteBuffer {\n\tval bytes = readByteArray()\n\treturn ByteBuffer.allocateDirect(bytes.size)\n\t\t.put(bytes)\n\t\t.rewind() as ByteBuffer\n}\n\n@Deprecated(\"\")\nfun InputStream.toByteBuffer(): ByteBuffer {\n\tval outStream = ByteArrayOutputStream(available())\n\tcopyTo(outStream)\n\tval bytes = outStream.toByteArray()\n\treturn ByteBuffer.allocateDirect(bytes.size).put(bytes).position(0) as ByteBuffer\n}\n\nfun FileSystem.isDirectory(path: Path) = try {\n\tmetadataOrNull(path)?.isDirectory == true\n} catch (_: IOException) {\n\tfalse\n}\n\nfun FileSystem.isRegularFile(path: Path) = try {\n\tmetadataOrNull(path)?.isRegularFile == true\n} catch (_: IOException) {\n\tfalse\n}\n\n@CheckResult\nfun ContentResolver.openSource(uri: Uri): Source = checkNotNull(openInputStream(uri)) {\n\t\"Cannot open input stream from $uri\"\n}.source()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Insets.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.view.View\nimport androidx.core.graphics.Insets\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.WindowInsetsCompat.Type.InsetsType\n\nfun Insets.end(view: View): Int {\n\treturn if (view.isRtl) left else right\n}\n\nfun Insets.start(view: View): Int {\n\treturn if (view.isRtl) right else left\n}\n\n@Deprecated(\"\")\nval WindowInsetsCompat.systemBarsInsets: Insets\n\tget() = getInsets(WindowInsetsCompat.Type.systemBars())\n\n@Deprecated(\"\")\nfun WindowInsetsCompat.consumeSystemBarsInsets(\n\tleft: Boolean = false,\n\ttop: Boolean = false,\n\tright: Boolean = false,\n\tbottom: Boolean = false,\n): WindowInsetsCompat {\n\tval barsInsets = systemBarsInsets\n\tval insets = Insets.of(\n\t\tif (left) 0 else barsInsets.left,\n\t\tif (top) 0 else barsInsets.top,\n\t\tif (right) 0 else barsInsets.right,\n\t\tif (bottom) 0 else barsInsets.bottom,\n\t)\n\treturn WindowInsetsCompat.Builder(this)\n\t\t.setInsets(WindowInsetsCompat.Type.systemBars(), insets)\n\t\t.build()\n}\n\nfun WindowInsetsCompat.consume(\n\tv: View,\n\t@InsetsType typeMask: Int,\n\tstart: Boolean = false,\n\ttop: Boolean = false,\n\tend: Boolean = false,\n\tbottom: Boolean = false,\n): WindowInsetsCompat {\n\tval insets = getInsets(typeMask)\n\tval newInsets = Insets.of(\n\t\t/* left = */ if (if (v.isRtl) end else start) 0 else insets.left,\n\t\t/* top = */ if (top) 0 else insets.top,\n\t\t/* right = */ if (if (v.isRtl) start else end) 0 else insets.right,\n\t\t/* bottom = */ if (bottom) 0 else insets.bottom,\n\t)\n\treturn WindowInsetsCompat.Builder(this)\n\t\t.setInsets(typeMask, newInsets)\n\t\t.build()\n}\n\nfun WindowInsetsCompat.consumeAll(\n\t@InsetsType typeMask: Int,\n): WindowInsetsCompat = WindowInsetsCompat.Builder(this)\n\t.setInsets(typeMask, Insets.NONE)\n\t.build()\n\n@Deprecated(\"\")\nfun WindowInsetsCompat.consumeSystemBarsInsets(\n\tview: View,\n\tstart: Boolean = false,\n\ttop: Boolean = false,\n\tend: Boolean = false,\n\tbottom: Boolean = false,\n): WindowInsetsCompat = consume(view, WindowInsetsCompat.Type.systemBars(), start, top, end, bottom)\n\n@Deprecated(\"\")\nfun WindowInsetsCompat.consumeAllSystemBarsInsets() = consumeAll(WindowInsetsCompat.Type.systemBars())\n\n@Deprecated(\"\")\nfun Insets.consume(\n\tview: View,\n\tstart: Boolean = false,\n\ttop: Boolean = false,\n\tend: Boolean = false,\n\tbottom: Boolean = false,\n): Insets = Insets.of(\n\t/* left = */ if (if (view.isRtl) end else start) 0 else this.left,\n\t/* top = */ if (top) 0 else this.top,\n\t/* right = */ if (if (view.isRtl) start else end) 0 else this.right,\n\t/* bottom = */ if (bottom) 0 else this.bottom,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/LocaleList.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.Context\nimport androidx.core.os.LocaleListCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.util.Set\nimport org.koitharu.kotatsu.parsers.util.toTitleCase\nimport java.util.Locale\n\noperator fun LocaleListCompat.iterator(): ListIterator<Locale> = LocaleListCompatIterator(this)\n\nfun LocaleListCompat.toList(): List<Locale> = List(size()) { i -> getOrThrow(i) }\n\ninline fun <T> LocaleListCompat.map(block: (Locale) -> T): List<T> {\n\treturn List(size()) { i -> block(getOrThrow(i)) }\n}\n\ninline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {\n\treturn Set(size()) { i -> block(getOrThrow(i)) }\n}\n\nfun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()\n\nfun String.toLocale(): Locale = Locale.forLanguageTag(this)\n\nfun String.toLocaleOrNull() = if (isEmpty()) {\n\tnull\n} else {\n\ttoLocale().takeUnless { it.displayName == this }\n}\n\nfun Locale?.getDisplayName(context: Context): String = when (this) {\n\tnull -> context.getString(R.string.all_languages)\n\tLocale.ROOT -> context.getString(R.string.various_languages)\n\telse -> getDisplayLanguage(this).toTitleCase(this)\n}\n\nprivate class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> {\n\n\tprivate var index = 0\n\n\toverride fun hasNext() = index < list.size()\n\n\toverride fun hasPrevious() = index > 0\n\n\toverride fun next() = list.get(index++) ?: throw NoSuchElementException()\n\n\toverride fun nextIndex() = index\n\n\toverride fun previous() = list.get(--index) ?: throw NoSuchElementException()\n\n\toverride fun previousIndex() = index - 1\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/MimeType.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport okhttp3.MediaType\n\nprivate const val TYPE_IMAGE = \"image\"\nprivate val REGEX_MIME = Regex(\"^\\\\w+/([-+.\\\\w]+|\\\\*)$\", RegexOption.IGNORE_CASE)\n\n@JvmInline\nvalue class MimeType(private val value: String) {\n\n\tval type: String?\n\t\tget() = value.substringBefore('/', \"\").takeIfSpecified()\n\n\tval subtype: String?\n\t\tget() = value.substringAfterLast('/', \"\").takeIfSpecified()\n\n\tprivate fun String.takeIfSpecified(): String? = takeUnless {\n\t\tit.isEmpty() || it == \"*\"\n\t}\n\n\toverride fun toString(): String = value\n}\n\nfun MediaType.toMimeType(): MimeType = MimeType(\"$type/$subtype\")\n\nfun String.toMimeTypeOrNull(): MimeType? = if (REGEX_MIME.matches(this)) {\n\tMimeType(lowercase())\n} else {\n\tnull\n}\n\nval MimeType.isImage: Boolean\n\tget() = type == TYPE_IMAGE\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport org.koitharu.kotatsu.core.io.NullOutputStream\nimport java.io.ObjectOutputStream\n\n@Suppress(\"UNCHECKED_CAST\")\nfun <T> Class<T>.castOrNull(obj: Any?): T? {\n\tif (obj == null || !isInstance(obj)) {\n\t\treturn null\n\t}\n\treturn obj as T\n}\n\nfun Any.isSerializable() = runCatching {\n\tval oos = ObjectOutputStream(NullOutputStream())\n\toos.writeObject(this)\n\toos.flush()\n}.isSuccess\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Preferences.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.SharedPreferences\nimport androidx.collection.ArraySet\nimport androidx.preference.ListPreference\nimport androidx.preference.MultiSelectListPreference\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.channels.trySendBlocking\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flow\nimport org.json.JSONArray\n\nfun ListPreference.setDefaultValueCompat(defaultValue: String) {\n\tif (value == null) {\n\t\tvalue = defaultValue\n\t}\n}\n\nfun MultiSelectListPreference.setDefaultValueCompat(defaultValue: Set<String>) {\n\tsetDefaultValue(defaultValue) // FIXME not working\n}\n\nfun <E : Enum<E>> SharedPreferences.getEnumValue(key: String, enumClass: Class<E>): E? {\n\tval stringValue = getString(key, null) ?: return null\n\treturn enumClass.enumConstants?.find {\n\t\tit.name == stringValue\n\t}\n}\n\nfun <E : Enum<E>> SharedPreferences.getEnumValue(key: String, defaultValue: E): E {\n\treturn getEnumValue(key, defaultValue.javaClass) ?: defaultValue\n}\n\nfun <E : Enum<E>> SharedPreferences.Editor.putEnumValue(key: String, value: E?) {\n\tputString(key, value?.name)\n}\n\nfun SharedPreferences.observeChanges(): Flow<String?> = callbackFlow {\n\tval listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->\n\t\ttrySendBlocking(key)\n\t}\n\tregisterOnSharedPreferenceChangeListener(listener)\n\tawaitClose {\n\t\tunregisterOnSharedPreferenceChangeListener(listener)\n\t}\n}\n\nfun <T> SharedPreferences.observe(key: String, valueProducer: suspend () -> T): Flow<T> = flow {\n\temit(valueProducer())\n\tobserveChanges().collect { upstreamKey ->\n\t\tif (upstreamKey == key) {\n\t\t\temit(valueProducer())\n\t\t}\n\t}\n}.distinctUntilChanged()\n\nfun SharedPreferences.Editor.putAll(values: Map<String, *>) {\n\tvalues.forEach { e ->\n\t\twhen (val v = e.value) {\n\t\t\tis Boolean -> putBoolean(e.key, v)\n\t\t\tis Int -> putInt(e.key, v)\n\t\t\tis Long -> putLong(e.key, v)\n\t\t\tis Float -> putFloat(e.key, v)\n\t\t\tis String -> putString(e.key, v)\n\t\t\tis JSONArray -> putStringSet(e.key, v.toStringSet())\n\t\t}\n\t}\n}\n\nprivate fun JSONArray.toStringSet(): Set<String> {\n\tval len = length()\n\tval result = ArraySet<String>(len)\n\tfor (i in 0 until len) {\n\t\tresult.add(getString(i))\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Primitive.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/RecyclerView.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.util.DisplayMetrics\nimport androidx.core.view.doOnNextLayout\nimport androidx.core.view.isEmpty\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearSmoothScroller\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\nimport com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder\nimport com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewHolder\n\nfun RecyclerView.clearItemDecorations() {\n\tsuppressLayout(true)\n\twhile (itemDecorationCount > 0) {\n\t\tremoveItemDecorationAt(0)\n\t}\n\tsuppressLayout(false)\n}\n\nfun RecyclerView.removeItemDecoration(cls: Class<out RecyclerView.ItemDecoration>) {\n\trepeat(itemDecorationCount) { i ->\n\t\tif (cls.isInstance(getItemDecorationAt(i))) {\n\t\t\tremoveItemDecorationAt(i)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nvar RecyclerView.firstVisibleItemPosition: Int\n\tget() = (layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition()\n\t\t?: RecyclerView.NO_POSITION\n\tset(value) {\n\t\tif (value != RecyclerView.NO_POSITION) {\n\t\t\t(layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(value, 0)\n\t\t}\n\t}\n\nval RecyclerView.visibleItemCount: Int\n\tget() = (layoutManager as? LinearLayoutManager)?.run {\n\t\tfindLastVisibleItemPosition() - findFirstVisibleItemPosition()\n\t} ?: 0\n\nfun <T> RecyclerView.ViewHolder.getItem(clazz: Class<T>): T? {\n\tval rawItem = when (this) {\n\t\tis AdapterDelegateViewBindingViewHolder<*, *> -> item\n\t\tis AdapterDelegateViewHolder<*> -> item\n\t\telse -> null\n\t} ?: return null\n\treturn if (clazz.isAssignableFrom(rawItem.javaClass)) {\n\t\tclazz.cast(rawItem)\n\t} else {\n\t\tnull\n\t}\n}\n\nval RecyclerView.isScrolledToTop: Boolean\n\tget() {\n\t\tif (isEmpty()) {\n\t\t\treturn true\n\t\t}\n\t\tval holder = findViewHolderForAdapterPosition(0)\n\t\treturn holder != null && holder.itemView.top >= 0\n\t}\n\nval RecyclerView.LayoutManager?.firstVisibleItemPosition\n\tget() = when (this) {\n\t\tis LinearLayoutManager -> findFirstVisibleItemPosition()\n\t\tis StaggeredGridLayoutManager -> findFirstVisibleItemPositions(null)[0]\n\t\telse -> 0\n\t}\n\nval RecyclerView.LayoutManager?.isLayoutReversed\n\tget() = when (this) {\n\t\tis LinearLayoutManager -> reverseLayout\n\t\tis StaggeredGridLayoutManager -> reverseLayout\n\t\telse -> false\n\t}\n\n// https://medium.com/flat-pack-tech/quickly-scroll-to-the-top-of-a-recyclerview-da15b717f3c4\nfun RecyclerView.smoothScrollToTop() {\n\tval layoutManager = layoutManager as? LinearLayoutManager ?: return\n\n\tif (!context.isAnimationsEnabled) {\n\t\tlayoutManager.scrollToPositionWithOffset(0, 0)\n\t\treturn\n\t}\n\n\tval smoothScroller = object : LinearSmoothScroller(context) {\n\t\tinit {\n\t\t\ttargetPosition = 0\n\t\t}\n\n\t\toverride fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?) =\n\t\t\tsuper.calculateSpeedPerPixel(displayMetrics) / DEFAULT_SPEED_FACTOR\n\t}\n\n\tval jumpBeforeScroll = layoutManager.findFirstVisibleItemPosition() > DEFAULT_JUMP_THRESHOLD\n\tif (jumpBeforeScroll) {\n\t\tlayoutManager.scrollToPositionWithOffset(DEFAULT_JUMP_THRESHOLD, 0)\n\t\tdoOnNextLayout {\n\t\t\tlayoutManager.startSmoothScroll(smoothScroller)\n\t\t}\n\t} else {\n\t\tlayoutManager.startSmoothScroll(smoothScroller)\n\t}\n}\n\nprivate const val DEFAULT_JUMP_THRESHOLD = 30\nprivate const val DEFAULT_SPEED_FACTOR = 1f\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Resources.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.Resources\nimport android.os.Build\nimport androidx.annotation.PluralsRes\nimport androidx.annotation.Px\nimport androidx.core.util.TypedValueCompat\nimport coil3.size.Size\nimport kotlin.math.roundToInt\nimport androidx.core.R as androidxR\n\n@Px\nfun Resources.resolveDp(dp: Int) = resolveDp(dp.toFloat()).roundToInt()\n\n@Px\nfun Resources.resolveDp(dp: Float) = TypedValueCompat.dpToPx(dp, displayMetrics)\n\n@Px\nfun Resources.resolveSp(sp: Float) = TypedValueCompat.spToPx(sp, displayMetrics)\n\n@SuppressLint(\"DiscouragedApi\")\nfun Context.getSystemBoolean(resName: String, fallback: Boolean): Boolean {\n\tval id = Resources.getSystem().getIdentifier(resName, \"bool\", \"android\")\n\treturn if (id != 0) {\n\t\tcreatePackageContext(\"android\", 0).resources.getBoolean(id)\n\t} else {\n\t\tfallback\n\t}\n}\n\nfun Resources.getQuantityStringSafe(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any): String = try {\n\tgetQuantityString(resId, quantity, *formatArgs)\n} catch (e: Resources.NotFoundException) {\n\tif (Build.VERSION.SDK_INT == Build.VERSION_CODES.VANILLA_ICE_CREAM) { // known issue\n\t\te.printStackTraceDebug()\n\t\tformatArgs.firstOrNull()?.toString() ?: quantity.toString()\n\t} else {\n\t\tthrow e\n\t}\n}\n\nfun Resources.getNotificationIconSize() = Size(\n\tgetDimensionPixelSize(androidxR.dimen.compat_notification_large_icon_max_width),\n\tgetDimensionPixelSize(androidxR.dimen.compat_notification_large_icon_max_height),\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/String.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.Context\nimport androidx.collection.arraySetOf\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.util.ellipsize\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport java.util.UUID\n\nfun String.toUUIDOrNull(): UUID? = try {\n\tUUID.fromString(this)\n} catch (e: IllegalArgumentException) {\n\te.printStackTraceDebug()\n\tnull\n}\n\nfun String.transliterate(skipMissing: Boolean): String {\n\tval cyr = charArrayOf(\n\t\t'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',\n\t\t'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я', 'ё', 'ў',\n\t)\n\tval lat = arrayOf(\n\t\t\"a\", \"b\", \"v\", \"g\", \"d\", \"e\", \"zh\", \"z\", \"i\", \"y\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\",\n\t\t\"r\", \"s\", \"t\", \"u\", \"f\", \"h\", \"ts\", \"ch\", \"sh\", \"sch\", \"\", \"i\", \"\", \"e\", \"ju\", \"ja\", \"jo\", \"w\",\n\t)\n\treturn buildString(length + 5) {\n\t\tfor (c in this@transliterate) {\n\t\t\tval p = cyr.binarySearch(c.lowercaseChar())\n\t\t\tif (p in lat.indices) {\n\t\t\t\tif (c.isUpperCase()) {\n\t\t\t\t\tappend(lat[p].uppercase())\n\t\t\t\t} else {\n\t\t\t\t\tappend(lat[p])\n\t\t\t\t}\n\t\t\t} else if (!skipMissing) {\n\t\t\t\tappend(c)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfun String.toFileNameSafe(): String = this.transliterate(false)\n\t.replace(Regex(\"[^a-z0-9_\\\\-]\", arraySetOf(RegexOption.IGNORE_CASE)), \" \")\n\t.replace(Regex(\"\\\\s+\"), \"_\")\n\nfun CharSequence.sanitize(): CharSequence {\n\treturn filterNot { c -> c.isReplacement() }\n}\n\nfun Char.isReplacement() = this in '\\uFFF0'..'\\uFFFF'\n\nfun <T> Collection<T>.joinToStringWithLimit(context: Context, limit: Int, transform: ((T) -> String)): String {\n\tif (size == 1) {\n\t\treturn transform(first()).ellipsize(limit)\n\t}\n\treturn buildString(limit + 6) {\n\t\tfor ((i, item) in this@joinToStringWithLimit.withIndex()) {\n\t\t\tval str = transform(item)\n\t\t\twhen {\n\t\t\t\ti == 0 -> append(str.ellipsize(limit - 4))\n\t\t\t\tlength + str.length > limit -> {\n\t\t\t\t\tappend(\", \")\n\t\t\t\t\tappend(context.getString(R.string.list_ellipsize_pattern, this@joinToStringWithLimit.size - i))\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\telse -> append(\", \").append(str)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfun String.isHttpUrl() = startsWith(\"https://\", ignoreCase = true) || startsWith(\"http://\", ignoreCase = true)\n\nfun concatStrings(context: Context, a: String?, b: String?): String? = when {\n\ta.isNullOrEmpty() && b.isNullOrEmpty() -> null\n\ta.isNullOrEmpty() -> b?.nullIfEmpty()\n\tb.isNullOrEmpty() -> a.nullIfEmpty()\n\telse -> context.getString(R.string.download_summary_pattern, a, b)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/TextView.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.graphics.Typeface\nimport android.graphics.drawable.Drawable\nimport android.widget.TextView\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StringRes\nimport androidx.annotation.StyleRes\nimport androidx.core.content.res.use\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.widget.TextViewCompat\n\nvar TextView.textAndVisible: CharSequence?\n\tget() = text?.takeIf { isVisible }\n\tset(value) {\n\t\ttext = value\n\t\tisGone = value.isNullOrEmpty()\n\t}\n\nvar TextView.drawableStart: Drawable?\n\tinline get() = compoundDrawablesRelative[0]\n\tset(value) {\n\t\tval dr = compoundDrawablesRelative\n\t\tsetCompoundDrawablesRelativeWithIntrinsicBounds(value, dr[1], dr[2], dr[3])\n\t}\n\nvar TextView.drawableEnd: Drawable?\n\tinline get() = compoundDrawablesRelative[2]\n\tset(value) {\n\t\tval dr = compoundDrawablesRelative\n\t\tsetCompoundDrawablesRelativeWithIntrinsicBounds(dr[0], dr[1], value, dr[3])\n\t}\n\nvar TextView.drawableTop: Drawable?\n\tinline get() = compoundDrawablesRelative[1]\n\tset(value) {\n\t\tval dr = compoundDrawablesRelative\n\t\tsetCompoundDrawablesRelativeWithIntrinsicBounds(dr[0], value, dr[2], dr[3])\n\t}\n\nfun TextView.setTextAndVisible(@StringRes textResId: Int) {\n\tif (textResId == 0) {\n\t\ttext = null\n\t\tisGone = true\n\t} else {\n\t\tsetText(textResId)\n\t\tisGone = text.isNullOrEmpty()\n\t}\n}\n\nfun TextView.setTextColorAttr(@AttrRes attrResId: Int) {\n\tsetTextColor(context.getThemeColorStateList(attrResId))\n}\n\nvar TextView.isBold: Boolean\n\tget() = typeface.isBold\n\tset(value) {\n\t\tvar style = typeface.style\n\t\tstyle = if (value) {\n\t\t\tstyle or Typeface.BOLD\n\t\t} else {\n\t\t\tstyle and Typeface.BOLD.inv()\n\t\t}\n\t\tsetTypeface(typeface, style)\n\t}\n\nfun TextView.setThemeTextAppearance(@AttrRes resId: Int, @StyleRes fallback: Int) {\n\tcontext.obtainStyledAttributes(intArrayOf(resId)).use {\n\t\tTextViewCompat.setTextAppearance(this, it.getResourceId(0, fallback))\n\t}\n}\n\nval TextView.isTextTruncated: Boolean\n\tget() {\n\t\tval l = layout ?: return false\n\t\tif (maxLines in 0 until l.lineCount) {\n\t\t\treturn true\n\t\t}\n\t\tval layoutLines = l.lineCount\n\t\treturn layoutLines > 0 && l.getEllipsisCount(layoutLines - 1) > 0\n\t}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.Context\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport android.content.res.TypedArray\nimport android.graphics.Color\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.annotation.FloatRange\nimport androidx.annotation.Px\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.res.use\nimport androidx.core.graphics.ColorUtils\n\nval Resources.isNightMode: Boolean\n\tget() = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES\n\nfun Context.getThemeDrawable(\n\t@AttrRes resId: Int,\n) = obtainStyledAttributes(intArrayOf(resId)).use {\n\tit.getDrawable(0)\n}\n\n@ColorInt\nfun Context.getThemeColor(\n\t@AttrRes resId: Int,\n\t@ColorInt fallback: Int = Color.TRANSPARENT,\n) = obtainStyledAttributes(intArrayOf(resId)).use {\n\tit.getColor(0, fallback)\n}\n\n@Px\nfun Context.getThemeDimensionPixelSize(\n\t@AttrRes resId: Int,\n\t@Px fallback: Int = 0,\n) = obtainStyledAttributes(intArrayOf(resId)).use {\n\tit.getDimensionPixelSize(0, fallback)\n}\n\n@Px\nfun Context.getThemeDimensionPixelOffset(\n\t@AttrRes resId: Int,\n\t@Px fallback: Int = 0,\n) = obtainStyledAttributes(intArrayOf(resId)).use {\n\tit.getDimensionPixelOffset(0, fallback)\n}\n\n@ColorInt\nfun Context.getThemeColor(\n\t@AttrRes resId: Int,\n\t@FloatRange(from = 0.0, to = 1.0) alphaFactor: Float,\n\t@ColorInt fallback: Int = Color.TRANSPARENT,\n): Int {\n\tif (alphaFactor <= 0f) {\n\t\treturn Color.TRANSPARENT\n\t}\n\tval color = getThemeColor(resId, fallback)\n\tif (alphaFactor >= 1f) {\n\t\treturn color\n\t}\n\treturn ColorUtils.setAlphaComponent(color, (0xFF * alphaFactor).toInt())\n}\n\nfun Context.getThemeColorStateList(\n\t@AttrRes resId: Int,\n) = obtainStyledAttributes(intArrayOf(resId)).use {\n\tit.getColorStateList(0)\n}\n\nfun Context.getThemeResId(\n\t@AttrRes resId: Int,\n\tfallback: Int\n): Int = obtainStyledAttributes(intArrayOf(resId)).use {\n\tit.getResourceId(0, fallback)\n}\n\n@Deprecated(\"\")\nfun TypedArray.getDrawableCompat(context: Context, index: Int): Drawable? {\n\tval resId = getResourceId(index, 0)\n\treturn if (resId != 0) ContextCompat.getDrawable(context, resId) else null\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.ActivityNotFoundException\nimport android.content.res.Resources\nimport android.database.sqlite.SQLiteFullException\nimport androidx.annotation.DrawableRes\nimport coil3.network.HttpException\nimport com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException\nimport kotlinx.coroutines.CancellationException\nimport okhttp3.Response\nimport okhttp3.internal.http2.StreamResetException\nimport okio.FileNotFoundException\nimport okio.IOException\nimport okio.ProtocolException\nimport org.acra.ktx.sendSilentlyWithAcra\nimport org.acra.ktx.sendWithAcra\nimport org.jsoup.HttpStatusException\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.BadBackupFormatException\nimport org.koitharu.kotatsu.core.exceptions.CaughtException\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.exceptions.EmptyHistoryException\nimport org.koitharu.kotatsu.core.exceptions.EmptyMangaException\nimport org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException\nimport org.koitharu.kotatsu.core.exceptions.InteractiveActionRequiredException\nimport org.koitharu.kotatsu.core.exceptions.NoDataReceivedException\nimport org.koitharu.kotatsu.core.exceptions.NonFileUriException\nimport org.koitharu.kotatsu.core.exceptions.ProxyConfigException\nimport org.koitharu.kotatsu.core.exceptions.SyncApiException\nimport org.koitharu.kotatsu.core.exceptions.UnsupportedFileException\nimport org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException\nimport org.koitharu.kotatsu.core.exceptions.WrapperIOException\nimport org.koitharu.kotatsu.core.exceptions.WrongPasswordException\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED\nimport org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED\nimport org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED\nimport org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_MULTIPLE_STATES_NOT_SUPPORTED\nimport org.koitharu.kotatsu.parsers.ErrorMessages.SEARCH_NOT_SUPPORTED\nimport org.koitharu.kotatsu.parsers.exception.AuthRequiredException\nimport org.koitharu.kotatsu.parsers.exception.ContentUnavailableException\nimport org.koitharu.kotatsu.parsers.exception.NotFoundException\nimport org.koitharu.kotatsu.parsers.exception.ParseException\nimport org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException\nimport java.io.File\nimport java.net.ConnectException\nimport java.net.HttpURLConnection\nimport java.net.NoRouteToHostException\nimport java.net.SocketException\nimport java.net.SocketTimeoutException\nimport java.net.UnknownHostException\nimport java.util.Locale\nimport java.util.zip.ZipException\n\nprivate const val MSG_NO_SPACE_LEFT = \"No space left on device\"\nprivate const val MSG_CONNECTION_RESET = \"Connection reset\"\nprivate const val IMAGE_FORMAT_NOT_SUPPORTED = \"Image format not supported\"\n\nprivate val FNFE_MESSAGE_REGEX = Regex(\"^(/[^\\\\s:]+)?.+?\\\\s([A-Z]{2,6})?\\\\s.+$\")\n\nfun Throwable.getDisplayMessage(resources: Resources): String = getDisplayMessageOrNull(resources)\n    ?: resources.getString(R.string.error_occurred)\n\nprivate fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = when (this) {\n    is CancellationException -> cause?.getDisplayMessageOrNull(resources) ?: message\n    is CaughtException -> cause.getDisplayMessageOrNull(resources)\n    is WrapperIOException -> cause.getDisplayMessageOrNull(resources)\n    is ScrobblerAuthRequiredException -> resources.getString(\n        R.string.scrobbler_auth_required,\n        resources.getString(scrobbler.titleResId),\n    )\n\n    is AuthRequiredException -> resources.getString(R.string.auth_required)\n    is InteractiveActionRequiredException -> resources.getString(R.string.additional_action_required)\n    is CloudFlareProtectedException -> resources.getString(R.string.captcha_required_message)\n    is CloudFlareBlockedException -> resources.getString(R.string.blocked_by_server_message)\n    is ActivityNotFoundException,\n    is UnsupportedOperationException,\n        -> resources.getString(R.string.operation_not_supported)\n\n    is TooManyRequestExceptions -> {\n        val delay = getRetryDelay()\n        val formattedTime = if (delay > 0L && delay < Long.MAX_VALUE) {\n            resources.formatDurationShort(delay)\n        } else {\n            null\n        }\n        if (formattedTime != null) {\n            resources.getString(R.string.too_many_requests_message_retry, formattedTime)\n        } else {\n            resources.getString(R.string.too_many_requests_message)\n        }\n    }\n\n    is ZipException -> resources.getString(R.string.error_corrupted_zip, this.message.orEmpty())\n    is SQLiteFullException -> resources.getString(R.string.error_no_space_left)\n    is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)\n    is BadBackupFormatException -> resources.getString(R.string.unsupported_backup_message)\n    is FileNotFoundException -> parseMessage(resources) ?: message\n    is AccessDeniedException -> resources.getString(R.string.no_access_to_file)\n    is NonFileUriException -> resources.getString(R.string.error_non_file_uri)\n    is EmptyHistoryException -> resources.getString(R.string.history_is_empty)\n    is EmptyMangaException -> reason?.let { resources.getString(it.msgResId) } ?: cause?.getDisplayMessage(resources)\n    is ProxyConfigException -> resources.getString(R.string.invalid_proxy_configuration)\n    is SyncApiException,\n    is ContentUnavailableException -> message\n\n    is ParseException -> shortMessage\n    is ConnectException,\n    is UnknownHostException,\n    is NoRouteToHostException,\n    is SocketTimeoutException -> resources.getString(R.string.network_error)\n\n    is ImageDecodeException -> {\n        val type = format?.substringBefore('/')\n        val formatString = format.ifNullOrEmpty { resources.getString(R.string.unknown).lowercase(Locale.getDefault()) }\n        if (type.isNullOrEmpty() || type == \"image\") {\n            resources.getString(R.string.error_image_format, formatString)\n        } else {\n            resources.getString(R.string.error_not_image, formatString)\n        }\n    }\n\n    is NoDataReceivedException -> resources.getString(R.string.error_no_data_received)\n    is IncompatiblePluginException -> {\n        cause?.getDisplayMessageOrNull(resources)?.let {\n            resources.getString(R.string.plugin_incompatible_with_cause, it)\n        } ?: resources.getString(R.string.plugin_incompatible)\n    }\n\n    is WrongPasswordException -> resources.getString(R.string.wrong_password)\n    is NotFoundException -> resources.getString(R.string.not_found_404)\n    is UnsupportedSourceException -> resources.getString(R.string.unsupported_source)\n\n    is HttpException -> getHttpDisplayMessage(response.code, resources)\n    is HttpStatusException -> getHttpDisplayMessage(statusCode, resources)\n\n    else -> mapDisplayMessage(message, resources) ?: message\n}.takeUnless { it.isNullOrBlank() }\n\n@DrawableRes\nfun Throwable.getDisplayIcon(): Int = when (this) {\n    is AuthRequiredException -> R.drawable.ic_auth_key_large\n    is CloudFlareProtectedException -> R.drawable.ic_bot_large\n    is UnknownHostException,\n    is SocketTimeoutException,\n    is ConnectException,\n    is NoRouteToHostException,\n    is ProtocolException -> R.drawable.ic_plug_large\n\n    is CloudFlareBlockedException -> R.drawable.ic_denied_large\n\n    is InteractiveActionRequiredException -> R.drawable.ic_interaction_large\n    else -> R.drawable.ic_error_large\n}\n\nfun Throwable.getCauseUrl(): String? = when (this) {\n    is ParseException -> url\n    is NotFoundException -> url\n    is TooManyRequestExceptions -> url\n    is CaughtException -> cause.getCauseUrl()\n    is WrapperIOException -> cause.getCauseUrl()\n    is NoDataReceivedException -> url\n    is CloudFlareBlockedException -> url\n    is CloudFlareProtectedException -> url\n    is InteractiveActionRequiredException -> url\n    is HttpStatusException -> url\n    is UnsupportedSourceException -> manga?.publicUrl?.takeIf { it.isHttpUrl() }\n    is EmptyMangaException -> manga.publicUrl.takeIf { it.isHttpUrl() }\n    is HttpException -> (response.delegate as? Response)?.request?.url?.toString()\n    else -> null\n}\n\nprivate fun getHttpDisplayMessage(statusCode: Int, resources: Resources): String? = when (statusCode) {\n    HttpURLConnection.HTTP_NOT_FOUND -> resources.getString(R.string.not_found_404)\n    HttpURLConnection.HTTP_FORBIDDEN -> resources.getString(R.string.access_denied_403)\n    HttpURLConnection.HTTP_GATEWAY_TIMEOUT -> resources.getString(R.string.network_unavailable)\n    in 500..599 -> resources.getString(R.string.server_error, statusCode)\n    else -> null\n}\n\nprivate fun mapDisplayMessage(msg: String?, resources: Resources): String? = when {\n    msg.isNullOrEmpty() -> null\n    msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)\n    msg.contains(IMAGE_FORMAT_NOT_SUPPORTED) -> resources.getString(R.string.error_corrupted_file)\n    msg == MSG_CONNECTION_RESET -> resources.getString(R.string.error_connection_reset)\n    msg == FILTER_MULTIPLE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_genres_not_supported)\n    msg == FILTER_MULTIPLE_STATES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_states_not_supported)\n    msg == SEARCH_NOT_SUPPORTED -> resources.getString(R.string.error_search_not_supported)\n    msg == FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_locale_genre_not_supported)\n    msg == FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_states_genre_not_supported)\n    else -> null\n}\n\nfun Throwable.isReportable(): Boolean {\n    if (this is Error) {\n        return true\n    }\n    if (this is CaughtException) {\n        return cause.isReportable()\n    }\n    if (this is WrapperIOException) {\n        return cause.isReportable()\n    }\n    if (ExceptionResolver.canResolve(this)) {\n        return false\n    }\n    if (this is ParseException\n        || this.isNetworkError()\n        || this is CloudFlareBlockedException\n        || this is CloudFlareProtectedException\n        || this is BadBackupFormatException\n        || this is WrongPasswordException\n        || this is TooManyRequestExceptions\n        || this is HttpStatusException\n    ) {\n        return false\n    }\n    return true\n}\n\nfun Throwable.isNetworkError(): Boolean {\n    return this is UnknownHostException\n        || this is SocketTimeoutException\n        || this is StreamResetException\n        || this is SocketException\n        || this is HttpException && response.code == HttpURLConnection.HTTP_GATEWAY_TIMEOUT\n}\n\nfun Throwable.report(silent: Boolean = false) {\n    val exception = CaughtException(this)\n    if (!silent) {\n        exception.sendWithAcra()\n    } else if (!BuildConfig.DEBUG) {\n        exception.sendSilentlyWithAcra()\n    }\n}\n\nfun Throwable.isWebViewUnavailable(): Boolean {\n    val trace = stackTraceToString()\n    return trace.contains(\"android.webkit.WebView.<init>\")\n}\n\n@Suppress(\"FunctionName\")\nfun NoSpaceLeftException() = IOException(MSG_NO_SPACE_LEFT)\n\nfun FileNotFoundException.getFile(): File? {\n    val groups = FNFE_MESSAGE_REGEX.matchEntire(message ?: return null)?.groupValues ?: return null\n    return groups.getOrNull(1)?.let { File(it) }\n}\n\nfun FileNotFoundException.parseMessage(resources: Resources): String? {\n    /*\n    Examples:\n    /storage/0000-0000/Android/media/d1f08350-0c25-460b-8f50-008e49de3873.jpg.tmp: open failed: EROFS (Read-only file system)\n     /storage/emulated/0/Android/data/org.koitharu.kotatsu/cache/pages/fe06e192fa371e55918980f7a24c91ea.jpg: open failed: ENOENT (No such file or directory)\n     /storage/0000-0000/Android/data/org.koitharu.kotatsu/files/manga/e57d3af4-216e-48b2-8432-1541d58eea1e.tmp (I/O error)\n     */\n    val groups = FNFE_MESSAGE_REGEX.matchEntire(message ?: return null)?.groupValues ?: return null\n    val path = groups.getOrNull(1)\n    val error = groups.getOrNull(2)\n    val baseMessageIs = when (error) {\n        \"EROFS\" -> R.string.no_write_permission_to_file\n        \"ENOENT\" -> R.string.file_not_found\n        else -> return null\n    }\n    return if (path.isNullOrEmpty()) {\n        resources.getString(baseMessageIs)\n    } else {\n        resources.getString(\n            R.string.inline_preference_pattern,\n            resources.getString(baseMessageIs),\n            path,\n        )\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Toolbar.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport androidx.annotation.DrawableRes\nimport androidx.appcompat.widget.Toolbar\n\nfun Toolbar.setNavigationIconSafe(@DrawableRes iconRes: Int, retry: Boolean = true) {\n\ttry {\n\t\tsetNavigationIcon(iconRes)\n\t} catch (e: IllegalStateException) {\n\t\tif (retry) {\n\t\t\tpost { setNavigationIconSafe(iconRes, retry = false) }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Uri.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.net.Uri\nimport androidx.core.net.toUri\nimport okio.Path\nimport java.io.File\n\nconst val URI_SCHEME_ZIP = \"file+zip\"\nprivate const val URI_SCHEME_FILE = \"file\"\nprivate const val URI_SCHEME_HTTP = \"http\"\nprivate const val URI_SCHEME_HTTPS = \"https\"\nprivate const val URI_SCHEME_LEGACY_CBZ = \"cbz\"\nprivate const val URI_SCHEME_LEGACY_ZIP = \"zip\"\n\nfun Uri.isZipUri() = scheme.let {\n\tit == URI_SCHEME_ZIP || it == URI_SCHEME_LEGACY_CBZ || it == URI_SCHEME_LEGACY_ZIP\n}\n\nfun Uri.isFileUri() = scheme == URI_SCHEME_FILE\n\nfun Uri.isNetworkUri() = scheme.let {\n\tit == URI_SCHEME_HTTP || it == URI_SCHEME_HTTPS\n}\n\nfun File.toZipUri(entryPath: String): Uri = \"$URI_SCHEME_ZIP://$absolutePath#$entryPath\".toUri()\n\nfun File.toZipUri(entryPath: Path?): Uri =\n\ttoZipUri(entryPath?.toString()?.removePrefix(Path.DIRECTORY_SEPARATOR).orEmpty())\n\nfun String.toUriOrNull() = if (isEmpty()) null else this.toUri()\n\nfun File.toUri(fragment: String?): Uri = toUri().run {\n\tif (fragment != null) {\n\t\tbuildUpon().fragment(fragment).build()\n\t} else {\n\t\tthis\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.content.Context\nimport android.graphics.Point\nimport android.graphics.Rect\nimport android.os.Build\nimport android.view.View\nimport android.view.View.MeasureSpec\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport android.widget.Checkable\nimport androidx.annotation.StringRes\nimport androidx.appcompat.widget.ActionMenuView\nimport androidx.appcompat.widget.Toolbar\nimport androidx.appcompat.widget.TooltipCompat\nimport androidx.core.view.children\nimport androidx.core.view.descendants\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ViewHolder\nimport androidx.swiperefreshlayout.widget.CircularProgressDrawable\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.button.MaterialButton\nimport com.google.android.material.chip.Chip\nimport com.google.android.material.progressindicator.BaseProgressIndicator\nimport com.google.android.material.slider.RangeSlider\nimport com.google.android.material.slider.Slider\nimport com.google.android.material.tabs.TabLayout\nimport kotlin.math.roundToInt\n\nfun View.hasGlobalPoint(x: Int, y: Int): Boolean {\n\tif (visibility != View.VISIBLE) {\n\t\treturn false\n\t}\n\tval rect = Rect()\n\tgetGlobalVisibleRect(rect)\n\treturn rect.contains(x, y)\n}\n\nval ViewGroup.hasVisibleChildren: Boolean\n\tget() = children.any { it.isVisible }\n\nfun View.measureHeight(): Int {\n\tval vh = height\n\treturn if (vh == 0) {\n\t\tmeasure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)\n\t\tmeasuredHeight\n\t} else vh\n}\n\nfun View.measureWidth(): Int {\n\tval vw = width\n\treturn if (vw == 0) {\n\t\tmeasure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)\n\t\tmeasuredWidth\n\t} else vw\n}\n\ninline fun ViewPager2.doOnPageChanged(crossinline callback: (Int) -> Unit) {\n\tregisterOnPageChangeCallback(\n\t\tobject : ViewPager2.OnPageChangeCallback() {\n\n\t\t\toverride fun onPageSelected(position: Int) {\n\t\t\t\tsuper.onPageSelected(position)\n\t\t\t\tcallback(position)\n\t\t\t}\n\t\t},\n\t)\n}\n\nval ViewPager2.recyclerView: RecyclerView?\n\tget() = children.firstNotNullOfOrNull { it as? RecyclerView }\n\nfun ViewPager2.findCurrentViewHolder(): ViewHolder? {\n\treturn recyclerView?.findViewHolderForAdapterPosition(currentItem)\n}\n\nfun FragmentManager.findCurrentPagerFragment(pager: ViewPager2): Fragment? {\n\tval currentId = pager.adapter?.getItemId(pager.currentItem) ?: pager.currentItem\n\treturn findFragmentByTag(\"f$currentId\")\n}\n\nfun View.resetTransformations() {\n\talpha = 1f\n\ttranslationX = 0f\n\ttranslationY = 0f\n\ttranslationZ = 0f\n\tscaleX = 1f\n\tscaleY = 1f\n\trotation = 0f\n\trotationX = 0f\n\trotationY = 0f\n}\n\nfun Slider.setValueRounded(newValue: Float) {\n\tval step = stepSize\n\tval roundedValue = if (step <= 0f) {\n\t\tnewValue\n\t} else {\n\t\t(newValue / step).roundToInt() * step\n\t}\n\tvalue = roundedValue.coerceIn(valueFrom, valueTo)\n}\n\nfun RangeSlider.setValuesRounded(vararg newValues: Float) {\n\tval step = stepSize\n\tvalues = newValues.map { newValue ->\n\t\tif (step <= 0f) {\n\t\t\tnewValue\n\t\t} else {\n\t\t\t(newValue / step).roundToInt() * step\n\t\t}.coerceIn(valueFrom, valueTo)\n\t}\n}\n\nfun RecyclerView.invalidateNestedItemDecorations() {\n\tdescendants.filterIsInstance<RecyclerView>().forEach {\n\t\tit.invalidateItemDecorations()\n\t}\n}\n\nval View.parentView: ViewGroup?\n\tget() = parent as? ViewGroup\n\n@Suppress(\"UnusedReceiverParameter\")\nfun View.measureDimension(desiredSize: Int, measureSpec: Int): Int {\n\tvar result: Int\n\tval specMode = MeasureSpec.getMode(measureSpec)\n\tval specSize = MeasureSpec.getSize(measureSpec)\n\tif (specMode == MeasureSpec.EXACTLY) {\n\t\tresult = specSize\n\t} else {\n\t\tresult = desiredSize\n\t\tif (specMode == MeasureSpec.AT_MOST) {\n\t\t\tresult = result.coerceAtMost(specSize)\n\t\t}\n\t}\n\treturn result\n}\n\nfun <V> V.setChecked(checked: Boolean, animate: Boolean) where V : View, V : Checkable {\n\tval skipAnimation = !animate && checked != isChecked\n\tisChecked = checked\n\tif (skipAnimation) {\n\t\tjumpDrawablesToCurrentState()\n\t}\n}\n\nvar View.isRtl: Boolean\n\tget() = layoutDirection == View.LAYOUT_DIRECTION_RTL\n\tset(value) {\n\t\tlayoutDirection = if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR\n\t}\n\nfun TabLayout.setTabsEnabled(enabled: Boolean) {\n\tfor (i in 0 until tabCount) {\n\t\tgetTabAt(i)?.view?.isEnabled = enabled\n\t}\n}\n\nfun BaseProgressIndicator<*>.showOrHide(value: Boolean) {\n\tif (value) {\n\t\tshow()\n\t} else {\n\t\thide()\n\t}\n}\n\nfun View.setTooltipCompat(tooltip: CharSequence?) {\n\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\ttooltipText = tooltip\n\t} else if (!isLongClickable) { // don't use TooltipCompat if has a LongClickListener\n\t\tTooltipCompat.setTooltipText(this, tooltip)\n\t}\n}\n\nfun View.setTooltipCompat(@StringRes tooltipResId: Int) = setTooltipCompat(context.getString(tooltipResId))\n\nval Toolbar.menuView: ActionMenuView?\n\tget() {\n\t\tmenu // to call ensureMenu()\n\t\treturn children.firstNotNullOfOrNull { it as? ActionMenuView }\n\t}\n\nfun MaterialButton.setProgressIcon() {\n\tval progressDrawable = CircularProgressDrawable(context)\n\tprogressDrawable.strokeWidth = resources.resolveDp(2f)\n\tprogressDrawable.setColorSchemeColors(currentTextColor)\n\tprogressDrawable.setTintList(textColors)\n\ticon = progressDrawable\n\tprogressDrawable.start()\n}\n\nfun Chip.setProgressIcon() {\n\tval progressDrawable = CircularProgressDrawable(context)\n\tprogressDrawable.strokeWidth = resources.resolveDp(2f)\n\tprogressDrawable.setColorSchemeColors(currentTextColor)\n\tchipIcon = progressDrawable\n\tprogressDrawable.start()\n}\n\nfun View.setContentDescriptionAndTooltip(@StringRes resId: Int) {\n\tval text = resources.getString(resId)\n\tcontentDescription = text\n\tsetTooltipCompat(text)\n}\n\nfun View.getWindowBounds(): Rect {\n\tval wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager\n\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n\t\twm.currentWindowMetrics.bounds\n\t} else {\n\t\tval size = Point()\n\t\t@Suppress(\"DEPRECATION\")\n\t\tdisplay.getSize(size)\n\t\tRect(0, 0, size.x, size.y)\n\t}\n}\n\nfun View.isOnScreen(): Boolean {\n\tif (!isShown) {\n\t\treturn false\n\t}\n\tval actualPosition = Rect()\n\tgetGlobalVisibleRect(actualPosition)\n\treturn actualPosition.intersect(getWindowBounds())\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ViewModel.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport androidx.annotation.MainThread\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.createViewModelLazy\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.viewmodel.CreationExtras\n\n@MainThread\ninline fun <reified VM : ViewModel> Fragment.parentFragmentViewModels(\n\tnoinline extrasProducer: (() -> CreationExtras)? = null,\n\tnoinline factoryProducer: (() -> ViewModelProvider.Factory)? = null,\n): Lazy<VM> = createViewModelLazy(\n\tviewModelClass = VM::class,\n\tstoreProducer = { requireParentFragment().viewModelStore },\n\textrasProducer = { extrasProducer?.invoke() ?: requireParentFragment().defaultViewModelCreationExtras },\n\tfactoryProducer = factoryProducer ?: { requireParentFragment().defaultViewModelProviderFactory },\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/WorkManager.kt",
    "content": "package org.koitharu.kotatsu.core.util.ext\n\nimport android.annotation.SuppressLint\nimport androidx.work.Data\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport androidx.work.WorkQuery\nimport androidx.work.WorkRequest\nimport androidx.work.impl.WorkManagerImpl\nimport androidx.work.impl.model.WorkSpec\nimport kotlinx.coroutines.guava.await\nimport java.util.UUID\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.coroutines.suspendCoroutine\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.deleteWork(id: UUID) = suspendCoroutine { cont ->\n\tworkManagerImpl.workTaskExecutor.executeOnTaskThread {\n\t\ttry {\n\t\t\tworkManagerImpl.workDatabase.workSpecDao().delete(id.toString())\n\t\t\tcont.resume(Unit)\n\t\t} catch (e: Exception) {\n\t\t\tcont.resumeWithException(e)\n\t\t}\n\t}\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.deleteWorks(ids: Collection<UUID>) = suspendCoroutine { cont ->\n\tworkManagerImpl.workTaskExecutor.executeOnTaskThread {\n\t\ttry {\n\t\t\tval db = workManagerImpl.workDatabase\n\t\t\tdb.runInTransaction {\n\t\t\t\tfor (id in ids) {\n\t\t\t\t\tdb.workSpecDao().delete(id.toString())\n\t\t\t\t}\n\t\t\t}\n\t\t\tcont.resume(Unit)\n\t\t} catch (e: Exception) {\n\t\t\tcont.resumeWithException(e)\n\t\t}\n\t}\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.awaitWorkInfosByTag(tag: String): List<WorkInfo> {\n\treturn getWorkInfosByTag(tag).await()\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.awaitFinishedWorkInfosByTag(tag: String): List<WorkInfo> {\n\tval query = WorkQuery.Builder.fromTags(listOf(tag))\n\t\t.addStates(listOf(WorkInfo.State.SUCCEEDED, WorkInfo.State.CANCELLED, WorkInfo.State.FAILED))\n\t\t.build()\n\treturn getWorkInfos(query).await()\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.awaitWorkInfoById(id: UUID): WorkInfo? {\n\treturn getWorkInfoById(id).await()\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.awaitUniqueWorkInfoByName(name: String): List<WorkInfo> {\n\treturn getWorkInfosForUniqueWork(name).await()\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.awaitUpdateWork(request: WorkRequest): WorkManager.UpdateResult {\n\treturn updateWork(request).await()\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.getWorkSpec(id: UUID): WorkSpec? = suspendCoroutine { cont ->\n\tworkManagerImpl.workTaskExecutor.executeOnTaskThread {\n\t\ttry {\n\t\t\tval spec = workManagerImpl.workDatabase.workSpecDao().getWorkSpec(id.toString())\n\t\t\tcont.resume(spec)\n\t\t} catch (e: Exception) {\n\t\t\tcont.resumeWithException(e)\n\t\t}\n\t}\n}\n\n@SuppressLint(\"RestrictedApi\")\nsuspend fun WorkManager.getWorkInputData(id: UUID): Data? = getWorkSpec(id)?.input\n\nval Data.isEmpty: Boolean\n\tget() = this == Data.EMPTY\n\nprivate val WorkManager.workManagerImpl\n\t@SuppressLint(\"RestrictedApi\") inline get() = this as WorkManagerImpl\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/iterator/MappingIterator.kt",
    "content": "package org.koitharu.kotatsu.core.util.iterator\n\nclass MappingIterator<T, R>(\n\tprivate val upstream: Iterator<T>,\n\tprivate val mapper: (T) -> R,\n) : Iterator<R> {\n\n\toverride fun hasNext(): Boolean = upstream.hasNext()\n\n\toverride fun next(): R = mapper(upstream.next())\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/ImageRequestIndicatorListener.kt",
    "content": "package org.koitharu.kotatsu.core.util.progress\n\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.SuccessResult\nimport com.google.android.material.progressindicator.BaseProgressIndicator\n\nclass ImageRequestIndicatorListener(\n\tprivate val indicators: Collection<BaseProgressIndicator<*>>,\n) : ImageRequest.Listener {\n\n\toverride fun onCancel(request: ImageRequest) = hide()\n\n\toverride fun onError(request: ImageRequest, result: ErrorResult) = hide()\n\n\toverride fun onStart(request: ImageRequest) = show()\n\n\toverride fun onSuccess(request: ImageRequest, result: SuccessResult) = hide()\n\n\tprivate fun hide() {\n\t\tindicators.forEach { it.hide() }\n\t}\n\n\tprivate fun show() {\n\t\tindicators.forEach { it.show() }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/IntPercentLabelFormatter.kt",
    "content": "package org.koitharu.kotatsu.core.util.progress\n\nimport android.content.Context\nimport com.google.android.material.slider.LabelFormatter\nimport org.koitharu.kotatsu.R\n\nclass IntPercentLabelFormatter(context: Context) : LabelFormatter {\n\n\tprivate val pattern = context.getString(R.string.percent_string_pattern)\n\n\toverride fun getFormattedValue(value: Float) = pattern.format(value.toInt().toString())\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/Progress.kt",
    "content": "package org.koitharu.kotatsu.core.util.progress\n\ndata class Progress(\n\tval progress: Int,\n\tval total: Int,\n) : Comparable<Progress> {\n\n\tval percent: Float\n\t\tget() = if (total == 0) 0f else progress / total.toFloat()\n\n\tval isEmpty: Boolean\n\t\tget() = progress == 0\n\n\tval isFull: Boolean\n\t\tget() = progress == total\n\n\tval isIndeterminate: Boolean\n\t\tget() = total < 0\n\n\toverride fun compareTo(other: Progress): Int = if (total == other.total) {\n\t\tprogress.compareTo(other.progress)\n\t} else {\n\t\tpercent.compareTo(other.percent)\n\t}\n\n\toperator fun inc() = if (isFull) {\n\t\tthis\n\t} else {\n\t\tcopy(\n\t\t\tprogress = progress + 1,\n\t\t\ttotal = total,\n\t\t)\n\t}\n\n\toperator fun dec() = if (isEmpty) {\n\t\tthis\n\t} else {\n\t\tcopy(\n\t\t\tprogress = progress - 1,\n\t\t\ttotal = total,\n\t\t)\n\t}\n\n\toperator fun plus(child: Progress) = Progress(\n\t\tprogress = progress * child.total + child.progress,\n\t\ttotal = total * child.total,\n\t)\n\n\tfun percentSting() = (percent * 100f).toInt().toString()\n\n\tcompanion object {\n\n\t\tval INDETERMINATE = Progress(0, -1)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/ProgressDeferred.kt",
    "content": "package org.koitharu.kotatsu.core.util.progress\n\nimport kotlinx.coroutines.Deferred\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.StateFlow\n\nclass ProgressDeferred<T, P>(\n\tprivate val deferred: Deferred<T>,\n\tprivate val progress: StateFlow<P>,\n) : Deferred<T> by deferred {\n\n\tval progressValue: P\n\t\tget() = progress.value\n\n\tfun progressAsFlow(): Flow<P> = progress\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/ProgressResponseBody.kt",
    "content": "package org.koitharu.kotatsu.core.util.progress\n\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport okhttp3.MediaType\nimport okhttp3.ResponseBody\nimport okio.Buffer\nimport okio.BufferedSource\nimport okio.ForwardingSource\nimport okio.Source\nimport okio.buffer\n\nclass ProgressResponseBody(\n\tprivate val delegate: ResponseBody,\n\tprivate val progressState: MutableStateFlow<Float>,\n) : ResponseBody() {\n\n\tprivate var bufferedSource: BufferedSource? = null\n\n\toverride fun close() {\n\t\tsuper.close()\n\t\tdelegate.close()\n\t}\n\n\toverride fun contentLength(): Long = delegate.contentLength()\n\n\toverride fun contentType(): MediaType? = delegate.contentType()\n\n\toverride fun source(): BufferedSource {\n\t\treturn bufferedSource ?: synchronized(this) {\n\t\t\tbufferedSource ?: ProgressSource(delegate.source(), contentLength(), progressState).buffer().also {\n\t\t\t\tbufferedSource = it\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class ProgressSource(\n\t\tdelegate: Source,\n\t\tprivate val contentLength: Long,\n\t\tprivate val progressState: MutableStateFlow<Float>,\n\t) : ForwardingSource(delegate) {\n\n\t\tprivate var totalBytesRead = 0L\n\n\t\toverride fun read(sink: Buffer, byteCount: Long): Long {\n\t\t\tval bytesRead = super.read(sink, byteCount)\n\t\t\tif (contentLength > 0) {\n\t\t\t\ttotalBytesRead += if (bytesRead != -1L) bytesRead else 0\n\t\t\t\tprogressState.value = (totalBytesRead.toDouble() / contentLength.toDouble()).toFloat()\n\t\t\t}\n\t\t\treturn bytesRead\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/util/progress/RealtimeEtaEstimator.kt",
    "content": "package org.koitharu.kotatsu.core.util.progress\n\nimport android.os.SystemClock\nimport androidx.annotation.AnyThread\nimport androidx.collection.CircularArray\nimport java.util.concurrent.TimeUnit\nimport kotlin.math.roundToLong\n\nclass RealtimeEtaEstimator {\n\n\tprivate val ticks = CircularArray<Tick>(MAX_TICKS)\n\n\t@Volatile\n\tprivate var lastChange = 0L\n\n\t@AnyThread\n\tfun onProgressChanged(value: Int, total: Int) {\n\t\tif (total <= 0 || value > total) {\n\t\t\treset()\n\t\t\treturn\n\t\t}\n\t\tval tick = Tick(value, total, SystemClock.elapsedRealtime())\n\t\tsynchronized(this) {\n\t\t\tif (!ticks.isEmpty()) {\n\t\t\t\tval last = ticks.last\n\t\t\t\tif (last.value == tick.value && last.total == tick.total) {\n\t\t\t\t\tticks.popLast()\n\t\t\t\t} else {\n\t\t\t\t\tlastChange = tick.timestamp\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlastChange = tick.timestamp\n\t\t\t}\n\t\t\tticks.addLast(tick)\n\t\t}\n\t}\n\n\t@AnyThread\n\tfun reset() = synchronized(this) {\n\t\tticks.clear()\n\t\tlastChange = 0L\n\t}\n\n\t@AnyThread\n\tfun getEta(): Long {\n\t\tval etl = getEstimatedTimeLeft()\n\t\treturn if (etl == NO_TIME || etl > MAX_TIME) NO_TIME else System.currentTimeMillis() + etl\n\t}\n\n\t@AnyThread\n\tfun isStuck(): Boolean = synchronized(this) {\n\t\treturn ticks.size() >= MIN_ESTIMATE_TICKS && (SystemClock.elapsedRealtime() - lastChange) > STUCK_DELAY\n\t}\n\n\tprivate fun getEstimatedTimeLeft(): Long = synchronized(this) {\n\t\tval ticksCount = ticks.size()\n\t\tif (ticksCount < MIN_ESTIMATE_TICKS) {\n\t\t\treturn NO_TIME\n\t\t}\n\t\tval percentDiff = ticks.last.percent - ticks.first.percent\n\t\tval timeDiff = ticks.last.timestamp - ticks.first.timestamp\n\t\tif (percentDiff <= 0 || timeDiff <= 0) {\n\t\t\treturn NO_TIME\n\t\t}\n\t\tval averageTime = timeDiff / percentDiff\n\t\tval percentLeft = 1.0 - ticks.last.percent\n\t\treturn (percentLeft * averageTime).roundToLong()\n\t}\n\n\tprivate class Tick(\n\t\t@JvmField val value: Int,\n\t\t@JvmField val total: Int,\n\t\t@JvmField val timestamp: Long,\n\t) {\n\n\t\tinit {\n\t\t\trequire(total > 0) { \"total = $total\" }\n\t\t\trequire(value >= 0) { \"value = $value\" }\n\t\t\trequire(value <= total) { \"total = $total, value = $value\" }\n\t\t}\n\n\t\t@JvmField\n\t\tval percent = value.toDouble() / total.toDouble()\n\t}\n\n\tprivate companion object {\n\n\t\tconst val MAX_TICKS = 20\n\t\tconst val MIN_ESTIMATE_TICKS = 4\n\t\tconst val NO_TIME = -1L\n\t\tconst val STUCK_DELAY = 10_000L\n\t\tval MAX_TIME = TimeUnit.DAYS.toMillis(1)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/core/zip/ZipOutput.kt",
    "content": "package org.koitharu.kotatsu.core.zip\n\nimport androidx.annotation.WorkerThread\nimport androidx.collection.ArraySet\nimport okio.Closeable\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.withChildren\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.util.zip.Deflater\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\nimport java.util.zip.ZipOutputStream\n\nclass ZipOutput(\n\tval file: File,\n\tprivate val compressionLevel: Int = Deflater.DEFAULT_COMPRESSION,\n) : Closeable {\n\n\tprivate val entryNames = ArraySet<String>()\n\tprivate var cachedOutput: ZipOutputStream? = null\n\tprivate var append: Boolean = false\n\n\t@Blocking\n\tfun put(name: String, file: File): Boolean = withOutput { output ->\n\t\toutput.appendFile(file, name)\n\t}\n\n\t@Blocking\n\tfun put(name: String, content: String): Boolean = withOutput { output ->\n\t\toutput.appendText(content, name)\n\t}\n\n\t@Blocking\n\tfun addDirectory(name: String): Boolean {\n\t\tval entry = if (name.endsWith(\"/\")) {\n\t\t\tZipEntry(name)\n\t\t} else {\n\t\t\tZipEntry(\"$name/\")\n\t\t}\n\t\treturn if (entryNames.add(entry.name)) {\n\t\t\twithOutput { output ->\n\t\t\t\toutput.putNextEntry(entry)\n\t\t\t\toutput.closeEntry()\n\t\t\t}\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\t@Blocking\n\tfun copyEntryFrom(other: ZipFile, entry: ZipEntry): Boolean {\n\t\treturn if (entryNames.add(entry.name)) {\n\t\t\tval zipEntry = ZipEntry(entry.name)\n\t\t\twithOutput { output ->\n\t\t\t\toutput.putNextEntry(zipEntry)\n\t\t\t\ttry {\n\t\t\t\t\tother.getInputStream(entry).use { input ->\n\t\t\t\t\t\tinput.copyTo(output)\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\toutput.closeEntry()\n\t\t\t\t}\n\t\t\t}\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\t@Blocking\n\tfun finish() = withOutput { output ->\n\t\toutput.finish()\n\t}\n\n\t@Synchronized\n\toverride fun close() {\n\t\tcachedOutput?.closeSafe()\n\t\tcachedOutput = null\n\t}\n\n\t@WorkerThread\n\tprivate fun ZipOutputStream.appendFile(fileToZip: File, name: String): Boolean {\n\t\tif (fileToZip.isDirectory) {\n\t\t\tval entry = if (name.endsWith(\"/\")) {\n\t\t\t\tZipEntry(name)\n\t\t\t} else {\n\t\t\t\tZipEntry(\"$name/\")\n\t\t\t}\n\t\t\tif (!entryNames.add(entry.name)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tputNextEntry(entry)\n\t\t\tcloseEntry()\n\t\t\tfileToZip.withChildren { children ->\n\t\t\t\tchildren.forEach { childFile ->\n\t\t\t\t\tappendFile(childFile, \"$name/${childFile.name}\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tFileInputStream(fileToZip).use { fis ->\n\t\t\t\tif (!entryNames.add(name)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tval zipEntry = ZipEntry(name)\n\t\t\t\tputNextEntry(zipEntry)\n\t\t\t\ttry {\n\t\t\t\t\tfis.copyTo(this)\n\t\t\t\t} finally {\n\t\t\t\t\tcloseEntry()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\t@WorkerThread\n\tprivate fun ZipOutputStream.appendText(content: String, name: String): Boolean {\n\t\tif (!entryNames.add(name)) {\n\t\t\treturn false\n\t\t}\n\t\tval zipEntry = ZipEntry(name)\n\t\tputNextEntry(zipEntry)\n\t\ttry {\n\t\t\tcontent.byteInputStream().copyTo(this)\n\t\t} finally {\n\t\t\tcloseEntry()\n\t\t}\n\t\treturn true\n\t}\n\n\t@Synchronized\n\tprivate fun <T> withOutput(block: (ZipOutputStream) -> T): T {\n\t\treturn try {\n\t\t\t(cachedOutput ?: newOutput(append)).withOutputImpl(block).also {\n\t\t\t\tappend = true // after 1st success write\n\t\t\t}\n\t\t} catch (e: NullPointerException) { // probably NullPointerException: Deflater has been closed\n\t\t\te.printStackTraceDebug()\n\t\t\tnewOutput(append).withOutputImpl(block)\n\t\t}\n\t}\n\n\tprivate fun <T> ZipOutputStream.withOutputImpl(block: (ZipOutputStream) -> T): T {\n\t\tval res = block(this)\n\t\tflush()\n\t\treturn res\n\t}\n\n\tprivate fun newOutput(append: Boolean) = ZipOutputStream(FileOutputStream(file, append)).also {\n\t\tit.setLevel(compressionLevel)\n\t\tcachedOutput?.closeSafe()\n\t\tcachedOutput = it\n\t}\n\n\tprivate fun Closeable.closeSafe() {\n\t\ttry {\n\t\t\tclose()\n\t\t} catch (e: NullPointerException) {\n\t\t\t// Don't throw the \"Deflater has been closed\" exception\n\t\t\te.printStackTraceDebug()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt",
    "content": "package org.koitharu.kotatsu.details.data\n\nimport org.koitharu.kotatsu.core.model.getLocale\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.model.withOverride\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.reader.data.filterChapters\nimport java.util.Locale\n\ndata class MangaDetails(\n    private val manga: Manga,\n    private val localManga: LocalManga?,\n    private val override: MangaOverride?,\n    val description: CharSequence?,\n    val isLoaded: Boolean,\n) {\n\n    constructor(manga: Manga) : this(\n        manga = manga,\n        localManga = null,\n        override = null,\n        description = null,\n        isLoaded = false,\n    )\n\n    val id: Long\n        get() = manga.id\n\n    val allChapters: List<MangaChapter> by lazy { mergeChapters() }\n\n    val chapters: Map<String?, List<MangaChapter>> by lazy {\n        allChapters.groupBy { it.branch }\n    }\n\n    val isLocal\n        get() = manga.isLocal\n\n    val local: LocalManga?\n        get() = localManga ?: if (manga.isLocal) LocalManga(manga) else null\n\n    val coverUrl: String?\n        get() = override?.coverUrl\n            .ifNullOrEmpty { manga.largeCoverUrl }\n            .ifNullOrEmpty { manga.coverUrl }\n            .ifNullOrEmpty { localManga?.manga?.coverUrl }\n            ?.nullIfEmpty()\n\n    val isRestricted: Boolean\n        get() = manga.state == MangaState.RESTRICTED\n\n    private val mergedManga by lazy {\n        if (localManga == null) {\n            // fast path\n            manga.withOverride(override)\n        } else {\n            manga.copy(\n                title = override?.title.ifNullOrEmpty { manga.title },\n                coverUrl = override?.coverUrl.ifNullOrEmpty { manga.coverUrl },\n                largeCoverUrl = override?.coverUrl.ifNullOrEmpty { manga.largeCoverUrl },\n                contentRating = override?.contentRating ?: manga.contentRating,\n                chapters = allChapters,\n            )\n        }\n    }\n\n    fun toManga() = mergedManga\n\n    fun getLocale(): Locale? {\n        findAppropriateLocale(chapters.keys.singleOrNull())?.let {\n            return it\n        }\n        return manga.source.getLocale()\n    }\n\n    fun filterChapters(branch: String?) = copy(\n        manga = manga.filterChapters(branch),\n        localManga = localManga?.run {\n            copy(manga = manga.filterChapters(branch))\n        },\n    )\n\n    private fun mergeChapters(): List<MangaChapter> {\n        val chapters = manga.chapters\n        val localChapters = local?.manga?.chapters.orEmpty()\n        if (chapters.isNullOrEmpty()) {\n            return localChapters\n        }\n        val localMap = if (localChapters.isNotEmpty()) {\n            localChapters.associateByTo(LinkedHashMap(localChapters.size)) { it.id }\n        } else {\n            null\n        }\n        val result = ArrayList<MangaChapter>(chapters.size)\n        for (chapter in chapters) {\n            val local = localMap?.remove(chapter.id)\n            result += local ?: chapter\n        }\n        if (!localMap.isNullOrEmpty()) {\n            result.addAll(localMap.values)\n        }\n        return result\n    }\n\n    private fun findAppropriateLocale(name: String?): Locale? {\n        if (name.isNullOrEmpty()) {\n            return null\n        }\n        return Locale.getAvailableLocales().find { lc ->\n            name.contains(lc.getDisplayName(lc), ignoreCase = true) ||\n                name.contains(lc.getDisplayName(Locale.ENGLISH), ignoreCase = true) ||\n                name.contains(lc.getDisplayLanguage(lc), ignoreCase = true) ||\n                name.contains(lc.getDisplayLanguage(Locale.ENGLISH), ignoreCase = true)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/data/ReadingTime.kt",
    "content": "package org.koitharu.kotatsu.details.data\n\nimport android.content.res.Resources\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\n\ndata class ReadingTime(\n\tval minutes: Int,\n\tval hours: Int,\n\tval isContinue: Boolean,\n) {\n\n\tfun format(resources: Resources): String = when {\n\t\thours == 0 && minutes == 0 -> resources.getString(R.string.less_than_minute)\n\t\thours == 0 -> resources.getQuantityStringSafe(R.plurals.minutes, minutes, minutes)\n\t\tminutes == 0 -> resources.getQuantityStringSafe(R.plurals.hours, hours, hours)\n\t\telse -> resources.getString(\n\t\t\tR.string.remaining_time_pattern,\n\t\t\tresources.getQuantityStringSafe(R.plurals.hours, hours, hours),\n\t\t\tresources.getQuantityStringSafe(R.plurals.minutes, minutes, minutes),\n\t\t)\n\t}\n\n\tfun formatShort(resources: Resources): String? = when {\n\t\thours == 0 && minutes == 0 -> null\n\t\thours == 0 -> resources.getString(R.string.minutes_short, minutes)\n\t\tminutes == 0 -> resources.getString(R.string.hours_short, hours)\n\t\telse -> resources.getString(R.string.hours_minutes_short, hours, minutes)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/domain/BranchComparator.kt",
    "content": "package org.koitharu.kotatsu.details.domain\n\nimport org.koitharu.kotatsu.core.util.LocaleStringComparator\nimport org.koitharu.kotatsu.details.ui.model.MangaBranch\n\nclass BranchComparator : Comparator<MangaBranch> {\n\n\tprivate val delegate = LocaleStringComparator()\n\n\toverride fun compare(o1: MangaBranch, o2: MangaBranch): Int = delegate.compare(o1.name, o2.name)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsInteractor.kt",
    "content": "package org.koitharu.kotatsu.details.domain\n\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChangedBy\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\nimport org.koitharu.kotatsu.tracker.domain.TrackingRepository\nimport javax.inject.Inject\n\n/* TODO: remove */\nclass DetailsInteractor @Inject constructor(\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val favouritesRepository: FavouritesRepository,\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val trackingRepository: TrackingRepository,\n\tprivate val settings: AppSettings,\n\tprivate val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,\n) {\n\n\tfun observeFavourite(mangaId: Long): Flow<Set<FavouriteCategory>> {\n\t\treturn favouritesRepository.observeCategories(mangaId)\n\t}\n\n\tfun observeNewChapters(mangaId: Long): Flow<Int> {\n\t\treturn settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled }\n\t\t\t.flatMapLatest { isEnabled ->\n\t\t\t\tif (isEnabled) {\n\t\t\t\t\ttrackingRepository.observeNewChaptersCount(mangaId)\n\t\t\t\t} else {\n\t\t\t\t\tflowOf(0)\n\t\t\t\t}\n\t\t\t}\n\t}\n\n\tfun observeScrobblingInfo(mangaId: Long): Flow<List<ScrobblingInfo>> {\n\t\treturn combine(\n\t\t\tscrobblers.map { it.observeScrobblingInfo(mangaId) },\n\t\t) { scrobblingInfo ->\n\t\t\tscrobblingInfo.filterNotNull()\n\t\t}\n\t}\n\n\tfun observeIncognitoMode(mangaFlow: Flow<Manga?>): Flow<TriStateOption> {\n\t\treturn mangaFlow\n\t\t\t.filterNotNull()\n\t\t\t.distinctUntilChangedBy { it.isNsfw() }\n\t\t\t.combine(observeIncognitoMode()) { manga, globalIncognito ->\n\t\t\t\twhen {\n\t\t\t\t\tglobalIncognito -> TriStateOption.ENABLED\n\t\t\t\t\tmanga.isNsfw() -> settings.incognitoModeForNsfw\n\t\t\t\t\telse -> TriStateOption.DISABLED\n\t\t\t\t}\n\t\t\t}\n\t}\n\n\tsuspend fun updateLocal(subject: MangaDetails?, localManga: LocalManga): MangaDetails? {\n\t\tsubject ?: return null\n\t\treturn if (subject.id == localManga.manga.id) {\n\t\t\tif (subject.isLocal) {\n\t\t\t\tsubject.copy(\n\t\t\t\t\tmanga = localManga.manga,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tsubject.copy(\n\t\t\t\t\tlocalManga = runCatchingCancellable {\n\t\t\t\t\t\tlocalManga.copy(\n\t\t\t\t\t\t\tmanga = localMangaRepository.getDetails(localManga.manga),\n\t\t\t\t\t\t)\n\t\t\t\t\t}.getOrNull() ?: subject.local,\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tsubject\n\t\t}\n\t}\n\n\tsuspend fun findRemote(seed: Manga) = localMangaRepository.getRemoteManga(seed)\n\n\tprivate fun observeIncognitoMode() = settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) {\n\t\tisIncognitoModeEnabled\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt",
    "content": "package org.koitharu.kotatsu.details.domain\n\nimport android.text.Html\nimport android.text.SpannableString\nimport android.text.Spanned\nimport android.text.style.ForegroundColorSpan\nimport androidx.core.text.getSpans\nimport androidx.core.text.parseAsHtml\nimport coil3.request.CachePolicy\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.runInterruptible\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.nav.MangaIntent\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.parser.CachingMangaRepository\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.core.util.ext.sanitize\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.explore.domain.RecoverMangaUseCase\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.exception.NotFoundException\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.recoverNotNull\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\nclass DetailsLoadUseCase @Inject constructor(\n\tprivate val mangaDataRepository: MangaDataRepository,\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val recoverUseCase: RecoverMangaUseCase,\n\tprivate val imageGetter: Html.ImageGetter,\n\tprivate val networkState: NetworkState,\n) {\n\n\toperator fun invoke(intent: MangaIntent, force: Boolean): Flow<MangaDetails> = flow {\n\t\tval manga = requireNotNull(mangaDataRepository.resolveIntent(intent, withChapters = true)) {\n\t\t\t\"Cannot resolve intent $intent\"\n\t\t}\n\t\tval override = mangaDataRepository.getOverride(manga.id)\n\t\temit(\n\t\t\tMangaDetails(\n\t\t\t\tmanga = manga,\n\t\t\t\tlocalManga = null,\n\t\t\t\toverride = override,\n\t\t\t\tdescription = manga.description?.parseAsHtml(withImages = false),\n\t\t\t\tisLoaded = false,\n\t\t\t),\n\t\t)\n\t\tif (manga.isLocal) {\n\t\t\tloadLocal(manga, override, force)\n\t\t} else {\n\t\t\tloadRemote(manga, override, force)\n\t\t}\n\t}.distinctUntilChanged()\n\t\t.flowOn(Dispatchers.Default)\n\n\t/**\n\t * Load local manga + try to load the linked remote one if network is not restricted\n\t * Suppress any network errors\n\t */\n\tprivate suspend fun FlowCollector<MangaDetails>.loadLocal(manga: Manga, override: MangaOverride?, force: Boolean) {\n\t\tval skipNetworkLoad = !force && networkState.isOfflineOrRestricted()\n\t\tval localDetails = localMangaRepository.getDetails(manga)\n\t\temit(\n\t\t\tMangaDetails(\n\t\t\t\tmanga = localDetails,\n\t\t\t\tlocalManga = null,\n\t\t\t\toverride = override,\n\t\t\t\tdescription = localDetails.description?.parseAsHtml(withImages = false),\n\t\t\t\tisLoaded = skipNetworkLoad,\n\t\t\t),\n\t\t)\n\t\tif (skipNetworkLoad) {\n\t\t\treturn\n\t\t}\n\t\tval remoteManga = localMangaRepository.getRemoteManga(manga)\n\t\tif (remoteManga == null) {\n\t\t\temit(\n\t\t\t\tMangaDetails(\n\t\t\t\t\tmanga = localDetails,\n\t\t\t\t\tlocalManga = null,\n\t\t\t\t\toverride = override,\n\t\t\t\t\tdescription = localDetails.description?.parseAsHtml(withImages = true),\n\t\t\t\t\tisLoaded = true,\n\t\t\t\t),\n\t\t\t)\n\t\t} else {\n\t\t\tval remoteDetails = getDetails(remoteManga, force).getOrNull()\n\t\t\temit(\n\t\t\t\tMangaDetails(\n\t\t\t\t\tmanga = remoteDetails ?: remoteManga,\n\t\t\t\t\tlocalManga = LocalManga(localDetails),\n\t\t\t\t\toverride = override,\n\t\t\t\t\tdescription = (remoteDetails ?: localDetails).description?.parseAsHtml(withImages = true),\n\t\t\t\t\tisLoaded = true,\n\t\t\t\t),\n\t\t\t)\n\t\t\tif (remoteDetails != null) {\n\t\t\t\tmangaDataRepository.updateChapters(remoteDetails)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Load remote manga + saved one if available\n\t * Throw network errors after loading local manga only\n\t */\n\tprivate suspend fun FlowCollector<MangaDetails>.loadRemote(\n\t\tmanga: Manga,\n\t\toverride: MangaOverride?,\n\t\tforce: Boolean\n\t) = coroutineScope {\n\t\tval remoteDeferred = async {\n\t\t\tgetDetails(manga, force)\n\t\t}\n\t\tval localManga = localMangaRepository.findSavedManga(manga, withDetails = true)\n\t\tif (localManga != null) {\n\t\t\temit(\n\t\t\t\tMangaDetails(\n\t\t\t\t\tmanga = manga,\n\t\t\t\t\tlocalManga = localManga,\n\t\t\t\t\toverride = override,\n\t\t\t\t\tdescription = localManga.manga.description?.parseAsHtml(withImages = true),\n\t\t\t\t\tisLoaded = false,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tval remoteDetails = remoteDeferred.await().getOrThrow()\n\t\temit(\n\t\t\tMangaDetails(\n\t\t\t\tmanga = remoteDetails,\n\t\t\t\tlocalManga = localManga,\n\t\t\t\toverride = override,\n\t\t\t\tdescription = (remoteDetails.description\n\t\t\t\t\t?: localManga?.manga?.description)?.parseAsHtml(withImages = true),\n\t\t\t\tisLoaded = true,\n\t\t\t),\n\t\t)\n\t\tmangaDataRepository.updateChapters(remoteDetails)\n\t}\n\n\tprivate suspend fun getDetails(seed: Manga, force: Boolean) = runCatchingCancellable {\n\t\tval repository = mangaRepositoryFactory.create(seed.source)\n\t\tif (repository is CachingMangaRepository) {\n\t\t\trepository.getDetails(seed, if (force) CachePolicy.WRITE_ONLY else CachePolicy.ENABLED)\n\t\t} else {\n\t\t\trepository.getDetails(seed)\n\t\t}\n\t}.recoverNotNull { e ->\n\t\tif (e is NotFoundException) {\n\t\t\trecoverUseCase(seed)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate suspend fun String.parseAsHtml(withImages: Boolean): CharSequence? = if (withImages) {\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\tparseAsHtml(imageGetter = imageGetter)\n\t\t}.filterSpans()\n\t} else {\n\t\trunInterruptible(Dispatchers.Default) {\n\t\t\tparseAsHtml()\n\t\t}.filterSpans().sanitize()\n\t}.trim().nullIfEmpty()\n\n\tprivate fun Spanned.filterSpans(): Spanned {\n\t\tval spannable = SpannableString.valueOf(this)\n\t\tval spans = spannable.getSpans<ForegroundColorSpan>()\n\t\tfor (span in spans) {\n\t\t\tspannable.removeSpan(span)\n\t\t}\n\t\treturn spannable\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/domain/ProgressUpdateUseCase.kt",
    "content": "package org.koitharu.kotatsu.details.domain\n\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\nclass ProgressUpdateUseCase @Inject constructor(\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val database: MangaDatabase,\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val networkState: NetworkState,\n) {\n\n\tsuspend operator fun invoke(manga: Manga): Float {\n\t\tval history = database.getHistoryDao().find(manga.id) ?: return PROGRESS_NONE\n\t\tval seed = if (manga.isLocal) {\n\t\t\tlocalMangaRepository.getRemoteManga(manga) ?: manga\n\t\t} else {\n\t\t\tmanga\n\t\t}\n\t\tif (!seed.isLocal && !networkState.value) {\n\t\t\treturn PROGRESS_NONE\n\t\t}\n\t\tval repo = mangaRepositoryFactory.create(seed.source)\n\t\tval details = if (manga.source != seed.source || seed.chapters.isNullOrEmpty()) {\n\t\t\trepo.getDetails(seed)\n\t\t} else {\n\t\t\tseed\n\t\t}\n\t\tval chapter = details.findChapterById(history.chapterId) ?: return PROGRESS_NONE\n\t\tval chapters = details.getChapters(chapter.branch)\n\t\tval chapterRepo = if (repo.source == chapter.source) {\n\t\t\trepo\n\t\t} else {\n\t\t\tmangaRepositoryFactory.create(chapter.source)\n\t\t}\n\t\tval chaptersCount = chapters.size\n\t\tif (chaptersCount == 0) {\n\t\t\treturn PROGRESS_NONE\n\t\t}\n\t\tval chapterIndex = chapters.indexOfFirst { x -> x.id == history.chapterId }\n\t\tval pagesCount = chapterRepo.getPages(chapter).size\n\t\tif (pagesCount == 0) {\n\t\t\treturn PROGRESS_NONE\n\t\t}\n\t\tval pagePercent = (history.page + 1) / pagesCount.toFloat()\n\t\tval ppc = 1f / chaptersCount\n\t\tval result = ppc * chapterIndex + ppc * pagePercent\n\t\tif (result != history.percent) {\n\t\t\tdatabase.getHistoryDao().update(\n\t\t\t\thistory.copy(\n\t\t\t\t\tchapterId = chapter.id,\n\t\t\t\t\tpercent = result,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/domain/ReadingTimeUseCase.kt",
    "content": "package org.koitharu.kotatsu.details.domain\n\nimport org.koitharu.kotatsu.core.model.MangaHistory\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.data.ReadingTime\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.stats.data.StatsRepository\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport kotlin.math.roundToInt\n\nclass ReadingTimeUseCase @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val statsRepository: StatsRepository,\n) {\n\n\tsuspend operator fun invoke(manga: MangaDetails?, branch: String?, history: MangaHistory?): ReadingTime? {\n\t\tif (!settings.isReadingTimeEstimationEnabled) {\n\t\t\treturn null\n\t\t}\n\t\tval chapters = manga?.chapters?.get(branch)\n\t\tif (chapters.isNullOrEmpty()) {\n\t\t\treturn null\n\t\t}\n\t\tval isOnHistoryBranch = history != null && chapters.findById(history.chapterId) != null\n\t\t// Impossible task, I guess. Good luck on this.\n\t\tvar averageTimeSec: Int = 20 /* pages */ * getSecondsPerPage(manga.id) * chapters.size\n\t\tif (isOnHistoryBranch) {\n\t\t\taverageTimeSec = (averageTimeSec * (1f - history.percent)).roundToInt()\n\t\t}\n\t\tif (averageTimeSec < 60) {\n\t\t\treturn null\n\t\t}\n\t\treturn ReadingTime(\n\t\t\tminutes = (averageTimeSec / 60) % 60,\n\t\t\thours = averageTimeSec / 3600,\n\t\t\tisContinue = isOnHistoryBranch,\n\t\t)\n\t}\n\n\tprivate suspend fun getSecondsPerPage(mangaId: Long): Int {\n\t\tvar time = if (settings.isStatsEnabled) {\n\t\t\tTimeUnit.MILLISECONDS.toSeconds(statsRepository.getTimePerPage(mangaId)).toInt()\n\t\t} else {\n\t\t\t0\n\t\t}\n\t\tif (time == 0) {\n\t\t\ttime = 10 // default\n\t\t}\n\t\treturn time\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/domain/RelatedMangaUseCase.kt",
    "content": "package org.koitharu.kotatsu.details.domain\n\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\nclass RelatedMangaUseCase @Inject constructor(\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\n\tsuspend operator fun invoke(seed: Manga) = runCatchingCancellable {\n\t\tmangaRepositoryFactory.create(seed.source).getRelated(seed)\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/service/MangaPrefetchService.kt",
    "content": "package org.koitharu.kotatsu.details.service\n\nimport android.content.Context\nimport android.content.Intent\nimport dagger.hilt.android.AndroidEntryPoint\nimport dagger.hilt.android.EntryPointAccessors\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableChapter\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.isPowerSaveMode\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass MangaPrefetchService : CoroutineIntentService() {\n\n\t@Inject\n\tlateinit var mangaRepositoryFactory: MangaRepository.Factory\n\n\t@Inject\n\tlateinit var cache: MemoryContentCache\n\n\t@Inject\n\tlateinit var historyRepository: HistoryRepository\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\twhen (intent.action) {\n\t\t\tACTION_PREFETCH_DETAILS -> prefetchDetails(\n\t\t\t\tmanga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga\n\t\t\t\t\t?: return,\n\t\t\t)\n\n\t\t\tACTION_PREFETCH_PAGES -> prefetchPages(\n\t\t\t\tchapter = intent.getParcelableExtraCompat<ParcelableChapter>(EXTRA_CHAPTER)?.chapter\n\t\t\t\t\t?: return,\n\t\t\t)\n\n\t\t\tACTION_PREFETCH_LAST -> prefetchLast()\n\t\t}\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) = Unit\n\n\tprivate suspend fun prefetchDetails(manga: Manga) {\n\t\tval source = mangaRepositoryFactory.create(manga.source)\n\t\trunCatchingCancellable { source.getDetails(manga) }\n\t}\n\n\tprivate suspend fun prefetchPages(chapter: MangaChapter) {\n\t\tval source = mangaRepositoryFactory.create(chapter.source)\n\t\trunCatchingCancellable { source.getPages(chapter) }\n\t}\n\n\tprivate suspend fun prefetchLast() {\n\t\tval last = historyRepository.getLastOrNull() ?: return\n\t\tif (last.isLocal) return\n\t\tval repo = mangaRepositoryFactory.create(last.source)\n\t\tval details = runCatchingCancellable { repo.getDetails(last) }.getOrNull() ?: return\n\t\tval chapters = details.chapters\n\t\tif (chapters.isNullOrEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tval history = historyRepository.getOne(last)\n\t\tval chapter = if (history == null) {\n\t\t\tchapters.firstOrNull()\n\t\t} else {\n\t\t\tchapters.findById(history.chapterId) ?: chapters.firstOrNull()\n\t\t} ?: return\n\t\trunCatchingCancellable { repo.getPages(chapter) }\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val EXTRA_MANGA = \"manga\"\n\t\tprivate const val EXTRA_CHAPTER = \"manga\"\n\t\tprivate const val ACTION_PREFETCH_DETAILS = \"details\"\n\t\tprivate const val ACTION_PREFETCH_PAGES = \"pages\"\n\t\tprivate const val ACTION_PREFETCH_LAST = \"last\"\n\n\t\tfun prefetchDetails(context: Context, manga: Manga) {\n\t\t\tif (!isPrefetchAvailable(context, manga.source)) return\n\t\t\tval intent = Intent(context, MangaPrefetchService::class.java)\n\t\t\tintent.action = ACTION_PREFETCH_DETAILS\n\t\t\tintent.putExtra(EXTRA_MANGA, ParcelableManga(manga))\n\t\t\ttryStart(context, intent)\n\t\t}\n\n\t\tfun prefetchPages(context: Context, chapter: MangaChapter) {\n\t\t\tif (!isPrefetchAvailable(context, chapter.source)) return\n\t\t\tval intent = Intent(context, MangaPrefetchService::class.java)\n\t\t\tintent.action = ACTION_PREFETCH_PAGES\n\t\t\tintent.putExtra(EXTRA_CHAPTER, ParcelableChapter(chapter))\n\t\t\ttryStart(context, intent)\n\t\t}\n\n\t\tfun prefetchLast(context: Context) {\n\t\t\tif (!isPrefetchAvailable(context, null)) return\n\t\t\tval intent = Intent(context, MangaPrefetchService::class.java)\n\t\t\tintent.action = ACTION_PREFETCH_LAST\n\t\t\ttryStart(context, intent)\n\t\t}\n\n\t\tprivate fun isPrefetchAvailable(context: Context, source: MangaSource?): Boolean {\n\t\t\tif (source == LocalMangaSource || context.isPowerSaveMode()) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tval entryPoint = EntryPointAccessors.fromApplication(\n\t\t\t\tcontext,\n\t\t\t\tPrefetchCompanionEntryPoint::class.java,\n\t\t\t)\n\t\t\treturn entryPoint.settings.isContentPrefetchEnabled\n\t\t}\n\n\t\tprivate fun tryStart(context: Context, intent: Intent) {\n\t\t\ttry {\n\t\t\t\tcontext.startService(intent)\n\t\t\t} catch (e: IllegalStateException) {\n\t\t\t\t// probably app is in background\n\t\t\t\te.printStackTraceDebug()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/service/PrefetchCompanionEntryPoint.kt",
    "content": "package org.koitharu.kotatsu.details.service\n\nimport dagger.hilt.EntryPoint\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport org.koitharu.kotatsu.core.prefs.AppSettings\n\n@EntryPoint\n@InstallIn(SingletonComponent::class)\ninterface PrefetchCompanionEntryPoint {\n\tval settings: AppSettings\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/AuthorSpan.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport android.text.Spannable\nimport android.text.TextPaint\nimport android.text.style.ClickableSpan\nimport android.view.View\nimport android.widget.TextView\n\nclass AuthorSpan(private val listener: OnAuthorClickListener) : ClickableSpan() {\n\n\toverride fun onClick(widget: View) {\n\t\tval text = (widget as? TextView)?.text as? Spannable ?: return\n\t\tval start = text.getSpanStart(this)\n\t\tval end = text.getSpanEnd(this)\n\t\tval selected = text.substring(start, end).trim()\n\t\tif (selected.isNotEmpty()) {\n\t\t\tlistener.onAuthorClick(selected)\n\t\t}\n\t}\n\n\toverride fun updateDrawState(ds: TextPaint) {\n\t\tds.setColor(ds.linkColor)\n\t}\n\n\tfun interface OnAuthorClickListener {\n\n\t\tfun onAuthorClick(author: String)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport android.content.Context\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.details.ui.model.toListItem\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.util.mapToSet\n\nfun MangaDetails.mapChapters(\n\tcurrentChapterId: Long,\n\tnewCount: Int,\n\tbranch: String?,\n\tbookmarks: List<Bookmark>,\n\tisGrid: Boolean,\n\tisDownloadedOnly: Boolean,\n): List<ChapterListItem> {\n\tval remoteChapters = chapters[branch].orEmpty()\n\tval localChapters = local?.manga?.getChapters(branch).orEmpty()\n\tif (remoteChapters.isEmpty() && localChapters.isEmpty()) {\n\t\treturn emptyList()\n\t}\n\tval bookmarked = bookmarks.mapToSet { it.chapterId }\n\tval newFrom = if (newCount == 0 || remoteChapters.isEmpty()) Int.MAX_VALUE else remoteChapters.size - newCount\n\tval ids = buildSet(maxOf(remoteChapters.size, localChapters.size)) {\n\t\tremoteChapters.mapTo(this) { it.id }\n\t\tlocalChapters.mapTo(this) { it.id }\n\t}\n\tval result = ArrayList<ChapterListItem>(ids.size)\n\tval localMap = if (localChapters.isNotEmpty()) {\n\t\tlocalChapters.associateByTo(LinkedHashMap(localChapters.size)) { it.id }\n\t} else {\n\t\tnull\n\t}\n\tvar isUnread = currentChapterId !in ids\n\tif (!isDownloadedOnly || local?.manga?.chapters == null) {\n\t\tfor (chapter in remoteChapters) {\n\t\t\tval local = localMap?.remove(chapter.id)\n\t\t\tif (chapter.id == currentChapterId) {\n\t\t\t\tisUnread = true\n\t\t\t}\n\t\t\tresult += (local ?: chapter).toListItem(\n\t\t\t\tisCurrent = chapter.id == currentChapterId,\n\t\t\t\tisUnread = isUnread,\n\t\t\t\tisNew = isUnread && result.size >= newFrom,\n\t\t\t\tisDownloaded = local != null,\n\t\t\t\tisBookmarked = chapter.id in bookmarked,\n\t\t\t\tisGrid = isGrid,\n\t\t\t)\n\t\t}\n\t}\n\tif (!localMap.isNullOrEmpty()) {\n\t\tfor (chapter in localMap.values) {\n\t\t\tif (chapter.id == currentChapterId) {\n\t\t\t\tisUnread = true\n\t\t\t}\n\t\t\tresult += chapter.toListItem(\n\t\t\t\tisCurrent = chapter.id == currentChapterId,\n\t\t\t\tisUnread = isUnread,\n\t\t\t\tisNew = false,\n\t\t\t\tisDownloaded = !isLocal,\n\t\t\t\tisBookmarked = chapter.id in bookmarked,\n\t\t\t\tisGrid = isGrid,\n\t\t\t)\n\t\t}\n\t}\n\treturn result\n}\n\nfun List<ChapterListItem>.withVolumeHeaders(context: Context): MutableList<ListModel> {\n\tvar prevVolume = 0\n\tval result = ArrayList<ListModel>((size * 1.4).toInt())\n\tfor (item in this) {\n\t\tval chapter = item.chapter\n\t\tif (chapter.volume != prevVolume) {\n\t\t\tval text = if (chapter.volume == 0) {\n\t\t\t\tcontext.getString(R.string.volume_unknown)\n\t\t\t} else {\n\t\t\t\tcontext.getString(R.string.volume_, chapter.volume)\n\t\t\t}\n\t\t\tresult.add(ListHeader(text))\n\t\t\tprevVolume = chapter.volume\n\t\t}\n\t\tresult.add(item)\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport android.app.assist.AssistContent\nimport android.content.Context\nimport android.os.Bundle\nimport android.text.SpannedString\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewTreeObserver\nimport android.widget.Toast\nimport androidx.activity.viewModels\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.inSpans\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport androidx.core.view.updatePaddingRelative\nimport androidx.swiperefreshlayout.widget.SwipeRefreshLayout\nimport androidx.transition.TransitionManager\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport coil3.request.allowRgb565\nimport coil3.request.crossfade\nimport coil3.request.lifecycle\nimport coil3.request.transformations\nimport coil3.size.Precision\nimport coil3.transform.RoundedCornersTransformation\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\nimport com.google.android.material.chip.Chip\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.filterNot\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.core.image.CoilMemoryCacheKey\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.core.model.getSummary\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.titleResId\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.parser.favicon.faviconUri\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.image.FaviconDrawable\nimport org.koitharu.kotatsu.core.ui.image.TextDrawable\nimport org.koitharu.kotatsu.core.ui.image.TextViewTarget\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.sheet.BottomSheetCollapseCallback\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.LocaleUtils\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.copyToClipboard\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.enqueueWith\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.isTextTruncated\nimport org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.parentView\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.databinding.ActivityDetailsBinding\nimport org.koitharu.kotatsu.databinding.LayoutDetailsTableBinding\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.data.ReadingTime\nimport org.koitharu.kotatsu.details.service.MangaPrefetchService\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.details.ui.model.HistoryInfo\nimport org.koitharu.kotatsu.details.ui.scrobbling.ScrobblingItemDecoration\nimport org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter\nimport org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver\nimport org.koitharu.kotatsu.main.ui.owners.BottomSheetOwner\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.toTitleCase\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\nimport javax.inject.Inject\nimport kotlin.math.roundToInt\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nclass DetailsActivity :\n\tBaseActivity<ActivityDetailsBinding>(),\n\tView.OnClickListener,\n\tView.OnLayoutChangeListener,\n\tViewTreeObserver.OnDrawListener,\n\tChipsView.OnChipClickListener,\n\tOnListItemClickListener<Bookmark>,\n\tSwipeRefreshLayout.OnRefreshListener,\n\tAuthorSpan.OnAuthorClickListener,\n\tBottomSheetOwner {\n\n\t@Inject\n\tlateinit var shortcutManager: AppShortcutManager\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate val viewModel: DetailsViewModel by viewModels()\n\tprivate lateinit var menuProvider: DetailsMenuProvider\n\tprivate lateinit var infoBinding: LayoutDetailsTableBinding\n\n\toverride val bottomSheet: View?\n\t\tget() = viewBinding.containerBottomSheet\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityDetailsBinding.inflate(layoutInflater))\n\t\tinfoBinding = LayoutDetailsTableBinding.bind(viewBinding.root)\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tsupportActionBar?.setDisplayShowTitleEnabled(false)\n\t\tviewBinding.chipFavorite.setOnClickListener(this)\n\t\tinfoBinding.textViewLocal.setOnClickListener(this)\n\t\tinfoBinding.textViewSource.setOnClickListener(this)\n\t\tviewBinding.imageViewCover.setOnClickListener(this)\n\t\tviewBinding.textViewTitle.setOnClickListener(this)\n\t\tviewBinding.buttonDescriptionMore.setOnClickListener(this)\n\t\tviewBinding.buttonScrobblingMore.setOnClickListener(this)\n\t\tviewBinding.buttonRelatedMore.setOnClickListener(this)\n\t\tviewBinding.textViewDescription.addOnLayoutChangeListener(this)\n\t\tviewBinding.swipeRefreshLayout.setOnRefreshListener(this)\n\t\tviewBinding.textViewDescription.viewTreeObserver.addOnDrawListener(this)\n\t\tinfoBinding.textViewAuthor.movementMethod = LinkMovementMethodCompat.getInstance()\n\t\tviewBinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()\n\t\tviewBinding.chipsTags.onChipClickListener = this\n\t\tTitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)\n\t\tif (settings.isDescriptionExpanded) {\n\t\t\tviewBinding.textViewDescription.maxLines = Int.MAX_VALUE - 1\n\t\t}\n\t\tviewBinding.containerBottomSheet?.let { sheet ->\n\t\t\tsheet.setOnClickListener(this)\n\t\t\tsheet.addOnLayoutChangeListener(this)\n\t\t\tonBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet))\n\t\t\tBottomSheetBehavior.from(sheet).addBottomSheetCallback(\n\t\t\t\tDetailsBottomSheetCallback(viewBinding.swipeRefreshLayout, checkNotNull(viewBinding.navbarDim)),\n\t\t\t)\n\t\t}\n\n\t\tval appRouter = router\n\t\tviewModel.mangaDetails.filterNotNull().observe(this, ::onMangaUpdated)\n\t\tviewModel.coverUrl.observe(this, ::loadCover)\n\t\tviewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved)\n\t\tviewModel.onError\n\t\t\t.filterNot { appRouter.isChapterPagesSheetShown() }\n\t\t\t.observeEvent(this, DetailsErrorObserver(this, viewModel, exceptionResolver))\n\t\tviewModel.onActionDone\n\t\t\t.filterNot { appRouter.isChapterPagesSheetShown() }\n\t\t\t.observeEvent(this, ReversibleActionObserver(viewBinding.scrollView))\n\t\tcombine(viewModel.historyInfo, viewModel.isLoading, ::Pair).observe(this) {\n\t\t\tonHistoryChanged(it.first, it.second)\n\t\t}\n\t\tviewModel.isLoading.observe(this, ::onLoadingStateChanged)\n\t\tviewModel.scrobblingInfo.observe(this, ::onScrobblingInfoChanged)\n\t\tviewModel.localSize.observe(this, ::onLocalSizeChanged)\n\t\tviewModel.relatedManga.observe(this, ::onRelatedMangaChanged)\n\t\tviewModel.favouriteCategories.observe(this, ::onFavoritesChanged)\n\t\tval menuInvalidator = MenuInvalidator(this)\n\t\tviewModel.isStatsAvailable.observe(this, menuInvalidator)\n\t\tviewModel.remoteManga.observe(this, menuInvalidator)\n\t\tviewModel.tags.observe(this, ::onTagsChanged)\n\t\tviewModel.chapters.observe(this, PrefetchObserver(this))\n\t\tviewModel.onDownloadStarted\n\t\t\t.filterNot { appRouter.isChapterPagesSheetShown() }\n\t\t\t.observeEvent(this, DownloadStartedObserver(viewBinding.scrollView))\n\t\tmenuProvider = DetailsMenuProvider(\n\t\t\tactivity = this,\n\t\t\tviewModel = viewModel,\n\t\t\tsnackbarHost = viewBinding.scrollView,\n\t\t\tappShortcutManager = shortcutManager,\n\t\t)\n\t\taddMenuProvider(menuProvider)\n\t}\n\n\toverride fun onProvideAssistContent(outContent: AssistContent) {\n\t\tsuper.onProvideAssistContent(outContent)\n\t\tviewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }\n\t}\n\n\toverride fun isNsfwContent(): Flow<Boolean> = viewModel.manga.map { it?.contentRating == ContentRating.ADULT }\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.textView_source -> {\n\t\t\t\tval manga = viewModel.getMangaOrNull() ?: return\n\t\t\t\trouter.openList(manga.source, null, null)\n\t\t\t}\n\n\t\t\tR.id.textView_local -> {\n\t\t\t\tval manga = viewModel.getMangaOrNull() ?: return\n\t\t\t\trouter.showLocalInfoDialog(manga)\n\t\t\t}\n\n\t\t\tR.id.chip_favorite -> {\n\t\t\t\tval manga = viewModel.getMangaOrNull() ?: return\n\t\t\t\trouter.showFavoriteDialog(manga)\n\t\t\t}\n\n\t\t\tR.id.imageView_cover -> {\n\t\t\t\tval manga = viewModel.getMangaOrNull() ?: return\n\t\t\t\trouter.openImage(\n\t\t\t\t\turl = viewModel.coverUrl.value ?: return,\n\t\t\t\t\tsource = manga.source,\n\t\t\t\t\tpreview = CoilMemoryCacheKey.from(viewBinding.imageViewCover),\n\t\t\t\t\tanchor = v,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tR.id.button_description_more -> {\n\t\t\t\tval tv = viewBinding.textViewDescription\n\t\t\t\tif (tv.context.isAnimationsEnabled) {\n\t\t\t\t\ttv.parentView?.let {\n\t\t\t\t\t\tTransitionManager.beginDelayedTransition(it)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (tv.maxLines in 1 until Integer.MAX_VALUE) {\n\t\t\t\t\ttv.maxLines = Integer.MAX_VALUE\n\t\t\t\t} else {\n\t\t\t\t\ttv.maxLines = resources.getInteger(R.integer.details_description_lines)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tR.id.button_scrobbling_more -> {\n\t\t\t\trouter.showScrobblingSelectorSheet(\n\t\t\t\t\tmanga = viewModel.getMangaOrNull() ?: return,\n\t\t\t\t\tscrobblerService = viewModel.scrobblingInfo.value.firstOrNull()?.scrobbler,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tR.id.button_related_more -> {\n\t\t\t\tval manga = viewModel.getMangaOrNull() ?: return\n\t\t\t\trouter.openRelated(manga)\n\t\t\t}\n\n\t\t\tR.id.textView_title -> {\n\t\t\t\tval title = viewModel.getMangaOrNull()?.title?.nullIfEmpty() ?: return\n\t\t\t\tbuildAlertDialog(this) {\n\t\t\t\t\tsetMessage(title)\n\t\t\t\t\tsetNegativeButton(R.string.close, null)\n\t\t\t\t\tsetPositiveButton(androidx.preference.R.string.copy) { _, _ ->\n\t\t\t\t\t\tcopyToClipboard(getString(R.string.content_type_manga), title)\n\t\t\t\t\t}\n\t\t\t\t}.show()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onAuthorClick(author: String) {\n\t\trouter.showAuthorDialog(author, viewModel.getMangaOrNull()?.source ?: return)\n\t}\n\n\toverride fun onChipClick(chip: Chip, data: Any?) {\n\t\tval tag = data as? MangaTag ?: return\n\t\trouter.showTagDialog(tag)\n\t}\n\n\toverride fun onItemClick(item: Bookmark, view: View) {\n\t\trouter.openReader(ReaderIntent.Builder(view.context).bookmark(item).incognito().build())\n\t\tToast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()\n\t}\n\n\toverride fun onRefresh() {\n\t\tviewModel.reload()\n\t}\n\n\toverride fun onDraw() {\n\t\tviewBinding.run {\n\t\t\tbuttonDescriptionMore.isVisible = textViewDescription.maxLines == Int.MAX_VALUE ||\n\t\t\t\ttextViewDescription.isTextTruncated\n\t\t}\n\t}\n\n\toverride fun onLayoutChange(\n\t\tv: View?,\n\t\tleft: Int,\n\t\ttop: Int,\n\t\tright: Int,\n\t\tbottom: Int,\n\t\toldLeft: Int,\n\t\toldTop: Int,\n\t\toldRight: Int,\n\t\toldBottom: Int\n\t) {\n\t\twith(viewBinding) {\n\t\t\tcontainerBottomSheet?.let { sheet ->\n\t\t\t\tval peekHeight = BottomSheetBehavior.from(sheet).peekHeight\n\t\t\t\tif (scrollView.paddingBottom != peekHeight) {\n\t\t\t\t\tscrollView.updatePadding(bottom = peekHeight)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tif (viewBinding.cardChapters != null) {\n\t\t\t// landscape\n\t\t\tviewBinding.cardChapters?.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\t\ttopMargin = barsInsets.top + resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer)\n\t\t\t\tmarginEnd = barsInsets.end(v) + resources.getDimensionPixelOffset(R.dimen.side_card_offset)\n\t\t\t\tbottomMargin = barsInsets.bottom + resources.getDimensionPixelOffset(R.dimen.side_card_offset)\n\t\t\t}\n\t\t\tviewBinding.scrollView.updatePaddingRelative(\n\t\t\t\tbottom = barsInsets.bottom,\n\t\t\t\tstart = barsInsets.start(v),\n\t\t\t)\n\t\t\tviewBinding.appbar.updatePaddingRelative(\n\t\t\t\tstart = barsInsets.start(v),\n\t\t\t)\n\t\t\treturn insets.consume(v, typeMask, bottom = true, end = true)\n\t\t} else {\n\t\t\tviewBinding.navbarDim?.updateLayoutParams {\n\t\t\t\theight = barsInsets.bottom\n\t\t\t}\n\t\t\treturn insets\n\t\t}\n\t}\n\n\tprivate fun onFavoritesChanged(categories: Set<FavouriteCategory>) {\n\t\tval chip = viewBinding.chipFavorite\n\t\tchip.setChipIconResource(if (categories.isEmpty()) R.drawable.ic_heart_outline else R.drawable.ic_heart)\n\t\tchip.text = if (categories.isEmpty()) {\n\t\t\tgetString(R.string.add_to_favourites)\n\t\t} else {\n\t\t\tcategories.joinToStringWithLimit(this, FAV_LABEL_LIMIT) { it.title }\n\t\t}\n\t}\n\n\tprivate fun onLocalSizeChanged(size: Long) {\n\t\tif (size == 0L) {\n\t\t\tinfoBinding.textViewLocal.isVisible = false\n\t\t\tinfoBinding.textViewLocalLabel.isVisible = false\n\t\t} else {\n\t\t\tinfoBinding.textViewLocal.text = FileSize.BYTES.format(this, size)\n\t\t\tinfoBinding.textViewLocal.isVisible = true\n\t\t\tinfoBinding.textViewLocalLabel.isVisible = true\n\t\t}\n\t}\n\n\tprivate fun onRelatedMangaChanged(related: List<MangaListModel>) {\n\t\tif (related.isEmpty()) {\n\t\t\tviewBinding.groupRelated.isVisible = false\n\t\t\treturn\n\t\t}\n\t\tval rv = viewBinding.recyclerViewRelated\n\n\t\t@Suppress(\"UNCHECKED_CAST\")\n\t\tval adapter = (rv.adapter as? BaseListAdapter<ListModel>) ?: BaseListAdapter<ListModel>()\n\t\t\t.addDelegate(\n\t\t\t\tListItemType.MANGA_GRID,\n\t\t\t\tmangaGridItemAD(\n\t\t\t\t\tsizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)),\n\t\t\t\t) { item, view ->\n\t\t\t\t\trouter.openDetails(item.toMangaWithOverride())\n\t\t\t\t},\n\t\t\t).also { rv.adapter = it }\n\t\tadapter.items = related\n\t\tviewBinding.groupRelated.isVisible = true\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tviewBinding.swipeRefreshLayout.isRefreshing = isLoading\n\t}\n\n\tprivate fun onScrobblingInfoChanged(scrobblings: List<ScrobblingInfo>) {\n\t\tvar adapter = viewBinding.recyclerViewScrobbling.adapter as? ScrollingInfoAdapter\n\t\tviewBinding.groupScrobbling.isGone = scrobblings.isEmpty()\n\t\tif (adapter != null) {\n\t\t\tadapter.items = scrobblings\n\t\t} else {\n\t\t\tadapter = ScrollingInfoAdapter(router)\n\t\t\tadapter.items = scrobblings\n\t\t\tviewBinding.recyclerViewScrobbling.adapter = adapter\n\t\t\tviewBinding.recyclerViewScrobbling.addItemDecoration(ScrobblingItemDecoration())\n\t\t}\n\t}\n\n\tprivate fun onMangaUpdated(details: MangaDetails) {\n\t\tval manga = details.toManga()\n\t\twith(viewBinding) {\n\t\t\ttextViewTitle.text = manga.title\n\t\t\ttextViewSubtitle.textAndVisible = manga.altTitles.joinToString(\"\\n\")\n\t\t\ttextViewNsfw16.isVisible = manga.contentRating == ContentRating.SUGGESTIVE\n\t\t\ttextViewNsfw18.isVisible = manga.contentRating == ContentRating.ADULT\n\t\t\ttextViewDescription.text = details.description.ifNullOrEmpty { getString(R.string.no_description) }\n\t\t}\n\t\twith(infoBinding) {\n\t\t\tval translation = details.getLocale()\n\t\t\tinfoBinding.textViewTranslation.textAndVisible = translation?.getDisplayLanguage(translation)\n\t\t\t\t?.toTitleCase(translation)\n\t\t\tinfoBinding.textViewTranslation.drawableStart = translation?.let {\n\t\t\t\tLocaleUtils.getEmojiFlag(it)\n\t\t\t}?.let {\n\t\t\t\tTextDrawable.compound(infoBinding.textViewTranslation, it)\n\t\t\t}\n\t\t\tinfoBinding.textViewTranslationLabel.isVisible = infoBinding.textViewTranslation.isVisible\n\t\t\ttextViewAuthor.textAndVisible = manga.getAuthorsString()\n\t\t\ttextViewAuthorLabel.isVisible = textViewAuthor.isVisible\n\t\t\tif (manga.hasRating) {\n\t\t\t\tratingBarRating.rating = manga.rating * ratingBarRating.numStars\n\t\t\t\tratingBarRating.isVisible = true\n\t\t\t\ttextViewRatingLabel.isVisible = true\n\t\t\t} else {\n\t\t\t\tratingBarRating.isVisible = false\n\t\t\t\ttextViewRatingLabel.isVisible = false\n\t\t\t}\n\t\t\tmanga.state?.let { state ->\n\t\t\t\ttextViewState.textAndVisible = resources.getString(state.titleResId)\n\t\t\t\ttextViewStateLabel.isVisible = textViewState.isVisible\n\t\t\t} ?: run {\n\t\t\t\ttextViewState.isVisible = false\n\t\t\t\ttextViewStateLabel.isVisible = false\n\t\t\t}\n\n\t\t\tif (manga.source == LocalMangaSource || manga.source == UnknownMangaSource) {\n\t\t\t\ttextViewSource.isVisible = false\n\t\t\t\ttextViewSourceLabel.isVisible = false\n\t\t\t} else {\n\t\t\t\ttextViewSource.textAndVisible = manga.source.getTitle(this@DetailsActivity)\n\t\t\t\ttextViewSource.setTooltipCompat(manga.source.getSummary(this@DetailsActivity))\n\t\t\t\ttextViewSourceLabel.isVisible = textViewSource.isVisible == true\n\t\t\t}\n\t\t\tval faviconPlaceholderFactory = FaviconDrawable.Factory(R.style.FaviconDrawable_Chip)\n\t\t\tImageRequest.Builder(this@DetailsActivity)\n\t\t\t\t.data(manga.source.faviconUri())\n\t\t\t\t.lifecycle(this@DetailsActivity)\n\t\t\t\t.crossfade(false)\n\t\t\t\t.precision(Precision.EXACT)\n\t\t\t\t.size(resources.getDimensionPixelSize(materialR.dimen.m3_chip_icon_size))\n\t\t\t\t.target(TextViewTarget(textViewSource, Gravity.START))\n\t\t\t\t.placeholder(faviconPlaceholderFactory)\n\t\t\t\t.error(faviconPlaceholderFactory)\n\t\t\t\t.fallback(faviconPlaceholderFactory)\n\t\t\t\t.mangaSourceExtra(manga.source)\n\t\t\t\t.transformations(RoundedCornersTransformation(resources.getDimension(R.dimen.chip_icon_corner)))\n\t\t\t\t.allowRgb565(true)\n\t\t\t\t.enqueueWith(coil)\n\t\t}\n\t\ttitle = manga.title\n\t\tinvalidateOptionsMenu()\n\t}\n\n\tprivate fun onMangaRemoved(manga: Manga) {\n\t\tToast.makeText(\n\t\t\tthis,\n\t\t\tgetString(R.string._s_deleted_from_local_storage, manga.title),\n\t\t\tToast.LENGTH_SHORT,\n\t\t).show()\n\t\tfinishAfterTransition()\n\t}\n\n\tprivate fun onHistoryChanged(info: HistoryInfo, isLoading: Boolean) = with(infoBinding) {\n\t\ttextViewChapters.text = when {\n\t\t\tisLoading -> getString(R.string.loading_)\n\t\t\tinfo.currentChapter >= 0 -> getString(\n\t\t\t\tR.string.chapter_d_of_d,\n\t\t\t\tinfo.currentChapter + 1,\n\t\t\t\tinfo.totalChapters,\n\t\t\t).withEstimatedTime(info.estimatedTime)\n\n\t\t\tinfo.totalChapters == 0 -> getString(R.string.no_chapters)\n\t\t\tinfo.totalChapters == -1 -> getString(R.string.error_occurred)\n\t\t\telse -> resources.getQuantityStringSafe(R.plurals.chapters, info.totalChapters, info.totalChapters)\n\t\t\t\t.withEstimatedTime(info.estimatedTime)\n\t\t}\n\t\ttextViewProgress.textAndVisible = if (info.percent <= 0f) {\n\t\t\tnull\n\t\t} else {\n\t\t\tval displayPercent = if (ReadingProgress.isCompleted(info.percent)) 100 else (info.percent * 100f).toInt()\n\t\t\tgetString(R.string.percent_string_pattern, displayPercent.toString())\n\t\t}\n\n\t\tprogress.setProgressCompat(\n\t\t\t(progress.max * info.percent.coerceIn(0f, 1f)).roundToInt(),\n\t\t\ttrue,\n\t\t)\n\t\ttextViewProgressLabel.isVisible = info.history != null\n\t\ttextViewProgress.isVisible = info.history != null\n\t\tprogress.isVisible = info.history != null\n\t}\n\n\tprivate fun onTagsChanged(tags: Collection<ChipsView.ChipModel>) {\n\t\tviewBinding.chipsTags.isVisible = tags.isNotEmpty()\n\t\tviewBinding.chipsTags.setChips(tags)\n\t}\n\n\tprivate fun loadCover(imageUrl: String?) {\n\t\tviewBinding.imageViewCover.setImageAsync(imageUrl, viewModel.getMangaOrNull())\n\t}\n\n\tprivate fun String.withEstimatedTime(time: ReadingTime?): String {\n\t\tif (time == null) {\n\t\t\treturn this\n\t\t}\n\t\tval timeFormatted = time.formatShort(resources)\n\t\treturn getString(R.string.chapters_time_pattern, this, timeFormatted)\n\t}\n\n\tprivate fun Manga.getAuthorsString(): SpannedString? {\n\t\tif (authors.isEmpty()) {\n\t\t\treturn null\n\t\t}\n\t\treturn buildSpannedString {\n\t\t\tauthors.forEach { a ->\n\t\t\t\tif (a.isNotEmpty()) {\n\t\t\t\t\tif (isNotEmpty()) {\n\t\t\t\t\t\tappend(\", \")\n\t\t\t\t\t}\n\t\t\t\t\tinSpans(AuthorSpan(this@DetailsActivity)) {\n\t\t\t\t\t\tappend(a)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}.nullIfEmpty()\n\t}\n\n\tprivate class PrefetchObserver(\n\t\tprivate val context: Context,\n\t) : FlowCollector<List<ChapterListItem>?> {\n\n\t\tprivate var isCalled = false\n\n\t\toverride suspend fun emit(value: List<ChapterListItem>?) {\n\t\t\tif (value.isNullOrEmpty()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!isCalled) {\n\t\t\t\tisCalled = true\n\t\t\t\tval item = value.find { it.isCurrent } ?: value.first()\n\t\t\t\tMangaPrefetchService.prefetchPages(context, item.chapter)\n\t\t\t}\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val FAV_LABEL_LIMIT = 16\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsBottomSheetCallback.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport android.view.View\nimport androidx.swiperefreshlayout.widget.SwipeRefreshLayout\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\n\nclass DetailsBottomSheetCallback(\n\tprivate val swipeRefreshLayout: SwipeRefreshLayout,\n\tprivate val navbarDimView: View,\n) : BottomSheetBehavior.BottomSheetCallback() {\n\n\toverride fun onStateChanged(bottomSheet: View, newState: Int) {\n\t\tswipeRefreshLayout.isEnabled = newState == BottomSheetBehavior.STATE_COLLAPSED\n\t}\n\n\toverride fun onSlide(bottomSheet: View, slideOffset: Float) {\n\t\tnavbarDimView.alpha = 1f - slideOffset.coerceAtLeast(0f)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException\nimport org.koitharu.kotatsu.core.exceptions.resolve.ErrorObserver\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.isNetworkError\nimport org.koitharu.kotatsu.core.util.ext.isSerializable\nimport org.koitharu.kotatsu.parsers.exception.NotFoundException\nimport org.koitharu.kotatsu.parsers.exception.ParseException\n\nclass DetailsErrorObserver(\n\toverride val activity: DetailsActivity,\n\tprivate val viewModel: DetailsViewModel,\n\tresolver: ExceptionResolver?,\n) : ErrorObserver(\n\tactivity.viewBinding.scrollView, null, resolver,\n\t{ isResolved ->\n\t\tif (isResolved) {\n\t\t\tviewModel.reload()\n\t\t}\n\t},\n) {\n\n\toverride suspend fun emit(value: Throwable) {\n\t\tval snackbar = Snackbar.make(host, value.getDisplayMessage(host.context.resources), Snackbar.LENGTH_SHORT)\n\t\tsnackbar.setAnchorView(activity.viewBinding.containerBottomSheet)\n\t\tif (value is NotFoundException || value is UnsupportedSourceException) {\n\t\t\tsnackbar.duration = Snackbar.LENGTH_INDEFINITE\n\t\t}\n\t\twhen {\n\t\t\tcanResolve(value) -> {\n\t\t\t\tsnackbar.setAction(ExceptionResolver.getResolveStringId(value)) {\n\t\t\t\t\tresolve(value)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvalue is ParseException -> {\n\t\t\t\tval router = router()\n\t\t\t\tif (router != null && value.isSerializable()) {\n\t\t\t\t\tsnackbar.setAction(R.string.details) {\n\t\t\t\t\t\trouter.showErrorDialog(value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvalue.isNetworkError() -> {\n\t\t\t\tsnackbar.setAction(R.string.try_again) {\n\t\t\t\t\tviewModel.reload()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsnackbar.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport android.app.Activity\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.FragmentActivity\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.snackbar.Snackbar\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.util.ext.isHttpUrl\n\nclass DetailsMenuProvider(\n\tprivate val activity: FragmentActivity,\n\tprivate val viewModel: DetailsViewModel,\n\tprivate val snackbarHost: View,\n\tprivate val appShortcutManager: AppShortcutManager,\n) : MenuProvider, ActivityResultCallback<ActivityResult> {\n\n\tprivate val activityForResultLauncher = activity.registerForActivityResult(\n\t\tActivityResultContracts.StartActivityForResult(),\n\t\tthis,\n\t)\n\n\tprivate val router: AppRouter\n\t\tget() = activity.router\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_details, menu)\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tval manga = viewModel.manga.value\n\t\tmenu.findItem(R.id.action_share).isVisible = manga != null && AppRouter.isShareSupported(manga)\n\t\tmenu.findItem(R.id.action_save).isVisible = manga?.source != null && manga.source != LocalMangaSource\n\t\tmenu.findItem(R.id.action_delete).isVisible = manga?.source == LocalMangaSource\n\t\tmenu.findItem(R.id.action_browser).isVisible = manga?.publicUrl?.isHttpUrl() == true\n\t\tmenu.findItem(R.id.action_alternatives).isVisible = manga?.source != LocalMangaSource\n\t\tmenu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(activity)\n\t\tmenu.findItem(R.id.action_scrobbling).isVisible = viewModel.isScrobblingAvailable\n\t\tmenu.findItem(R.id.action_online).isVisible = viewModel.remoteManga.value != null\n\t\tmenu.findItem(R.id.action_stats).isVisible = viewModel.isStatsAvailable.value\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\tval manga = viewModel.getMangaOrNull() ?: return false\n\t\twhen (menuItem.itemId) {\n\t\t\tR.id.action_share -> {\n\t\t\t\trouter.showShareDialog(manga)\n\t\t\t}\n\n\t\t\tR.id.action_delete -> {\n\t\t\t\tbuildAlertDialog(activity) {\n\t\t\t\t\tsetTitle(R.string.delete_manga)\n\t\t\t\t\tsetMessage(activity.getString(R.string.text_delete_local_manga, manga.title))\n\t\t\t\t\tsetPositiveButton(R.string.delete) { _, _ -> viewModel.deleteLocal() }\n\t\t\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\t\t}.show()\n\t\t\t}\n\n\t\t\tR.id.action_save -> {\n\t\t\t\trouter.showDownloadDialog(manga, snackbarHost)\n\t\t\t}\n\n\t\t\tR.id.action_browser -> {\n\t\t\t\trouter.openBrowser(url = manga.publicUrl, source = manga.source, title = manga.title)\n\t\t\t}\n\n\t\t\tR.id.action_online -> {\n\t\t\t\trouter.openDetails(viewModel.remoteManga.value ?: return false)\n\t\t\t}\n\n\t\t\tR.id.action_related -> {\n\t\t\t\trouter.openSearch(manga.title)\n\t\t\t}\n\n\t\t\tR.id.action_alternatives -> {\n\t\t\t\trouter.openAlternatives(manga)\n\t\t\t}\n\n\t\t\tR.id.action_stats -> {\n\t\t\t\trouter.showStatisticSheet(manga)\n\t\t\t}\n\n\t\t\tR.id.action_scrobbling -> {\n\t\t\t\trouter.showScrobblingSelectorSheet(manga, null)\n\t\t\t}\n\n\t\t\tR.id.action_shortcut -> {\n\t\t\t\tactivity.lifecycleScope.launch {\n\t\t\t\t\tif (!appShortcutManager.requestPinShortcut(manga)) {\n\t\t\t\t\t\tSnackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)\n\t\t\t\t\t\t\t.show()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tR.id.action_edit_override -> {\n\t\t\t\tval intent = AppRouter.overrideEditIntent(activity, manga)\n\t\t\t\tactivityForResultLauncher.launch(intent)\n\t\t\t}\n\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onActivityResult(result: ActivityResult) {\n\t\tif (result.resultCode == Activity.RESULT_OK) {\n\t\t\tviewModel.reload()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.firstOrNull\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.mapLatest\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository\nimport org.koitharu.kotatsu.core.model.getPreferredBranch\nimport org.koitharu.kotatsu.core.nav.MangaIntent\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.computeSize\nimport org.koitharu.kotatsu.core.util.ext.onEachWhile\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.domain.BranchComparator\nimport org.koitharu.kotatsu.details.domain.DetailsInteractor\nimport org.koitharu.kotatsu.details.domain.DetailsLoadUseCase\nimport org.koitharu.kotatsu.details.domain.ProgressUpdateUseCase\nimport org.koitharu.kotatsu.details.domain.ReadingTimeUseCase\nimport org.koitharu.kotatsu.details.domain.RelatedMangaUseCase\nimport org.koitharu.kotatsu.details.ui.model.HistoryInfo\nimport org.koitharu.kotatsu.details.ui.model.MangaBranch\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport org.koitharu.kotatsu.stats.data.StatsRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass DetailsViewModel @Inject constructor(\n\tprivate val historyRepository: HistoryRepository,\n\tbookmarksRepository: BookmarksRepository,\n\tsettings: AppSettings,\n\tprivate val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n\tdownloadScheduler: DownloadWorker.Scheduler,\n\tinteractor: DetailsInteractor,\n\tsavedStateHandle: SavedStateHandle,\n\tdeleteLocalMangaUseCase: DeleteLocalMangaUseCase,\n\tprivate val relatedMangaUseCase: RelatedMangaUseCase,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val detailsLoadUseCase: DetailsLoadUseCase,\n\tprivate val progressUpdateUseCase: ProgressUpdateUseCase,\n\tprivate val readingTimeUseCase: ReadingTimeUseCase,\n\tstatsRepository: StatsRepository,\n) : ChaptersPagesViewModel(\n\tsettings = settings,\n\tinteractor = interactor,\n\tbookmarksRepository = bookmarksRepository,\n\thistoryRepository = historyRepository,\n\tdownloadScheduler = downloadScheduler,\n\tdeleteLocalMangaUseCase = deleteLocalMangaUseCase,\n\tlocalStorageChanges = localStorageChanges,\n) {\n\n\tprivate val intent = MangaIntent(savedStateHandle)\n\tprivate var loadingJob: Job\n\tval mangaId = intent.mangaId\n\n\tinit {\n\t\tmangaDetails.value = intent.manga?.let { MangaDetails(it) }\n\t}\n\n\tval history = historyRepository.observeOne(mangaId)\n\t\t.onEach { h ->\n\t\t\treadingState.value = h?.let(::ReaderState)\n\t\t}.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)\n\n\tval favouriteCategories = interactor.observeFavourite(mangaId)\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptySet())\n\n\tval isStatsAvailable = statsRepository.observeHasStats(mangaId)\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)\n\n\tval remoteManga = MutableStateFlow<Manga?>(null)\n\n\tval historyInfo: StateFlow<HistoryInfo> = combine(\n\t\tmangaDetails,\n\t\tselectedBranch,\n\t\thistory,\n\t\tinteractor.observeIncognitoMode(manga),\n\t) { m, b, h, im ->\n\t\tval estimatedTime = readingTimeUseCase.invoke(m, b, h)\n\t\tHistoryInfo(m, b, h, im == TriStateOption.ENABLED, estimatedTime)\n\t}.withErrorHandling()\n\t\t.stateIn(\n\t\t\tscope = viewModelScope + Dispatchers.Default,\n\t\t\tstarted = SharingStarted.Eagerly,\n\t\t\tinitialValue = HistoryInfo(null, null, null, false, null),\n\t\t)\n\n\tval localSize = mangaDetails\n\t\t.map { it?.local }\n\t\t.distinctUntilChanged()\n\t\t.combine(localStorageChanges.onStart { emit(null) }) { x, _ -> x }\n\t\t.map { local ->\n\t\t\tif (local != null) {\n\t\t\t\trunCatchingCancellable {\n\t\t\t\t\tlocal.file.computeSize()\n\t\t\t\t}.getOrDefault(0L)\n\t\t\t} else {\n\t\t\t\t0L\n\t\t\t}\n\t\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), 0L)\n\n\tval isScrobblingAvailable: Boolean\n\t\tget() = scrobblers.any { it.isEnabled }\n\n\tval scrobblingInfo: StateFlow<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId)\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tval relatedManga: StateFlow<List<MangaListModel>> = manga.mapLatest {\n\t\tif (it != null && settings.isRelatedMangaEnabled) {\n\t\t\tmangaListMapper.toListModelList(\n\t\t\t\tmanga = relatedMangaUseCase(it).orEmpty(),\n\t\t\t\tmode = ListMode.GRID,\n\t\t\t)\n\t\t} else {\n\t\t\temptyList()\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())\n\n\tval tags = manga.mapLatest {\n\t\tmangaListMapper.mapTags(it?.tags.orEmpty())\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tval branches: StateFlow<List<MangaBranch>> = combine(\n\t\tmangaDetails,\n\t\tselectedBranch,\n\t\thistory,\n\t) { m, b, h ->\n\t\tval c = m?.chapters\n\t\tif (c.isNullOrEmpty()) {\n\t\t\treturn@combine emptyList()\n\t\t}\n\t\tval currentBranch = h?.let { m.allChapters.findById(it.chapterId) }?.branch\n\t\tc.map { x ->\n\t\t\tMangaBranch(\n\t\t\t\tname = x.key,\n\t\t\t\tcount = x.value.size,\n\t\t\t\tisSelected = x.key == b,\n\t\t\t\tisCurrent = h != null && x.key == currentBranch,\n\t\t\t)\n\t\t}.sortedWith(BranchComparator())\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tval selectedBranchValue: String?\n\t\tget() = selectedBranch.value\n\n\tinit {\n\t\tloadingJob = doLoad(force = false)\n\t\tlaunchJob(Dispatchers.Default + SkipErrors) {\n\t\t\tval manga = mangaDetails.firstOrNull { !it?.chapters.isNullOrEmpty() } ?: return@launchJob\n\t\t\tval h = history.firstOrNull()\n\t\t\tif (h != null) {\n\t\t\t\tprogressUpdateUseCase(manga.toManga())\n\t\t\t}\n\t\t}\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval manga = mangaDetails.firstOrNull { it != null && it.isLocal } ?: return@launchJob\n\t\t\tremoteManga.value = interactor.findRemote(manga.toManga())\n\t\t}\n\t}\n\n\tfun reload() {\n\t\tloadingJob.cancel()\n\t\tloadingJob = doLoad(force = true)\n\t}\n\n\tfun updateScrobbling(index: Int, rating: Float, status: ScrobblingStatus?) {\n\t\tval scrobbler = getScrobbler(index) ?: return\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tscrobbler.updateScrobblingInfo(\n\t\t\t\tmangaId = mangaId,\n\t\t\t\trating = rating,\n\t\t\t\tstatus = status,\n\t\t\t\tcomment = null,\n\t\t\t)\n\t\t}\n\t}\n\n\tfun unregisterScrobbling(index: Int) {\n\t\tval scrobbler = getScrobbler(index) ?: return\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tscrobbler.unregisterScrobbling(\n\t\t\t\tmangaId = mangaId,\n\t\t\t)\n\t\t}\n\t}\n\n\tfun removeFromHistory() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval handle = historyRepository.delete(setOf(mangaId))\n\t\t\tonActionDone.call(ReversibleAction(R.string.removed_from_history, handle))\n\t\t}\n\t}\n\n\tprivate fun doLoad(force: Boolean) = launchLoadingJob(Dispatchers.Default) {\n\t\tdetailsLoadUseCase.invoke(intent, force)\n\t\t\t.onEachWhile {\n\t\t\t\tif (it.allChapters.isNotEmpty()) {\n\t\t\t\t\tval manga = it.toManga()\n\t\t\t\t\t// find default branch\n\t\t\t\t\tval hist = historyRepository.getOne(manga)\n\t\t\t\t\tselectedBranch.value = manga.getPreferredBranch(hist)\n\t\t\t\t\ttrue\n\t\t\t\t} else {\n\t\t\t\t\tfalse\n\t\t\t\t}\n\t\t\t}.collect {\n\t\t\t\tmangaDetails.value = it\n\t\t\t}\n\t}\n\n\tprivate fun getScrobbler(index: Int): Scrobbler? {\n\t\tval info = scrobblingInfo.value.getOrNull(index)\n\t\tval scrobbler = if (info != null) {\n\t\t\tscrobblers.find { it.scrobblerService == info.scrobbler && it.isEnabled }\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t\tif (scrobbler == null) {\n\t\t\terrorEvent.call(IllegalStateException(\"Scrobbler [$index] is not available\"))\n\t\t}\n\t\treturn scrobbler\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/ReadButtonDelegate.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.text.style.DynamicDrawableSpan\nimport android.text.style.ForegroundColorSpan\nimport android.text.style.ImageSpan\nimport android.text.style.RelativeSizeSpan\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.Toast\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.inSpans\nimport androidx.core.view.MenuCompat\nimport androidx.core.view.get\nimport androidx.lifecycle.LifecycleOwner\nimport com.google.android.material.button.MaterialButton\nimport com.google.android.material.button.MaterialSplitButton\nimport com.google.android.material.snackbar.Snackbar\nimport kotlinx.coroutines.flow.combine\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.details.ui.model.HistoryInfo\n\nclass ReadButtonDelegate(\n\tprivate val splitButton: MaterialSplitButton,\n\tprivate val viewModel: DetailsViewModel,\n\tprivate val router: AppRouter,\n) : View.OnClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {\n\n\tprivate val buttonRead = splitButton[0] as MaterialButton\n\tprivate val buttonMenu = splitButton[1] as MaterialButton\n\n\tprivate val context: Context\n\t\tget() = buttonRead.context\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_read -> openReader(isIncognitoMode = false)\n\t\t\tR.id.button_read_menu -> showMenu()\n\t\t}\n\t}\n\n\toverride fun onMenuItemClick(item: MenuItem): Boolean {\n\t\twhen (item.itemId) {\n\t\t\tR.id.action_incognito -> openReader(isIncognitoMode = true)\n\t\t\tR.id.action_forget -> viewModel.removeFromHistory()\n\t\t\tR.id.action_download -> {\n\t\t\t\trouter.showDownloadDialog(\n\t\t\t\t\tmanga = setOf(viewModel.getMangaOrNull() ?: return false),\n\t\t\t\t\tsnackbarHost = splitButton,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tMenu.NONE -> {\n\t\t\t\tval branch = viewModel.branches.value.getOrNull(item.order) ?: return false\n\t\t\t\tviewModel.setSelectedBranch(branch.name)\n\t\t\t}\n\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onDismiss(menu: PopupMenu?) {\n\t\tbuttonMenu.isChecked = false\n\t}\n\n\tfun attach(lifecycleOwner: LifecycleOwner) {\n\t\tbuttonRead.setOnClickListener(this)\n\t\tbuttonMenu.setOnClickListener(this)\n\t\tcombine(viewModel.isLoading, viewModel.historyInfo, ::Pair)\n\t\t\t.observe(lifecycleOwner) { (isLoading, historyInfo) ->\n\t\t\t\tonHistoryChanged(isLoading, historyInfo)\n\t\t\t}\n\t}\n\n\tprivate fun showMenu() {\n\t\tval menu = PopupMenu(context, buttonMenu)\n\t\tmenu.inflate(R.menu.popup_read)\n\t\tprepareMenu(menu.menu)\n\t\tmenu.setOnMenuItemClickListener(this)\n\t\tmenu.setForceShowIcon(true)\n\t\tmenu.setOnDismissListener(this)\n\t\tif (menu.menu.hasVisibleItems()) {\n\t\t\tbuttonMenu.isChecked = true\n\t\t\tmenu.show()\n\t\t} else {\n\t\t\tbuttonMenu.isChecked = false\n\t\t}\n\t}\n\n\tprivate fun prepareMenu(menu: Menu) {\n\t\tMenuCompat.setGroupDividerEnabled(menu, true)\n\t\tmenu.populateBranchList()\n\t\tval history = viewModel.historyInfo.value\n\t\tmenu.findItem(R.id.action_incognito)?.isVisible = !history.isIncognitoMode\n\t\tmenu.findItem(R.id.action_forget)?.isVisible = history.history != null\n\t\tmenu.findItem(R.id.action_download)?.isVisible = viewModel.getMangaOrNull()?.isLocal == false\n\t}\n\n\tprivate fun openReader(isIncognitoMode: Boolean) {\n\t\tval manga = viewModel.getMangaOrNull() ?: return\n\t\tif (viewModel.historyInfo.value.isChapterMissing) {\n\t\t\tSnackbar.make(buttonRead, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)\n\t\t\t\t.show() // TODO\n\t\t} else {\n\t\t\tval intentBuilder = ReaderIntent.Builder(context)\n\t\t\t\t.manga(manga)\n\t\t\t\t.branch(viewModel.selectedBranchValue)\n\t\t\tif (isIncognitoMode) {\n\t\t\t\tintentBuilder.incognito()\n\t\t\t}\n\t\t\trouter.openReader(intentBuilder.build())\n\t\t\tif (isIncognitoMode) {\n\t\t\t\tToast.makeText(context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onHistoryChanged(isLoading: Boolean, info: HistoryInfo) {\n\t\tval isChaptersLoading = isLoading && (info.totalChapters <= 0 || info.isChapterMissing)\n\t\tbuttonRead.setText(\n\t\t\twhen {\n\t\t\t\tisChaptersLoading -> R.string.loading_\n\t\t\t\tinfo.isIncognitoMode -> R.string.incognito\n\t\t\t\tinfo.canContinue -> R.string._continue\n\t\t\t\telse -> R.string.read\n\t\t\t},\n\t\t)\n\t\tsplitButton.isEnabled = !isChaptersLoading && info.isValid\n\t}\n\n\tprivate fun Menu.populateBranchList() {\n\t\tval branches = viewModel.branches.value\n\t\tif (branches.size <= 1) {\n\t\t\treturn\n\t\t}\n\t\tfor ((i, branch) in branches.withIndex()) {\n\t\t\tval title = buildSpannedString {\n\t\t\t\tif (branch.isCurrent) {\n\t\t\t\t\tinSpans(\n\t\t\t\t\t\tImageSpan(\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tR.drawable.ic_current_chapter,\n\t\t\t\t\t\t\tDynamicDrawableSpan.ALIGN_BASELINE,\n\t\t\t\t\t\t),\n\t\t\t\t\t) {\n\t\t\t\t\t\tappend(' ')\n\t\t\t\t\t}\n\t\t\t\t\tappend(' ')\n\t\t\t\t}\n\t\t\t\tappend(branch.name ?: context.getString(R.string.system_default))\n\t\t\t\tappend(' ')\n\t\t\t\tappend(' ')\n\t\t\t\tinSpans(\n\t\t\t\t\tForegroundColorSpan(\n\t\t\t\t\t\tcontext.getThemeColor(\n\t\t\t\t\t\t\tandroid.R.attr.textColorSecondary,\n\t\t\t\t\t\t\tColor.LTGRAY,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tRelativeSizeSpan(0.74f),\n\t\t\t\t) {\n\t\t\t\t\tappend(branch.count.toString())\n\t\t\t\t}\n\t\t\t}\n\t\t\tval item = add(R.id.group_branches, Menu.NONE, i, title)\n\t\t\titem.isCheckable = true\n\t\t\titem.isChecked = branch.isSelected\n\t\t}\n\t\tsetGroupCheckable(R.id.group_branches, true, true)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/TitleScrollCoordinator.kt",
    "content": "package org.koitharu.kotatsu.details.ui\n\nimport android.content.Context\nimport android.widget.TextView\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.doOnLayout\nimport androidx.core.widget.NestedScrollView\nimport org.koitharu.kotatsu.core.util.ext.findActivity\nimport java.lang.ref.WeakReference\n\nclass TitleScrollCoordinator(\n\tprivate val titleView: TextView,\n) : NestedScrollView.OnScrollChangeListener {\n\n\tprivate val location = IntArray(2)\n\tprivate var activityRef: WeakReference<AppCompatActivity>? = null\n\n\toverride fun onScrollChange(v: NestedScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) {\n\t\tval actionBar = getActivity(v.context)?.supportActionBar ?: return\n\t\ttitleView.getLocationOnScreen(location)\n\t\tvar top = location[1] + titleView.height\n\t\tv.getLocationOnScreen(location)\n\t\ttop -= location[1]\n\t\tactionBar.setDisplayShowTitleEnabled(top < 0)\n\t}\n\n\tfun attach(scrollView: NestedScrollView) {\n\t\tscrollView.setOnScrollChangeListener(this)\n\t\tscrollView.doOnLayout {\n\t\t\tonScrollChange(scrollView, 0, 0, 0, 0)\n\t\t}\n\t}\n\n\tprivate fun getActivity(context: Context): AppCompatActivity? {\n\t\tactivityRef?.get()?.let {\n\t\t\tif (!it.isDestroyed) return it\n\t\t}\n\t\tval activity = context.findActivity() as? AppCompatActivity\n\t\tif (activity == null || activity.isDestroyed) {\n\t\t\treturn null\n\t\t}\n\t\tactivityRef = WeakReference(activity)\n\t\treturn activity\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChapterGridItemAD.kt",
    "content": "package org.koitharu.kotatsu.details.ui.adapter\n\nimport android.graphics.Typeface\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.getThemeColorStateList\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.databinding.ItemChapterGridBinding\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun chapterGridItemAD(\n\tclickListener: OnListItemClickListener<ChapterListItem>,\n) = adapterDelegateViewBinding<ChapterListItem, ListModel, ItemChapterGridBinding>(\n\tviewBinding = { inflater, parent -> ItemChapterGridBinding.inflate(inflater, parent, false) },\n\ton = { item, _, _ -> item is ChapterListItem && item.isGrid },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)\n\n\tbind { payloads ->\n\t\tif (payloads.isEmpty()) {\n\t\t\tbinding.textViewTitle.text = item.chapter.numberString() ?: \"?\"\n\t\t\titemView.setTooltipCompat(item.chapter.title)\n\t\t}\n\t\tbinding.imageViewNew.isVisible = item.isNew\n\t\tbinding.imageViewCurrent.isVisible = item.isCurrent\n\t\tbinding.imageViewBookmarked.isVisible = item.isBookmarked\n\t\tbinding.imageViewDownloaded.isVisible = item.isDownloaded\n\n\t\twhen {\n\t\t\titem.isCurrent -> {\n\t\t\t\tbinding.textViewTitle.setTextColor(context.getThemeColorStateList(android.R.attr.textColorPrimary))\n\t\t\t\tbinding.textViewTitle.typeface = Typeface.DEFAULT_BOLD\n\t\t\t}\n\n\t\t\titem.isUnread -> {\n\t\t\t\tbinding.textViewTitle.setTextColor(context.getThemeColorStateList(android.R.attr.textColorPrimary))\n\t\t\t\tbinding.textViewTitle.typeface = Typeface.DEFAULT\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tbinding.textViewTitle.setTextColor(context.getThemeColorStateList(android.R.attr.textColorHint))\n\t\t\t\tbinding.textViewTitle.typeface = Typeface.DEFAULT\n\t\t\t}\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt",
    "content": "package org.koitharu.kotatsu.details.ui.adapter\n\nimport android.graphics.Typeface\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.getThemeColorStateList\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemChapterBinding\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport com.google.android.material.R as materialR\n\nfun chapterListItemAD(\n\tclickListener: OnListItemClickListener<ChapterListItem>,\n) = adapterDelegateViewBinding<ChapterListItem, ListModel, ItemChapterBinding>(\n\tviewBinding = { inflater, parent -> ItemChapterBinding.inflate(inflater, parent, false) },\n\ton = { item, _, _ -> item is ChapterListItem && !item.isGrid },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.getTitle(context.resources)\n\t\tbinding.textViewDescription.textAndVisible = item.description\n\t\twhen {\n\t\t\titem.isCurrent -> {\n\t\t\t\tbinding.textViewTitle.drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_current_chapter)\n\t\t\t\tbinding.textViewTitle.setTextColor(context.getThemeColorStateList(android.R.attr.textColorPrimary))\n\t\t\t\tbinding.textViewDescription.setTextColor(context.getThemeColorStateList(android.R.attr.textColorPrimary))\n\t\t\t\tbinding.textViewTitle.typeface = Typeface.DEFAULT_BOLD\n\t\t\t\tbinding.textViewDescription.typeface = Typeface.DEFAULT_BOLD\n\t\t\t}\n\n\t\t\titem.isUnread -> {\n\t\t\t\tbinding.textViewTitle.drawableStart = if (item.isNew) {\n\t\t\t\t\tContextCompat.getDrawable(context, R.drawable.ic_new)\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t\tbinding.textViewTitle.setTextColor(context.getThemeColorStateList(android.R.attr.textColorPrimary))\n\t\t\t\tbinding.textViewDescription.setTextColor(context.getThemeColorStateList(materialR.attr.colorOutline))\n\t\t\t\tbinding.textViewTitle.typeface = Typeface.DEFAULT\n\t\t\t\tbinding.textViewDescription.typeface = Typeface.DEFAULT\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tbinding.textViewTitle.drawableStart = null\n\t\t\t\tbinding.textViewTitle.setTextColor(context.getThemeColorStateList(android.R.attr.textColorHint))\n\t\t\t\tbinding.textViewDescription.setTextColor(context.getThemeColorStateList(android.R.attr.textColorHint))\n\t\t\t\tbinding.textViewTitle.typeface = Typeface.DEFAULT\n\t\t\t\tbinding.textViewDescription.typeface = Typeface.DEFAULT\n\t\t\t}\n\t\t}\n\t\tbinding.imageViewBookmarked.isVisible = item.isBookmarked\n\t\tbinding.imageViewDownloaded.isVisible = item.isDownloaded\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt",
    "content": "package org.koitharu.kotatsu.details.ui.adapter\n\nimport android.content.Context\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.listHeaderAD\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass ChaptersAdapter(\n\tonItemClickListener: OnListItemClickListener<ChapterListItem>,\n) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {\n\n\tprivate var hasVolumes = false\n\n\tinit {\n\t\taddDelegate(ListItemType.HEADER, listHeaderAD(null))\n\t\taddDelegate(ListItemType.CHAPTER_LIST, chapterListItemAD(onItemClickListener))\n\t\taddDelegate(ListItemType.CHAPTER_GRID, chapterGridItemAD(onItemClickListener))\n\t}\n\n\toverride suspend fun emit(value: List<ListModel>?) {\n\t\tsuper.emit(value)\n\t\thasVolumes = value != null && value.any { it is ListHeader }\n\t}\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn if (hasVolumes) {\n\t\t\tfindHeader(position)?.getText(context)\n\t\t} else {\n\t\t\tval chapter = (items.getOrNull(position) as? ChapterListItem)?.chapter ?: return null\n\t\t\tchapter.numberString()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.details.ui.adapter\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.view.View\nimport androidx.cardview.widget.CardView\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.ColorUtils\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nclass ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate val radius = context.resources.getDimension(appcompatR.dimen.abc_control_corner_material)\n\tprivate val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle)\n\tprivate val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_offset)\n\tprivate val iconSize = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_size)\n\tprivate val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)\n\tprivate val fillColor = ColorUtils.setAlphaComponent(\n\t\tColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),\n\t\t0x74,\n\t)\n\n\tinit {\n\t\tpaint.color = ColorUtils.setAlphaComponent(\n\t\t\tcontext.getThemeColor(appcompatR.attr.colorPrimary, Color.DKGRAY),\n\t\t\t98,\n\t\t)\n\t\tpaint.style = Paint.Style.FILL\n\t\thasBackground = true\n\t\thasForeground = true\n\t\tisIncludeDecorAndMargins = false\n\n\t\tpaint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)\n\t\tcheckIcon?.setTint(strokeColor)\n\t}\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return RecyclerView.NO_ID\n\t\tval item = holder.getItem(ChapterListItem::class.java) ?: return RecyclerView.NO_ID\n\t\treturn item.chapter.id\n\t}\n\n\toverride fun onDrawBackground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\tif (child is CardView) {\n\t\t\treturn\n\t\t}\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t}\n\n\toverride fun onDrawForeground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State\n\t) {\n\t\tif (child !is CardView) {\n\t\t\treturn\n\t\t}\n\t\tval radius = child.radius\n\t\tpaint.color = fillColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t\tpaint.color = strokeColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t\tcheckIcon?.run {\n\t\t\tsetBounds(\n\t\t\t\t(bounds.right - iconSize - iconOffset).toInt(),\n\t\t\t\t(bounds.top + iconOffset).toInt(),\n\t\t\t\t(bounds.right - iconOffset).toInt(),\n\t\t\t\t(bounds.top + iconOffset + iconSize).toInt(),\n\t\t\t)\n\t\t\tdraw(canvas)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt",
    "content": "package org.koitharu.kotatsu.details.ui.model\n\nimport android.content.res.Resources\nimport android.text.format.DateUtils\nimport org.jsoup.internal.StringUtil.StringJoiner\nimport org.koitharu.kotatsu.core.model.getLocalizedTitle\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport kotlin.experimental.and\n\ndata class ChapterListItem(\n\tval chapter: MangaChapter,\n\tval flags: Byte,\n) : ListModel {\n\n\tprivate var cachedTitle: String? = null\n\n\tvar description: String? = null\n\t\tprivate set\n\t\tget() {\n\t\t\tif (field != null) return field\n\t\t\tfield = buildDescription()\n\t\t\treturn field\n\t\t}\n\n\tvar uploadDate: CharSequence? = null\n\t\tprivate set\n\t\tget() {\n\t\t\tif (field != null) return field\n\t\t\tif (chapter.uploadDate == 0L) return null\n\t\t\tfield = DateUtils.getRelativeTimeSpanString(\n\t\t\t\tchapter.uploadDate,\n\t\t\t\tSystem.currentTimeMillis(),\n\t\t\t\tDateUtils.DAY_IN_MILLIS,\n\t\t\t)\n\t\t\treturn field\n\t\t}\n\n\tval isCurrent: Boolean\n\t\tget() = hasFlag(FLAG_CURRENT)\n\n\tval isUnread: Boolean\n\t\tget() = hasFlag(FLAG_UNREAD)\n\n\tval isDownloaded: Boolean\n\t\tget() = hasFlag(FLAG_DOWNLOADED)\n\n\tval isBookmarked: Boolean\n\t\tget() = hasFlag(FLAG_BOOKMARKED)\n\n\tval isNew: Boolean\n\t\tget() = hasFlag(FLAG_NEW)\n\n\tval isGrid: Boolean\n\t\tget() = hasFlag(FLAG_GRID)\n\n\toperator fun contains(query: String): Boolean = with(chapter) {\n\t\ttitle?.contains(query, ignoreCase = true) == true\n\t\t\t|| numberString()?.contains(query) == true\n\t\t\t|| volumeString()?.contains(query) == true\n\t}\n\n\tfun getTitle(resources: Resources): String {\n\t\tcachedTitle?.let {\n\t\t\treturn it\n\t\t}\n\t\treturn chapter.getLocalizedTitle(resources).also {\n\t\t\tcachedTitle = it\n\t\t}\n\t}\n\n\tprivate fun buildDescription(): String {\n\t\tval joiner = StringJoiner(\" • \")\n\t\tchapter.numberString()?.let {\n\t\t\tjoiner.add(\"#\").append(it)\n\t\t}\n\t\tuploadDate?.let { date ->\n\t\t\tjoiner.add(date.toString())\n\t\t}\n\t\tchapter.scanlator?.let { scanlator ->\n\t\t\tif (scanlator.isNotBlank()) {\n\t\t\t\tjoiner.add(scanlator)\n\t\t\t}\n\t\t}\n\t\treturn joiner.complete()\n\t}\n\n\tprivate fun hasFlag(flag: Byte): Boolean {\n\t\treturn (flags and flag) == flag\n\t}\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ChapterListItem && chapter.id == other.chapter.id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\tif (previousState !is ChapterListItem) {\n\t\t\treturn super.getChangePayload(previousState)\n\t\t}\n\t\treturn if (chapter == previousState.chapter && flags != previousState.flags) {\n\t\t\tflags\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tconst val FLAG_UNREAD: Byte = 2\n\t\tconst val FLAG_CURRENT: Byte = 4\n\t\tconst val FLAG_NEW: Byte = 8\n\t\tconst val FLAG_BOOKMARKED: Byte = 16\n\t\tconst val FLAG_DOWNLOADED: Byte = 32\n\t\tconst val FLAG_GRID: Byte = 64\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/HistoryInfo.kt",
    "content": "package org.koitharu.kotatsu.details.ui.model\n\nimport org.koitharu.kotatsu.core.model.MangaHistory\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.data.ReadingTime\n\ndata class HistoryInfo(\n\tval totalChapters: Int,\n\tval currentChapter: Int,\n\tval history: MangaHistory?,\n\tval isIncognitoMode: Boolean,\n\tval isChapterMissing: Boolean,\n\tval canDownload: Boolean,\n\tval estimatedTime: ReadingTime?,\n) {\n\tval isValid: Boolean\n\t\tget() = totalChapters >= 0\n\n\tval canContinue\n\t\tget() = currentChapter >= 0\n\n\tval percent: Float\n\t\tget() = if (history != null && (canContinue || isChapterMissing)) {\n\t\t\thistory.percent\n\t\t} else {\n\t\t\t0f\n\t\t}\n}\n\nfun HistoryInfo(\n\tmanga: MangaDetails?,\n\tbranch: String?,\n\thistory: MangaHistory?,\n\tisIncognitoMode: Boolean,\n\testimatedTime: ReadingTime?,\n): HistoryInfo {\n\tval chapters = if (manga?.chapters?.isEmpty() == true) {\n\t\temptyList()\n\t} else {\n\t\tmanga?.chapters?.get(branch)\n\t}\n\tval currentChapter = if (history != null && !chapters.isNullOrEmpty()) {\n\t\tchapters.indexOfFirst { it.id == history.chapterId }\n\t} else {\n\t\t-2\n\t}\n\treturn HistoryInfo(\n\t\ttotalChapters = chapters?.size ?: -1,\n\t\tcurrentChapter = currentChapter,\n\t\thistory = history,\n\t\tisIncognitoMode = isIncognitoMode,\n\t\tisChapterMissing = history != null && manga?.isLoaded == true && manga.allChapters.none { it.id == history.chapterId },\n\t\tcanDownload = manga?.isLocal == false,\n\t\testimatedTime = estimatedTime,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt",
    "content": "package org.koitharu.kotatsu.details.ui.model\n\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_BOOKMARKED\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_CURRENT\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_DOWNLOADED\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_GRID\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_NEW\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_UNREAD\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport kotlin.experimental.or\n\nfun MangaChapter.toListItem(\n\tisCurrent: Boolean,\n\tisUnread: Boolean,\n\tisNew: Boolean,\n\tisDownloaded: Boolean,\n\tisBookmarked: Boolean,\n\tisGrid: Boolean,\n): ChapterListItem {\n\tvar flags: Byte = 0\n\tif (isCurrent) flags = flags or FLAG_CURRENT\n\tif (isUnread) flags = flags or FLAG_UNREAD\n\tif (isNew) flags = flags or FLAG_NEW\n\tif (isBookmarked) flags = flags or FLAG_BOOKMARKED\n\tif (isDownloaded) flags = flags or FLAG_DOWNLOADED\n\tif (isGrid) flags = flags or FLAG_GRID\n\treturn ChapterListItem(\n\t\tchapter = this,\n\t\tflags = flags,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt",
    "content": "package org.koitharu.kotatsu.details.ui.model\n\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class MangaBranch(\n\tval name: String?,\n\tval count: Int,\n\tval isSelected: Boolean,\n\tval isCurrent: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is MangaBranch && other.name == name\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\treturn if (previousState is MangaBranch && previousState.isSelected != isSelected) {\n\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n\n\toverride fun toString(): String {\n\t\treturn \"$name: $count\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChapterPagesMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.activity.OnBackPressedCallback\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.MenuProvider\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.slider.LabelFormatter\nimport com.google.android.material.slider.Slider\nimport com.google.android.material.slider.TickVisibilityMode\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.util.ext.setValueRounded\nimport org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_BOOKMARKS\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_CHAPTERS\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_PAGES\nimport java.lang.ref.WeakReference\n\nclass ChapterPagesMenuProvider(\n\tprivate val viewModel: ChaptersPagesViewModel,\n\tprivate val sheet: BaseAdaptiveSheet<*>,\n\tprivate val pager: ViewPager2,\n\tprivate val settings: AppSettings,\n) : OnBackPressedCallback(false), MenuProvider, SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener,\n\tSlider.OnChangeListener {\n\n\tprivate var expandedItemRef: WeakReference<MenuItem>? = null\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tval tab = getCurrentTab()\n\t\twhen (tab) {\n\t\t\tTAB_CHAPTERS -> {\n\t\t\t\tmenuInflater.inflate(R.menu.opt_chapters, menu)\n\t\t\t\tmenu.findItem(R.id.action_search)?.run {\n\t\t\t\t\tsetOnActionExpandListener(this@ChapterPagesMenuProvider)\n\t\t\t\t\t(actionView as? SearchView)?.setupChaptersSearchView()\n\t\t\t\t}\n\t\t\t\tmenu.findItem(R.id.action_search)?.isVisible = viewModel.emptyReason.value == null\n\t\t\t\tmenu.findItem(R.id.action_reversed)?.isChecked = viewModel.isChaptersReversed.value == true\n\t\t\t\tmenu.findItem(R.id.action_grid_view)?.isChecked = viewModel.isChaptersInGridView.value == true\n\t\t\t\tmenu.findItem(R.id.action_downloaded)?.let { menuItem ->\n\t\t\t\t\tmenuItem.isVisible = viewModel.mangaDetails.value?.local != null\n\t\t\t\t\tmenuItem.isChecked = viewModel.isDownloadedOnly.value == true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tTAB_PAGES, TAB_BOOKMARKS -> {\n\t\t\t\tmenuInflater.inflate(R.menu.opt_pages, menu)\n\t\t\t\tmenu.findItem(R.id.action_grid_size)?.run {\n\t\t\t\t\tsetOnActionExpandListener(this@ChapterPagesMenuProvider)\n\t\t\t\t\t(actionView as? Slider)?.setupPagesSizeSlider()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n\t\tR.id.action_reversed -> {\n\t\t\tviewModel.setChaptersReversed(!menuItem.isChecked)\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_grid_view -> {\n\t\t\tviewModel.setChaptersInGridView(!menuItem.isChecked)\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_downloaded -> {\n\t\t\tviewModel.isDownloadedOnly.value = !menuItem.isChecked\n\t\t\ttrue\n\t\t}\n\n\t\telse -> false\n\t}\n\n\toverride fun handleOnBackPressed() {\n\t\texpandedItemRef?.get()?.collapseActionView()\n\t}\n\n\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\texpandedItemRef = WeakReference(item)\n\t\tsheet.expandAndLock()\n\t\tisEnabled = true\n\t\treturn true\n\t}\n\n\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean {\n\t\texpandedItemRef = null\n\t\tisEnabled = false\n\t\t(item.actionView as? SearchView)?.setQuery(\"\", false)\n\t\tviewModel.performChapterSearch(null)\n\t\tsheet.unlock()\n\t\treturn true\n\t}\n\n\toverride fun onQueryTextSubmit(query: String?): Boolean = false\n\n\toverride fun onQueryTextChange(newText: String?): Boolean {\n\t\tviewModel.performChapterSearch(newText)\n\t\treturn true\n\t}\n\n\toverride fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {\n\t\tif (fromUser) {\n\t\t\tsettings.gridSizePages = value.toInt()\n\t\t}\n\t}\n\n\tprivate fun SearchView.setupChaptersSearchView() {\n\t\tsetOnQueryTextListener(this@ChapterPagesMenuProvider)\n\t\tsetIconifiedByDefault(false)\n\t\tqueryHint = context.getString(R.string.search_chapters)\n\t}\n\n\tprivate fun Slider.setupPagesSizeSlider() {\n\t\tvalueFrom = 50f\n\t\tvalueTo = 150f\n\t\tstepSize = 5f\n\t\ttickVisibilityMode = TickVisibilityMode.TICK_VISIBILITY_HIDDEN\n\t\tlabelBehavior = LabelFormatter.LABEL_FLOATING\n\t\tsetLabelFormatter(IntPercentLabelFormatter(context))\n\t\tsetValueRounded(settings.gridSizePages.toFloat())\n\t\taddOnChangeListener(this@ChapterPagesMenuProvider)\n\t}\n\n\tprivate fun getCurrentTab(): Int {\n\t\tvar page = pager.currentItem\n\t\tif (page > 0 && pager.adapter?.itemCount == 2) { // no Pages page\n\t\t\tpage++ // shift\n\t\t}\n\t\treturn page\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesAdapter.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager\n\nimport androidx.fragment.app.Fragment\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport com.google.android.material.tabs.TabLayout\nimport com.google.android.material.tabs.TabLayoutMediator\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.details.ui.pager.bookmarks.BookmarksFragment\nimport org.koitharu.kotatsu.details.ui.pager.chapters.ChaptersFragment\nimport org.koitharu.kotatsu.details.ui.pager.pages.PagesFragment\n\nclass ChaptersPagesAdapter(\n\tfragment: Fragment,\n\tval isPagesTabEnabled: Boolean,\n) : FragmentStateAdapter(fragment),\n\tTabLayoutMediator.TabConfigurationStrategy {\n\n\toverride fun getItemCount(): Int = if (isPagesTabEnabled) 3 else 2\n\n\toverride fun createFragment(position: Int): Fragment = when (position) {\n\t\t0 -> ChaptersFragment()\n\t\t1 -> if (isPagesTabEnabled) PagesFragment() else BookmarksFragment()\n\t\t2 -> BookmarksFragment()\n\t\telse -> throw IllegalArgumentException(\"Invalid position $position\")\n\t}\n\n\toverride fun onConfigureTab(tab: TabLayout.Tab, position: Int) {\n\t\ttab.setIcon(\n\t\t\twhen (position) {\n\t\t\t\t0 -> R.drawable.ic_list\n\t\t\t\t1 -> if (isPagesTabEnabled) R.drawable.ic_grid else R.drawable.ic_bookmark\n\t\t\t\t2 -> R.drawable.ic_bookmark\n\t\t\t\telse -> 0\n\t\t\t},\n\t\t)\n\t\t// tab.setText(\n\t\t// \twhen (position) {\n\t\t// \t\t0 -> R.string.chapters\n\t\t// \t\t1 -> if (isPagesTabEnabled) R.string.pages else R.string.bookmarks\n\t\t// \t\t2 -> R.string.bookmarks\n\t\t// \t\telse -> 0\n\t\t// \t},\n\t\t// )\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport com.google.android.material.tabs.TabLayout\nimport com.google.android.material.tabs.TabLayoutMediator\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_COLLAPSED\nimport org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_DRAGGING\nimport org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_EXPANDED\nimport org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_SETTLING\nimport org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.ui.util.ActionModeListener\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.ext.doOnPageChanged\nimport org.koitharu.kotatsu.core.util.ext.findCurrentPagerFragment\nimport org.koitharu.kotatsu.core.util.ext.menuView\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.recyclerView\nimport org.koitharu.kotatsu.core.util.ext.smoothScrollToTop\nimport org.koitharu.kotatsu.databinding.SheetChaptersPagesBinding\nimport org.koitharu.kotatsu.details.ui.DetailsViewModel\nimport org.koitharu.kotatsu.details.ui.ReadButtonDelegate\nimport org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(),\n\tTabLayout.OnTabSelectedListener,\n\tActionModeListener,\n\tAdaptiveSheetCallback {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate val viewModel by ChaptersPagesViewModel.ActivityVMLazy(this)\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetChaptersPagesBinding {\n\t\treturn SheetChaptersPagesBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: SheetChaptersPagesBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tdisableFitToContents()\n\n\t\tval args = arguments ?: Bundle.EMPTY\n\t\tvar defaultTab = args.getInt(AppRouter.KEY_TAB, settings.defaultDetailsTab)\n\t\tval adapter = ChaptersPagesAdapter(this, settings.isPagesTabEnabled)\n\t\tif (!adapter.isPagesTabEnabled) {\n\t\t\tdefaultTab = (defaultTab - 1).coerceAtLeast(TAB_CHAPTERS)\n\t\t}\n\t\t(viewModel as? DetailsViewModel)?.let { dvm ->\n\t\t\tReadButtonDelegate(binding.splitButtonRead, dvm, router).attach(viewLifecycleOwner)\n\t\t}\n\t\tbinding.pager.offscreenPageLimit = adapter.itemCount\n\t\tbinding.pager.recyclerView?.isNestedScrollingEnabled = false\n\t\tbinding.pager.adapter = adapter\n\t\tbinding.pager.doOnPageChanged(::onPageChanged)\n\t\tTabLayoutMediator(binding.tabs, binding.pager, adapter).attach()\n\t\tbinding.tabs.addOnTabSelectedListener(this)\n\t\tbinding.pager.setCurrentItem(defaultTab, false)\n\t\tbinding.tabs.isVisible = adapter.itemCount > 1\n\n\t\tval menuProvider = ChapterPagesMenuProvider(viewModel, this, binding.pager, settings)\n\t\tonBackPressedDispatcher.addCallback(viewLifecycleOwner, menuProvider)\n\t\tbinding.toolbar.addMenuProvider(menuProvider)\n\n\t\tval menuInvalidator = MenuInvalidator(binding.toolbar)\n\t\tviewModel.isChaptersReversed.observe(viewLifecycleOwner, menuInvalidator)\n\t\tviewModel.isChaptersInGridView.observe(viewLifecycleOwner, menuInvalidator)\n\t\tviewModel.isDownloadedOnly.observe(viewLifecycleOwner, menuInvalidator)\n\n\t\tactionModeDelegate?.addListener(this, viewLifecycleOwner)\n\t\taddSheetCallback(this, viewLifecycleOwner)\n\n\t\tviewModel.newChaptersCount.observe(viewLifecycleOwner, ::onNewChaptersChanged)\n\t\tif (dialog != null) {\n\t\t\tviewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.pager, this))\n\t\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager))\n\t\t\tviewModel.onDownloadStarted.observeEvent(viewLifecycleOwner, DownloadStartedObserver(binding.pager))\n\t\t} else {\n\t\t\tPeekHeightController(arrayOf(binding.headerBar, binding.toolbar)).attach()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat = insets\n\n\toverride fun onStateChanged(sheet: View, newState: Int) {\n        val binding = viewBinding ?: return\n        binding.layoutTouchBlock.isTouchEventsAllowed = dialog != null || newState != STATE_COLLAPSED\n        if (newState == STATE_DRAGGING || newState == STATE_SETTLING) {\n            return\n        }\n\t\tval isActionModeStarted = actionModeDelegate?.isActionModeStarted == true\n\t\tbinding.toolbar.menuView?.isVisible = newState == STATE_EXPANDED && !isActionModeStarted\n\t\tbinding.splitButtonRead.isVisible = newState != STATE_EXPANDED && !isActionModeStarted\n\t\t\t&& viewModel is DetailsViewModel\n\t}\n\n\toverride fun onActionModeStarted(mode: ActionMode) {\n\t\tviewBinding?.toolbar?.menuView?.isVisible = false\n\t\tview?.post(::expandAndLock)\n\t}\n\n\toverride fun onActionModeFinished(mode: ActionMode) {\n\t\tunlock()\n\t\tval state = behavior?.state ?: STATE_EXPANDED\n\t\tviewBinding?.toolbar?.menuView?.isVisible = state != STATE_COLLAPSED\n\t}\n\n\toverride fun onTabSelected(tab: TabLayout.Tab?) = Unit\n\n\toverride fun onTabUnselected(tab: TabLayout.Tab?) = Unit\n\n\toverride fun onTabReselected(tab: TabLayout.Tab?) {\n\t\tval f = childFragmentManager.findCurrentPagerFragment(\n\t\t\tviewBinding?.pager ?: return,\n\t\t) as? RecyclerViewOwner ?: return\n\t\tf.recyclerView?.smoothScrollToTop()\n\t}\n\n\toverride fun expandAndLock() {\n\t\tsuper.expandAndLock()\n\t\tadjustLockState()\n\t}\n\n\toverride fun unlock() {\n\t\tsuper.unlock()\n\t\tadjustLockState()\n\t}\n\n\tprivate fun adjustLockState() {\n\t\tviewBinding?.run {\n\t\t\tpager.isUserInputEnabled = !isLocked\n\t\t\ttabs.visibility = when {\n\t\t\t\t(pager.adapter?.itemCount ?: 0) <= 1 -> View.GONE\n\t\t\t\tisLocked -> View.INVISIBLE\n\t\t\t\telse -> View.VISIBLE\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onPageChanged(position: Int) {\n\t\tviewBinding?.toolbar?.invalidateMenu()\n\t\tsettings.lastDetailsTab = position\n\t}\n\n\tprivate fun onNewChaptersChanged(counter: Int) {\n\t\tval tab = viewBinding?.tabs?.getTabAt(0) ?: return\n\t\tif (counter == 0) {\n\t\t\ttab.removeBadge()\n\t\t} else {\n\t\t\tval badge = tab.orCreateBadge\n\t\t\tbadge.number = counter\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tconst val TAB_CHAPTERS = 0\n\t\tconst val TAB_PAGES = 1\n\t\tconst val TAB_BOOKMARKS = 2\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesViewModel.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager\n\nimport android.app.Activity\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.plus\nimport okio.FileNotFoundException\nimport org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository\nimport org.koitharu.kotatsu.core.model.toChipModel\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.LocaleStringComparator\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.combine\nimport org.koitharu.kotatsu.core.util.ext.requireValue\nimport org.koitharu.kotatsu.core.util.ext.sortedWithSafe\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.domain.DetailsInteractor\nimport org.koitharu.kotatsu.details.ui.DetailsActivity\nimport org.koitharu.kotatsu.details.ui.DetailsViewModel\nimport org.koitharu.kotatsu.details.ui.mapChapters\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.download.ui.worker.DownloadTask\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.reader.ui.ReaderActivity\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.ReaderViewModel\n\nabstract class ChaptersPagesViewModel(\n\t@JvmField protected val settings: AppSettings,\n\t@JvmField protected val interactor: DetailsInteractor,\n\tprivate val bookmarksRepository: BookmarksRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val downloadScheduler: DownloadWorker.Scheduler,\n\tprivate val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,\n\tprivate val localStorageChanges: SharedFlow<LocalManga?>,\n) : BaseViewModel() {\n\n\tval mangaDetails = MutableStateFlow<MangaDetails?>(null)\n\tval readingState = MutableStateFlow<ReaderState?>(null)\n\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\tval onDownloadStarted = MutableEventFlow<Unit>()\n\tval onMangaRemoved = MutableEventFlow<Manga>()\n\n\tprivate val chaptersQuery = MutableStateFlow(\"\")\n\tval selectedBranch = MutableStateFlow<String?>(null)\n\n\tval manga = mangaDetails.map { x -> x?.toManga() }\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)\n\n\tval coverUrl = mangaDetails.map { x -> x?.coverUrl }\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)\n\n\tval isChaptersReversed = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_REVERSE_CHAPTERS,\n\t\tvalueProducer = { isChaptersReverse },\n\t)\n\n\tval isChaptersInGridView = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_GRID_VIEW_CHAPTERS,\n\t\tvalueProducer = { isChaptersGridView },\n\t)\n\n\tval isDownloadedOnly = MutableStateFlow(false)\n\n\tval newChaptersCount = mangaDetails.flatMapLatest { d ->\n\t\tif (d?.isLocal == false) {\n\t\t\tinteractor.observeNewChapters(d.id)\n\t\t} else {\n\t\t\tflowOf(0)\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)\n\n\tval emptyReason: StateFlow<EmptyMangaReason?> = combine(\n\t\tmangaDetails,\n\t\tisLoading,\n\t\tonError.onStart { emit(null) },\n\t) { details, loading, error ->\n\t\twhen {\n\t\t\tdetails == null || loading -> null\n\t\t\tdetails.chapters.isNotEmpty() -> null\n\t\t\tdetails.toManga().state == MangaState.RESTRICTED -> EmptyMangaReason.RESTRICTED\n\t\t\terror != null -> EmptyMangaReason.LOADING_ERROR\n\t\t\telse -> EmptyMangaReason.NO_CHAPTERS\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(), null)\n\n\tval bookmarks = mangaDetails.flatMapLatest {\n\t\tif (it != null) {\n\t\t\tbookmarksRepository.observeBookmarks(it.toManga()).withErrorHandling()\n\t\t} else {\n\t\t\tflowOf(emptyList())\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())\n\n\tval chapters = combine(\n\t\tcombine(\n\t\t\tmangaDetails,\n\t\t\treadingState.map { it?.chapterId ?: 0L }.distinctUntilChanged(),\n\t\t\tselectedBranch,\n\t\t\tnewChaptersCount,\n\t\t\tbookmarks,\n\t\t\tisChaptersInGridView,\n\t\t\tisDownloadedOnly,\n\t\t) { manga, currentChapterId, branch, news, bookmarks, grid, downloadedOnly ->\n\t\t\tmanga?.mapChapters(\n\t\t\t\tcurrentChapterId = currentChapterId,\n\t\t\t\tnewCount = news,\n\t\t\t\tbranch = branch,\n\t\t\t\tbookmarks = bookmarks,\n\t\t\t\tisGrid = grid,\n\t\t\t\tisDownloadedOnly = downloadedOnly,\n\t\t\t).orEmpty()\n\t\t},\n\t\tisChaptersReversed,\n\t\tchaptersQuery,\n\t) { list, reversed, query ->\n\t\t(if (reversed) list.asReversed() else list).filterSearch(query)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tval quickFilter = combine(\n\t\tmangaDetails,\n\t\tselectedBranch,\n\t) { details, branch ->\n\t\tval branches = details?.chapters?.toList()?.sortedWithSafe(\n\t\t\tcompareBy(LocaleStringComparator()) { it.first },\n\t\t).orEmpty()\n\t\tif (branches.size > 1) {\n\t\t\tbranches.map {\n\t\t\t\tval option = ListFilterOption.Branch(titleText = it.first, chaptersCount = it.second.size)\n\t\t\t\toption.toChipModel(isChecked = it.first == branch)\n\t\t\t}\n\t\t} else {\n\t\t\temptyList()\n\t\t}\n\t}\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tlocalStorageChanges\n\t\t\t\t.collect { onDownloadComplete(it) }\n\t\t}\n\t}\n\n\tfun setChaptersReversed(newValue: Boolean) {\n\t\tsettings.isChaptersReverse = newValue\n\t}\n\n\tfun setChaptersInGridView(newValue: Boolean) {\n\t\tsettings.isChaptersGridView = newValue\n\t}\n\n\tfun setSelectedBranch(branch: String?) {\n\t\tselectedBranch.value = branch\n\t}\n\n\tfun performChapterSearch(query: String?) {\n\t\tchaptersQuery.value = query?.trim().orEmpty()\n\t}\n\n\tfun getMangaOrNull(): Manga? = mangaDetails.value?.toManga()\n\n\tfun requireManga() = mangaDetails.requireValue().toManga()\n\n\tfun markChapterAsCurrent(chapterId: Long) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval manga = mangaDetails.requireValue()\n\t\t\tval chapters = checkNotNull(manga.chapters[selectedBranch.value])\n\t\t\tval chapterIndex = chapters.indexOfFirst { it.id == chapterId }\n\t\t\tcheck(chapterIndex in chapters.indices) { \"Chapter not found\" }\n\t\t\tval percent = chapterIndex / chapters.size.toFloat()\n\t\t\thistoryRepository.addOrUpdate(\n\t\t\t\tmanga = manga.toManga(),\n\t\t\t\tchapterId = chapterId,\n\t\t\t\tpage = 0,\n\t\t\t\tscroll = 0,\n\t\t\t\tpercent = percent,\n\t\t\t\tforce = true,\n\t\t\t)\n\t\t}\n\t}\n\n\tfun download(chaptersIds: Set<Long>?, allowMeteredNetwork: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval manga = requireManga()\n\t\t\tval task = DownloadTask(\n\t\t\t\tmangaId = manga.id,\n\t\t\t\tisPaused = false,\n\t\t\t\tisSilent = false,\n\t\t\t\tchaptersIds = chaptersIds?.toLongArray(),\n\t\t\t\tdestination = null,\n\t\t\t\tformat = null,\n\t\t\t\tallowMeteredNetwork = allowMeteredNetwork,\n\t\t\t)\n\t\t\tdownloadScheduler.schedule(setOf(manga to task))\n\t\t\tonDownloadStarted.call(Unit)\n\t\t}\n\t}\n\n\tfun deleteLocal() {\n\t\tval m = mangaDetails.value?.local?.manga\n\t\tif (m == null) {\n\t\t\terrorEvent.call(FileNotFoundException())\n\t\t\treturn\n\t\t}\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tdeleteLocalMangaUseCase(m)\n\t\t\tonMangaRemoved.call(m)\n\t\t}\n\t}\n\n\tprivate fun List<ChapterListItem>.filterSearch(query: String): List<ChapterListItem> {\n\t\tif (query.isEmpty() || this.isEmpty()) {\n\t\t\treturn this\n\t\t}\n\t\treturn filter { it.contains(query) }\n\t}\n\n\tprivate suspend fun onDownloadComplete(downloadedManga: LocalManga?) {\n\t\tdownloadedManga ?: return\n\t\tmangaDetails.update {\n\t\t\tinteractor.updateLocal(it, downloadedManga)\n\t\t}\n\t}\n\n\tclass ActivityVMLazy(\n\t\tprivate val fragment: Fragment,\n\t) : Lazy<ChaptersPagesViewModel> {\n\t\tprivate var cached: ChaptersPagesViewModel? = null\n\n\t\toverride val value: ChaptersPagesViewModel\n\t\t\tget() {\n\t\t\t\tval viewModel = cached\n\t\t\t\treturn if (viewModel == null) {\n\t\t\t\t\tval activity = fragment.requireActivity()\n\t\t\t\t\tval vmClass = getViewModelClass(activity)\n\t\t\t\t\tViewModelProvider.create(\n\t\t\t\t\t\tstore = activity.viewModelStore,\n\t\t\t\t\t\tfactory = activity.defaultViewModelProviderFactory,\n\t\t\t\t\t\textras = activity.defaultViewModelCreationExtras,\n\t\t\t\t\t)[vmClass].also { cached = it }\n\t\t\t\t} else {\n\t\t\t\t\tviewModel\n\t\t\t\t}\n\t\t\t}\n\n\t\toverride fun isInitialized(): Boolean = cached != null\n\n\t\tprivate fun getViewModelClass(activity: Activity) = when (activity) {\n\t\t\tis ReaderActivity -> ReaderViewModel::class.java\n\t\t\tis DetailsActivity -> DetailsViewModel::class.java\n\t\t\telse -> error(\"Wrong activity ${activity.javaClass.simpleName} for ${ChaptersPagesViewModel::class.java.simpleName}\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/EmptyMangaReason.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\n\nenum class EmptyMangaReason(\n\t@StringRes val msgResId: Int,\n) {\n\n\tNO_CHAPTERS(R.string.no_chapters_in_manga),\n\tLOADING_ERROR(R.string.chapters_load_failed),\n\tRESTRICTED(R.string.manga_restricted_description),\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/PeekHeightController.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager\n\nimport android.view.View\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.OnApplyWindowInsetsListener\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.ancestors\nimport com.google.android.material.bottomsheet.BottomSheetBehavior\n\nclass PeekHeightController(\n\tprivate val views: Array<View>,\n) : View.OnLayoutChangeListener, OnApplyWindowInsetsListener {\n\n\tprivate var behavior: BottomSheetBehavior<*>? = null\n\n\tfun attach() {\n\t\tbehavior = findBehavior() ?: return\n\t\tviews.forEach { v ->\n\t\t\tv.addOnLayoutChangeListener(this)\n\t\t}\n\t\tViewCompat.setOnApplyWindowInsetsListener(views.first(), this)\n\t}\n\n\toverride fun onLayoutChange(\n\t\tv: View?,\n\t\tleft: Int,\n\t\ttop: Int,\n\t\tright: Int,\n\t\tbottom: Int,\n\t\toldLeft: Int,\n\t\toldTop: Int,\n\t\toldRight: Int,\n\t\toldBottom: Int\n\t) {\n\t\tif (top != oldTop || bottom != oldBottom) {\n\t\t\tupdatePeekHeight()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tupdatePeekHeight()\n\t\treturn insets\n\t}\n\n\tprivate fun updatePeekHeight() {\n\t\tbehavior?.peekHeight = views.sumOf { it.height } + getBottomInset()\n\t}\n\n\tprivate fun getBottomInset(): Int = ViewCompat.getRootWindowInsets(views.first())\n\t\t?.getInsets(WindowInsetsCompat.Type.navigationBars())\n\t\t?.bottom ?: 0\n\n\tprivate fun findBehavior(): BottomSheetBehavior<*>? {\n\t\treturn views.first().ancestors.firstNotNullOfOrNull {\n\t\t\t((it as? View)?.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior as? BottomSheetBehavior<*>\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.bookmarks\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.bookmarks.ui.BookmarksSelectionDecoration\nimport org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.nav.dismissParentDialog\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate\nimport org.koitharu.kotatsu.core.util.ext.findParentCallback\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.FragmentMangaBookmarksBinding\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel\nimport org.koitharu.kotatsu.list.ui.GridSpanResolver\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),\n\tOnListItemClickListener<Bookmark>,\n\tRecyclerViewOwner,\n\tListSelectionController.Callback {\n\n\tprivate val activityViewModel by ChaptersPagesViewModel.ActivityVMLazy(this)\n\tprivate val viewModel by viewModels<BookmarksViewModel>()\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\t@Inject\n\tlateinit var pageSaveHelperFactory: PageSaveHelper.Factory\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerView\n\n\tprivate lateinit var pageSaveHelper: PageSaveHelper\n\tprivate var bookmarksAdapter: BookmarksAdapter? = null\n\tprivate var spanResolver: GridSpanResolver? = null\n\tprivate var selectionController: ListSelectionController? = null\n\n\tprivate val spanSizeLookup = SpanSizeLookup()\n\tprivate val listCommitCallback = Runnable {\n\t\tspanSizeLookup.invalidateCache()\n\t}\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tpageSaveHelper = pageSaveHelperFactory.create(this)\n\t\tactivityViewModel.mangaDetails.observe(this, viewModel)\n\t}\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentMangaBookmarksBinding {\n\t\treturn FragmentMangaBookmarksBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: FragmentMangaBookmarksBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tspanResolver = GridSpanResolver(binding.root.resources)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = checkNotNull(findAppCompatDelegate()),\n\t\t\tdecoration = BookmarksSelectionDecoration(binding.root.context),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = this,\n\t\t)\n\t\tbookmarksAdapter = BookmarksAdapter(\n\t\t\tclickListener = this@BookmarksFragment,\n\t\t\theaderClickListener = null,\n\t\t)\n\t\tviewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) // before rv initialization\n\t\twith(binding.recyclerView) {\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\tsetHasFixedSize(true)\n\t\t\tPagerNestedScrollHelper(this).bind(viewLifecycleOwner)\n\t\t\tadapter = bookmarksAdapter\n\t\t\taddOnLayoutChangeListener(spanResolver)\n\t\t\t(layoutManager as GridLayoutManager).let {\n\t\t\t\tit.spanSizeLookup = spanSizeLookup\n\t\t\t\tit.spanCount = checkNotNull(spanResolver).spanCount\n\t\t\t}\n\t\t\tselectionController?.attachToRecyclerView(this)\n\t\t}\n\t\tviewModel.content.observe(viewLifecycleOwner) { bookmarksAdapter?.setItems(it, listCommitCallback) }\n\n\t\tviewModel.onError.observeEvent(\n\t\t\tviewLifecycleOwner,\n\t\t\tSnackbarErrorObserver(binding.recyclerView, this),\n\t\t)\n\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onDestroyView() {\n\t\tspanResolver = null\n\t\tbookmarksAdapter = null\n\t\tselectionController = null\n\t\tspanSizeLookup.invalidateCache()\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onItemClick(item: Bookmark, view: View) {\n\t\tif (selectionController?.onItemClick(item.pageId) == true) {\n\t\t\treturn\n\t\t}\n\t\tval listener = findParentCallback(ReaderNavigationCallback::class.java)\n\t\tif (listener != null && listener.onBookmarkSelected(item)) {\n\t\t\tdismissParentDialog()\n\t\t} else {\n\t\t\tval intent = ReaderIntent.Builder(view.context)\n\t\t\t\t.manga(activityViewModel.getMangaOrNull() ?: return)\n\t\t\t\t.bookmark(item)\n\t\t\t\t.incognito()\n\t\t\t\t.build()\n\t\t\trouter.openReader(intent)\n\t\t}\n\t}\n\n\toverride fun onItemLongClick(item: Bookmark, view: View): Boolean {\n\t\treturn selectionController?.onItemLongClick(view, item.pageId) == true\n\t}\n\n\toverride fun onItemContextClick(item: Bookmark, view: View): Boolean {\n\t\treturn selectionController?.onItemContextClick(view, item.pageId) == true\n\t}\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\trequireViewBinding().recyclerView.invalidateItemDecorations()\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu,\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_bookmarks, menu)\n\t\treturn true\n\t}\n\n\toverride fun onActionItemClicked(\n\t\tcontroller: ListSelectionController,\n\t\tmode: ActionMode?,\n\t\titem: MenuItem,\n\t): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_remove -> {\n\t\t\t\tval ids = selectionController?.snapshot() ?: return false\n\t\t\t\tviewModel.removeBookmarks(ids)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_save -> {\n\t\t\t\tviewModel.savePages(pageSaveHelper, selectionController?.snapshot() ?: return false)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\tprivate fun onGridScaleChanged(scale: Float) {\n\t\tspanSizeLookup.invalidateCache()\n\t\tspanResolver?.setGridSize(scale, requireViewBinding().recyclerView)\n\t}\n\n\tprivate inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() {\n\n\t\tinit {\n\t\t\tisSpanIndexCacheEnabled = true\n\t\t\tisSpanGroupIndexCacheEnabled = true\n\t\t}\n\n\t\toverride fun getSpanSize(position: Int): Int {\n\t\t\tval total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1\n\t\t\treturn when (bookmarksAdapter?.getItemViewType(position)) {\n\t\t\t\tListItemType.PAGE_THUMB.ordinal -> 1\n\t\t\t\telse -> total\n\t\t\t}\n\t\t}\n\n\t\tfun invalidateCache() {\n\t\t\tinvalidateSpanGroupIndexCache()\n\t\t\tinvalidateSpanIndexCache()\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksViewModel.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.bookmarks\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.requireValue\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport javax.inject.Inject\n\n@HiltViewModel\nclass BookmarksViewModel @Inject constructor(\n\tprivate val bookmarksRepository: BookmarksRepository,\n\tsettings: AppSettings,\n) : BaseViewModel(), FlowCollector<MangaDetails?> {\n\n\tprivate val manga = MutableStateFlow<Manga?>(null)\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\n\tval gridScale = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_GRID_SIZE_PAGES,\n\t\tvalueProducer = { gridSizePages / 100f },\n\t)\n\n\tval content: StateFlow<List<ListModel>> = manga.filterNotNull().flatMapLatest { m ->\n\t\tbookmarksRepository.observeBookmarks(m)\n\t\t\t.map { mapList(m, it) }\n\t}.withErrorHandling()\n\t\t.filterNotNull()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, listOf(LoadingState))\n\n\toverride suspend fun emit(value: MangaDetails?) {\n\t\tmanga.value = value?.toManga()\n\t}\n\n\tfun removeBookmarks(ids: Set<Long>) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval handle = bookmarksRepository.removeBookmarks(ids)\n\t\t\tonActionDone.call(ReversibleAction(R.string.bookmarks_removed, handle))\n\t\t}\n\t}\n\n\tfun savePages(pageSaveHelper: PageSaveHelper, ids: Set<Long>) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval m = manga.requireValue()\n\t\t\tval tasks = content.value.mapNotNull {\n\t\t\t\tif (it !is Bookmark || it.pageId !in ids) return@mapNotNull null\n\t\t\t\tPageSaveHelper.Task(\n\t\t\t\t\tmanga = m,\n\t\t\t\t\tchapterId = it.chapterId,\n\t\t\t\t\tpageNumber = it.page + 1,\n\t\t\t\t\tpage = it.toMangaPage(),\n\t\t\t\t)\n\t\t\t}\n\t\t\tval dest = pageSaveHelper.save(tasks)\n\t\t\tval msg = if (dest.size == 1) R.string.page_saved else R.string.pages_saved\n\t\t\tonActionDone.call(ReversibleAction(msg, null))\n\t\t}\n\t}\n\n\tprivate fun mapList(manga: Manga, bookmarks: List<Bookmark>): List<ListModel>? {\n\t\tval chapters = manga.chapters ?: return null\n\t\tval bookmarksMap = bookmarks.groupBy { it.chapterId }\n\t\tval result = ArrayList<ListModel>(bookmarks.size + bookmarksMap.size)\n\t\tfor (chapter in chapters) {\n\t\t\tval b = bookmarksMap[chapter.id]\n\t\t\tif (b.isNullOrEmpty()) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult += ListHeader(chapter)\n\t\t\tresult.addAll(b)\n\t\t}\n\t\tif (result.isEmpty()) {\n\t\t\tresult.add(\n\t\t\t\tEmptyState(\n\t\t\t\t\ticon = 0,\n\t\t\t\t\ttextPrimary = R.string.no_bookmarks_yet,\n\t\t\t\t\ttextSecondary = R.string.no_bookmarks_summary,\n\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChapterGridSpanHelper.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.chapters\n\nimport android.view.View\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport kotlin.math.roundToInt\n\nclass ChapterGridSpanHelper private constructor() : View.OnLayoutChangeListener {\n\n\toverride fun onLayoutChange(\n\t\tv: View?,\n\t\tleft: Int,\n\t\ttop: Int,\n\t\tright: Int,\n\t\tbottom: Int,\n\t\toldLeft: Int,\n\t\toldTop: Int,\n\t\toldRight: Int,\n\t\toldBottom: Int\n\t) {\n\t\tval rv = v as? RecyclerView ?: return\n\t\tif (rv.width > 0) {\n\t\t\tapply(rv)\n\t\t}\n\t}\n\n\tprivate fun apply(rv: RecyclerView) {\n\t\t(rv.layoutManager as? GridLayoutManager)?.spanCount = getSpanCount(rv)\n\t}\n\n\tclass SpanSizeLookup(\n\t\tprivate val recyclerView: RecyclerView\n\t) : GridLayoutManager.SpanSizeLookup() {\n\n\t\toverride fun getSpanSize(position: Int): Int {\n\t\t\treturn when (recyclerView.adapter?.getItemViewType(position)) {\n\t\t\t\tListItemType.CHAPTER_LIST.ordinal, // for smooth transition\n\t\t\t\tListItemType.HEADER.ordinal -> getTotalSpans()\n\n\t\t\t\telse -> 1\n\t\t\t}\n\t\t}\n\n\t\tprivate fun getTotalSpans() = (recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: 1\n\t}\n\n\tcompanion object {\n\n\t\tfun attach(view: RecyclerView) {\n\t\t\tval helper = ChapterGridSpanHelper()\n\t\t\tview.addOnLayoutChangeListener(helper)\n\t\t\thelper.apply(view)\n\t\t}\n\n\t\tfun getSpanCount(view: RecyclerView): Int {\n\t\t\tval cellWidth = view.resources.getDimension(R.dimen.chapter_grid_width)\n\t\t\tval estimatedCount = (view.width / cellWidth).roundToInt()\n\t\t\treturn estimatedCount.coerceAtLeast(2)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersFragment.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.chapters\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePadding\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.chip.Chip\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.nav.dismissParentDialog\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback\nimport org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate\nimport org.koitharu.kotatsu.core.util.ext.findParentCallback\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.FragmentChaptersBinding\nimport org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter\nimport org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration\nimport org.koitharu.kotatsu.details.ui.model.ChapterListItem\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel\nimport org.koitharu.kotatsu.details.ui.withVolumeHeaders\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport kotlin.math.roundToInt\n\n@AndroidEntryPoint\nclass ChaptersFragment :\n\tBaseFragment<FragmentChaptersBinding>(),\n\tOnListItemClickListener<ChapterListItem>,\n\tRecyclerViewOwner,\n\tChipsView.OnChipClickListener {\n\n\tprivate val viewModel by ChaptersPagesViewModel.ActivityVMLazy(this)\n\n\tprivate var chaptersAdapter: ChaptersAdapter? = null\n\tprivate var selectionController: ListSelectionController? = null\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerViewChapters\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentChaptersBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: FragmentChaptersBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tchaptersAdapter = ChaptersAdapter(this)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = checkNotNull(findAppCompatDelegate()),\n\t\t\tdecoration = ChaptersSelectionDecoration(binding.root.context),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = ChaptersSelectionCallback(viewModel, router, binding.recyclerViewChapters),\n\t\t)\n\t\tviewModel.isChaptersInGridView.observe(viewLifecycleOwner) { chaptersInGridView ->\n\t\t\tbinding.recyclerViewChapters.layoutManager = if (chaptersInGridView) {\n\t\t\t\tGridLayoutManager(context, ChapterGridSpanHelper.getSpanCount(binding.recyclerViewChapters)).apply {\n\t\t\t\t\tspanSizeLookup = ChapterGridSpanHelper.SpanSizeLookup(binding.recyclerViewChapters)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tLinearLayoutManager(context)\n\t\t\t}\n\t\t}\n\t\twith(binding.recyclerViewChapters) {\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, true))\n\t\t\tcheckNotNull(selectionController).attachToRecyclerView(this)\n\t\t\tsetHasFixedSize(true)\n\t\t\tPagerNestedScrollHelper(this).bind(viewLifecycleOwner)\n\t\t\tadapter = chaptersAdapter\n\t\t\tChapterGridSpanHelper.attach(this)\n\t\t}\n\t\tbinding.chipsFilter.onChipClickListener = this\n\t\tviewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)\n\t\tviewModel.chapters\n\t\t\t.map { it.withVolumeHeaders(requireContext()) }\n\t\t\t.flowOn(Dispatchers.Default)\n\t\t\t.observe(viewLifecycleOwner, this::onChaptersChanged)\n\t\tviewModel.quickFilter.observe(viewLifecycleOwner, this::onFilterChanged)\n\t\tviewModel.emptyReason.observe(viewLifecycleOwner) {\n\t\t\tbinding.textViewHolder.setTextAndVisible(it?.msgResId ?: 0)\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\tchaptersAdapter = null\n\t\tselectionController = null\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onItemClick(item: ChapterListItem, view: View) {\n\t\tif (selectionController?.onItemClick(item.chapter.id) == true) {\n\t\t\treturn\n\t\t}\n\t\tval listener = findParentCallback(ReaderNavigationCallback::class.java)\n\t\tif (listener != null && listener.onChapterSelected(item.chapter)) {\n\t\t\tdismissParentDialog()\n\t\t} else {\n\t\t\trouter.openReader(\n\t\t\t\tReaderIntent.Builder(view.context)\n\t\t\t\t\t.manga(viewModel.getMangaOrNull() ?: return)\n\t\t\t\t\t.state(ReaderState(item.chapter.id, 0, 0))\n\t\t\t\t\t.build(),\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun onItemLongClick(item: ChapterListItem, view: View): Boolean {\n\t\treturn selectionController?.onItemLongClick(view, item.chapter.id) == true\n\t}\n\n\toverride fun onItemContextClick(item: ChapterListItem, view: View): Boolean {\n\t\treturn selectionController?.onItemContextClick(view, item.chapter.id) == true\n\t}\n\n\toverride fun onChipClick(chip: Chip, data: Any?) {\n\t\tif (data !is ListFilterOption.Branch) return\n\t\tviewModel.setSelectedBranch(data.titleText)\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tviewBinding?.run {\n\t\t\tval bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\t\trecyclerViewChapters.updatePadding(\n\t\t\t\tleft = bars.left,\n\t\t\t\tright = bars.right,\n\t\t\t\tbottom = bars.bottom,\n\t\t\t)\n\t\t\tchipsFilter.updatePadding(\n\t\t\t\tleft = bars.left,\n\t\t\t\tright = bars.right,\n\t\t\t)\n\t\t}\n\t\treturn WindowInsetsCompat.CONSUMED\n\t}\n\n\tprivate fun onChaptersChanged(list: List<ListModel>) {\n\t\tval adapter = chaptersAdapter ?: return\n\t\tif (adapter.itemCount == 0) {\n\t\t\tval position = list.indexOfFirst { it is ChapterListItem && it.isCurrent } - 1\n\t\t\tif (position > 0) {\n\t\t\t\tval offset = (resources.getDimensionPixelSize(R.dimen.chapter_list_item_height) * 0.6).roundToInt()\n\t\t\t\tadapter.setItems(\n\t\t\t\t\tlist,\n\t\t\t\t\tRecyclerViewScrollCallback(requireViewBinding().recyclerViewChapters, position, offset),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tadapter.items = list\n\t\t\t}\n\t\t} else {\n\t\t\tadapter.items = list\n\t\t}\n\t}\n\n\tprivate fun onFilterChanged(list: List<ChipsView.ChipModel>) {\n\t\tviewBinding?.chipsFilter?.run {\n\t\t\tsetChips(list)\n\t\t\tisGone = list.isEmpty()\n\t\t}\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\trequireViewBinding().progressBar.isVisible = isLoading\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/chapters/ChaptersSelectionCallback.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.chapters\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.widget.Toast\nimport androidx.appcompat.view.ActionMode\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.list.BaseListSelectionCallback\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toCollection\nimport org.koitharu.kotatsu.core.util.ext.toSet\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel\nimport org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService\n\nclass ChaptersSelectionCallback(\n\tprivate val viewModel: ChaptersPagesViewModel,\n\tprivate val router: AppRouter,\n\trecyclerView: RecyclerView,\n) : BaseListSelectionCallback(recyclerView) {\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_chapters, menu)\n\t\treturn true\n\t}\n\n\toverride fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {\n\t\tval selectedIds = controller.peekCheckedIds()\n\t\tval allItems = viewModel.chapters.value\n\t\tval items = allItems.withIndex().filter { it.value.chapter.id in selectedIds }\n\t\tvar canSave = true\n\t\tvar canDelete = true\n\t\titems.forEach { (_, x) ->\n\t\t\tval isLocal = x.isDownloaded || x.chapter.source == LocalMangaSource\n\t\t\tif (isLocal) canSave = false else canDelete = false\n\t\t}\n\t\tmenu.findItem(R.id.action_save).isVisible = canSave\n\t\tmenu.findItem(R.id.action_delete).isVisible = canDelete\n\t\tmenu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size\n\t\tmenu.findItem(R.id.action_mark_current).isVisible = items.size == 1\n\t\tmode?.title = items.size.toString()\n\t\tvar hasGap = false\n\t\tfor (i in 0 until items.size - 1) {\n\t\t\tif (items[i].index + 1 != items[i + 1].index) {\n\t\t\t\thasGap = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tmenu.findItem(R.id.action_select_range).isVisible = hasGap\n\t\treturn true\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_save -> {\n\t\t\t\tval snapshot = controller.snapshot()\n\t\t\t\tmode?.finish()\n\t\t\t\tif (snapshot.isNotEmpty()) {\n\t\t\t\t\trouter.askForDownloadOverMeteredNetwork {\n\t\t\t\t\t\tviewModel.download(snapshot, it)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_delete -> {\n\t\t\t\tval ids = controller.peekCheckedIds()\n\t\t\t\tval manga = viewModel.getMangaOrNull()\n\t\t\t\twhen {\n\t\t\t\t\tids.isEmpty() || manga == null -> Unit\n\t\t\t\t\tids.size == manga.chapters?.size -> viewModel.deleteLocal()\n\t\t\t\t\telse -> {\n\t\t\t\t\t\tLocalChaptersRemoveService.start(recyclerView.context, manga, ids.toSet())\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tSnackbar.make(\n\t\t\t\t\t\t\t\trecyclerView,\n\t\t\t\t\t\t\t\tR.string.chapters_will_removed_background,\n\t\t\t\t\t\t\t\tSnackbar.LENGTH_LONG,\n\t\t\t\t\t\t\t).show()\n\t\t\t\t\t\t} catch (e: IllegalArgumentException) {\n\t\t\t\t\t\t\te.printStackTraceDebug()\n\t\t\t\t\t\t\tToast.makeText(\n\t\t\t\t\t\t\t\trecyclerView.context,\n\t\t\t\t\t\t\t\tR.string.chapters_will_removed_background,\n\t\t\t\t\t\t\t\tToast.LENGTH_SHORT,\n\t\t\t\t\t\t\t).show()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_select_range -> {\n\t\t\t\tval items = viewModel.chapters.value\n\t\t\t\tval ids = controller.peekCheckedIds().toCollection(HashSet())\n\t\t\t\tval buffer = HashSet<Long>()\n\t\t\t\tvar isAdding = false\n\t\t\t\tfor (x in items) {\n\t\t\t\t\tif (x.chapter.id in ids) {\n\t\t\t\t\t\tisAdding = true\n\t\t\t\t\t\tif (buffer.isNotEmpty()) {\n\t\t\t\t\t\t\tids.addAll(buffer)\n\t\t\t\t\t\t\tbuffer.clear()\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (isAdding) {\n\t\t\t\t\t\tbuffer.add(x.chapter.id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontroller.addAll(ids)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_select_all -> {\n\t\t\t\tval ids = viewModel.chapters.value.map {\n\t\t\t\t\tit.chapter.id\n\t\t\t\t}\n\t\t\t\tcontroller.addAll(ids)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_mark_current -> {\n\t\t\t\tval ids = controller.peekCheckedIds()\n\t\t\t\tif (ids.size == 1) {\n\t\t\t\t\tviewModel.markChapterAsCurrent(ids.first())\n\t\t\t\t} else {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport androidx.core.net.toUri\nimport coil3.ImageLoader\nimport coil3.decode.DataSource\nimport coil3.decode.ImageSource\nimport coil3.fetch.FetchResult\nimport coil3.fetch.Fetcher\nimport coil3.fetch.SourceFetchResult\nimport coil3.network.HttpException\nimport coil3.network.NetworkHeaders\nimport coil3.network.NetworkResponse\nimport coil3.network.NetworkResponseBody\nimport coil3.request.Options\nimport okhttp3.Headers\nimport okhttp3.OkHttpClient\nimport okhttp3.Response\nimport okio.FileSystem\nimport okio.Path.Companion.toOkioPath\nimport org.koitharu.kotatsu.core.network.MangaHttpClient\nimport org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.fetch\nimport org.koitharu.kotatsu.core.util.ext.isNetworkUri\nimport org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull\nimport org.koitharu.kotatsu.local.data.LocalStorageCache\nimport org.koitharu.kotatsu.local.data.PageCache\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.util.mimeType\nimport org.koitharu.kotatsu.parsers.util.requireBody\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport javax.inject.Inject\n\nclass MangaPageFetcher(\n\tprivate val okHttpClient: OkHttpClient,\n\tprivate val pagesCache: LocalStorageCache,\n\tprivate val options: Options,\n\tprivate val page: MangaPage,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val imageProxyInterceptor: ImageProxyInterceptor,\n\tprivate val imageLoader: ImageLoader,\n) : Fetcher {\n\n\toverride suspend fun fetch(): FetchResult? {\n\t\tif (!page.preview.isNullOrEmpty()) {\n\t\t\trunCatchingCancellable {\n\t\t\t\timageLoader.fetch(checkNotNull(page.preview), options)\n\t\t\t}.onSuccess {\n\t\t\t\treturn it\n\t\t\t}\n\t\t}\n\t\tval repo = mangaRepositoryFactory.create(page.source)\n\t\tval pageUrl = repo.getPageUrl(page)\n\t\tif (options.diskCachePolicy.readEnabled) {\n\t\t\tpagesCache[pageUrl]?.let { file ->\n\t\t\t\treturn SourceFetchResult(\n\t\t\t\t\tsource = ImageSource(file.toOkioPath(), options.fileSystem),\n\t\t\t\t\tmimeType = MimeTypes.getMimeTypeFromExtension(file.name)?.toString(),\n\t\t\t\t\tdataSource = DataSource.DISK,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn loadPage(pageUrl)\n\t}\n\n\tprivate suspend fun loadPage(pageUrl: String): FetchResult? = if (pageUrl.toUri().isNetworkUri()) {\n\t\tfetchPage(pageUrl)\n\t} else {\n\t\timageLoader.fetch(pageUrl, options)\n\t}\n\n\tprivate suspend fun fetchPage(pageUrl: String): FetchResult {\n\t\tval request = PageLoader.createPageRequest(pageUrl, page.source)\n\t\treturn imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->\n\t\t\tif (!response.isSuccessful) {\n\t\t\t\tthrow HttpException(response.toNetworkResponse())\n\t\t\t}\n\t\t\tval mimeType = response.mimeType?.toMimeTypeOrNull()\n\t\t\tval file = response.requireBody().use {\n\t\t\t\tpagesCache.set(pageUrl, it.source(), mimeType)\n\t\t\t}\n\t\t\tSourceFetchResult(\n\t\t\t\tsource = ImageSource(file.toOkioPath(), FileSystem.SYSTEM),\n\t\t\t\tmimeType = mimeType?.toString(),\n\t\t\t\tdataSource = DataSource.NETWORK,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun Response.toNetworkResponse() = NetworkResponse(\n\t\tcode = code,\n\t\trequestMillis = sentRequestAtMillis,\n\t\tresponseMillis = receivedResponseAtMillis,\n\t\theaders = headers.toNetworkHeaders(),\n\t\tbody = body?.source()?.let(::NetworkResponseBody),\n\t\tdelegate = this,\n\t)\n\n\tprivate fun Headers.toNetworkHeaders(): NetworkHeaders {\n\t\tval headers = NetworkHeaders.Builder()\n\t\tfor ((key, values) in this) {\n\t\t\theaders.add(key, values)\n\t\t}\n\t\treturn headers.build()\n\t}\n\n\tclass Factory @Inject constructor(\n\t\t@MangaHttpClient private val okHttpClient: OkHttpClient,\n\t\t@PageCache private val pagesCache: LocalStorageCache,\n\t\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\t\tprivate val imageProxyInterceptor: ImageProxyInterceptor,\n\t) : Fetcher.Factory<MangaPage> {\n\n\t\toverride fun create(data: MangaPage, options: Options, imageLoader: ImageLoader) = MangaPageFetcher(\n\t\t\tokHttpClient = okHttpClient,\n\t\t\tpagesCache = pagesCache,\n\t\t\toptions = options,\n\t\t\tpage = data,\n\t\t\tmangaRepositoryFactory = mangaRepositoryFactory,\n\t\t\timageProxyInterceptor = imageProxyInterceptor,\n\t\t\timageLoader = imageLoader,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageKeyer.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport coil3.key.Keyer\nimport coil3.request.Options\nimport org.koitharu.kotatsu.parsers.model.MangaPage\n\nclass MangaPageKeyer : Keyer<MangaPage> {\n\n\toverride fun key(data: MangaPage, options: Options) = data.url\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnail.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\n\ndata class PageThumbnail(\n\tval isCurrent: Boolean,\n\tval page: ReaderPage,\n) : ListModel {\n\n\tval number\n\t\tget() = page.index + 1\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is PageThumbnail && page == other.page\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport coil3.size.Size\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.setTextColorAttr\nimport org.koitharu.kotatsu.databinding.ItemPageThumbBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport com.google.android.material.R as materialR\n\nfun pageThumbnailAD(\n\tclickListener: OnListItemClickListener<PageThumbnail>,\n) = adapterDelegateViewBinding<PageThumbnail, ListModel, ItemPageThumbBinding>(\n\t{ inflater, parent -> ItemPageThumbBinding.inflate(inflater, parent, false) },\n) {\n\n\tval gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width)\n\tbinding.imageViewThumb.exactImageSize = Size(\n\t\twidth = gridWidth,\n\t\theight = (gridWidth / 13f * 18f).toInt(),\n\t)\n\n\tAdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)\n\n\tbind {\n\t\tbinding.imageViewThumb.setImageAsync(item.page)\n\t\twith(binding.textViewNumber) {\n\t\t\tsetBackgroundResource(if (item.isCurrent) R.drawable.bg_badge_accent else R.drawable.bg_badge_empty)\n\t\t\tsetTextColorAttr(if (item.isCurrent) materialR.attr.colorOnTertiary else android.R.attr.textColorPrimary)\n\t\t\ttext = item.number.toString()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAdapter.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport android.content.Context\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.listHeaderAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass PageThumbnailAdapter(\n\tclickListener: OnListItemClickListener<PageThumbnail>,\n) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {\n\n\tinit {\n\t\taddDelegate(ListItemType.PAGE_THUMB, pageThumbnailAD(clickListener))\n\t\taddDelegate(ListItemType.HEADER, listHeaderAD(null))\n\t}\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn findHeader(position)?.getText(context)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesFragment.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.view.ActionMode\nimport androidx.collection.ArraySet\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isInvisible\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flowOn\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.nav.dismissParentDialog\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.list.BoundsScrollListener\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate\nimport org.koitharu.kotatsu.core.util.ext.findParentCallback\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.core.util.ext.showOrHide\nimport org.koitharu.kotatsu.databinding.FragmentPagesBinding\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel\nimport org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason\nimport org.koitharu.kotatsu.list.ui.GridSpanResolver\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport org.koitharu.kotatsu.reader.ui.ReaderNavigationCallback\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport javax.inject.Inject\nimport kotlin.math.roundToInt\n\n@AndroidEntryPoint\nclass PagesFragment :\n\tBaseFragment<FragmentPagesBinding>(),\n\tOnListItemClickListener<PageThumbnail>,\n\tRecyclerViewOwner,\n\tListSelectionController.Callback {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\t@Inject\n\tlateinit var pageSaveHelperFactory: PageSaveHelper.Factory\n\n\tprivate val parentViewModel by ChaptersPagesViewModel.ActivityVMLazy(this)\n\tprivate val viewModel by viewModels<PagesViewModel>()\n\tprivate lateinit var pageSaveHelper: PageSaveHelper\n\n\tprivate var thumbnailsAdapter: PageThumbnailAdapter? = null\n\tprivate var spanResolver: GridSpanResolver? = null\n\tprivate var scrollListener: ScrollListener? = null\n\tprivate var selectionController: ListSelectionController? = null\n\n\tprivate val spanSizeLookup = SpanSizeLookup()\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerView\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tpageSaveHelper = pageSaveHelperFactory.create(this)\n\t\tcombine(\n\t\t\tparentViewModel.mangaDetails,\n\t\t\tparentViewModel.readingState,\n\t\t\tparentViewModel.selectedBranch,\n\t\t) { details, readingState, branch ->\n\t\t\tif (details != null && (details.isLoaded || details.chapters.isNotEmpty())) {\n\t\t\t\tPagesViewModel.State(details.filterChapters(branch), readingState, branch)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}.flowOn(Dispatchers.Default)\n\t\t\t.observe(this, viewModel::updateState)\n\t}\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPagesBinding {\n\t\treturn FragmentPagesBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: FragmentPagesBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tspanResolver = GridSpanResolver(binding.root.resources)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = checkNotNull(findAppCompatDelegate()),\n\t\t\tdecoration = PagesSelectionDecoration(binding.root.context),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = this,\n\t\t)\n\t\tthumbnailsAdapter = PageThumbnailAdapter(\n\t\t\tclickListener = this@PagesFragment,\n\t\t)\n\t\tviewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) // before rv initialization\n\t\twith(binding.recyclerView) {\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\tcheckNotNull(selectionController).attachToRecyclerView(this)\n\t\t\tadapter = thumbnailsAdapter\n\t\t\tsetHasFixedSize(true)\n\t\t\tPagerNestedScrollHelper(this).bind(viewLifecycleOwner)\n\t\t\taddOnLayoutChangeListener(spanResolver)\n\t\t\taddOnScrollListener(ScrollListener().also { scrollListener = it })\n\t\t\t(layoutManager as GridLayoutManager).let {\n\t\t\t\tit.spanSizeLookup = spanSizeLookup\n\t\t\t\tit.spanCount = checkNotNull(spanResolver).spanCount\n\t\t\t}\n\t\t}\n\t\tparentViewModel.emptyReason.observe(viewLifecycleOwner, ::onNoChaptersChanged)\n\t\tviewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged)\n\t\tviewModel.onPageSaved.observeEvent(this, PagesSavedObserver(binding.recyclerView))\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))\n\t\tcombine(\n\t\t\tviewModel.isLoading,\n\t\t\tviewModel.thumbnails,\n\t\t) { loading, content ->\n\t\t\tloading && content.isEmpty()\n\t\t}.observe(viewLifecycleOwner) {\n\t\t\tbinding.progressBar.showOrHide(it)\n\t\t}\n\t\tviewModel.isLoadingUp.observe(viewLifecycleOwner) { binding.progressBarTop.showOrHide(it) }\n\t\tviewModel.isLoadingDown.observe(viewLifecycleOwner) { binding.progressBarBottom.showOrHide(it) }\n\t}\n\n\toverride fun onDestroyView() {\n\t\tspanResolver = null\n\t\tscrollListener = null\n\t\tthumbnailsAdapter = null\n\t\tselectionController = null\n\t\tspanSizeLookup.invalidateCache()\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeBask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeBask)\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAll(typeBask)\n\t}\n\n\toverride fun onItemClick(item: PageThumbnail, view: View) {\n\t\tif (selectionController?.onItemClick(item.page.id) == true) {\n\t\t\treturn\n\t\t}\n\t\tval listener = findParentCallback(ReaderNavigationCallback::class.java)\n\t\tif (listener != null && listener.onPageSelected(item.page)) {\n\t\t\tdismissParentDialog()\n\t\t} else {\n\t\t\trouter.openReader(\n\t\t\t\tReaderIntent.Builder(view.context)\n\t\t\t\t\t.manga(parentViewModel.getMangaOrNull() ?: return)\n\t\t\t\t\t.state(ReaderState(item.page.chapterId, item.page.index, 0))\n\t\t\t\t\t.build(),\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun onItemLongClick(item: PageThumbnail, view: View): Boolean {\n\t\treturn selectionController?.onItemLongClick(view, item.page.id) == true\n\t}\n\n\toverride fun onItemContextClick(item: PageThumbnail, view: View): Boolean {\n\t\treturn selectionController?.onItemContextClick(view, item.page.id) == true\n\t}\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\tviewBinding?.recyclerView?.invalidateItemDecorations()\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu,\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_pages, menu)\n\t\treturn true\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_save -> {\n\t\t\t\tviewModel.savePages(pageSaveHelper, collectSelectedPages())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\tprivate suspend fun onThumbnailsChanged(list: List<ListModel>) {\n\t\tval adapter = thumbnailsAdapter ?: return\n\t\tif (adapter.itemCount == 0) {\n\t\t\tvar position = list.indexOfFirst { it is PageThumbnail && it.isCurrent }\n\t\t\tif (position > 0) {\n\t\t\t\tval spanCount = spanResolver?.spanCount ?: 0\n\t\t\t\tval offset = if (position > spanCount + 1) {\n\t\t\t\t\t(resources.getDimensionPixelSize(R.dimen.manga_list_details_item_height) * 0.6).roundToInt()\n\t\t\t\t} else {\n\t\t\t\t\tposition = 0\n\t\t\t\t\t0\n\t\t\t\t}\n\t\t\t\tval scrollCallback = RecyclerViewScrollCallback(requireViewBinding().recyclerView, position, offset)\n\t\t\t\tadapter.emit(list)\n\t\t\t\tscrollCallback.run()\n\t\t\t} else {\n\t\t\t\tadapter.emit(list)\n\t\t\t}\n\t\t} else {\n\t\t\tadapter.emit(list)\n\t\t}\n\t\tspanSizeLookup.invalidateCache()\n\t\tviewBinding?.recyclerView?.let {\n\t\t\tscrollListener?.postInvalidate(it)\n\t\t}\n\t}\n\n\tprivate fun onGridScaleChanged(scale: Float) {\n\t\tspanSizeLookup.invalidateCache()\n\t\tspanResolver?.setGridSize(scale, requireViewBinding().recyclerView)\n\t}\n\n\tprivate fun onNoChaptersChanged(reason: EmptyMangaReason?) {\n\t\twith(viewBinding ?: return) {\n\t\t\ttextViewHolder.setTextAndVisible(reason?.msgResId ?: 0)\n\t\t\trecyclerView.isInvisible = reason != null\n\t\t}\n\t}\n\n\tprivate fun collectSelectedPages(): Set<ReaderPage> {\n\t\tval checkedIds = selectionController?.peekCheckedIds() ?: return emptySet()\n\t\tval items = thumbnailsAdapter?.items ?: return emptySet()\n\t\tval result = ArraySet<ReaderPage>(checkedIds.size)\n\t\tfor (item in items) {\n\t\t\tif (item is PageThumbnail && item.page.id in checkedIds) {\n\t\t\t\tresult.add(item.page)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate inner class ScrollListener : BoundsScrollListener(3, 3) {\n\n\t\toverride fun onScrolledToStart(recyclerView: RecyclerView) {\n\t\t\tviewModel.loadPrevChapter()\n\t\t}\n\n\t\toverride fun onScrolledToEnd(recyclerView: RecyclerView) {\n\t\t\tviewModel.loadNextChapter()\n\t\t}\n\t}\n\n\tprivate inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() {\n\n\t\tinit {\n\t\t\tisSpanIndexCacheEnabled = true\n\t\t\tisSpanGroupIndexCacheEnabled = true\n\t\t}\n\n\t\toverride fun getSpanSize(position: Int): Int {\n\t\t\tval total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1\n\t\t\treturn when (thumbnailsAdapter?.getItemViewType(position)) {\n\t\t\t\tListItemType.PAGE_THUMB.ordinal -> 1\n\t\t\t\telse -> total\n\t\t\t}\n\t\t}\n\n\t\tfun invalidateCache() {\n\t\t\tinvalidateSpanGroupIndexCache()\n\t\t\tinvalidateSpanIndexCache()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesSavedObserver.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport android.net.Uri\nimport android.view.View\nimport com.google.android.material.snackbar.Snackbar\nimport kotlinx.coroutines.flow.FlowCollector\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ShareHelper\n\nclass PagesSavedObserver(\n\tprivate val snackbarHost: View,\n) : FlowCollector<Collection<Uri>> {\n\n\toverride suspend fun emit(value: Collection<Uri>) {\n\t\tval msg = when (value.size) {\n\t\t\t0 -> R.string.nothing_found\n\t\t\t1 -> R.string.page_saved\n\t\t\telse -> R.string.pages_saved\n\t\t}\n\t\tval snackbar = Snackbar.make(snackbarHost, msg, Snackbar.LENGTH_LONG)\n\t\tvalue.singleOrNull()?.let { uri ->\n\t\t\tsnackbar.setAction(R.string.share) {\n\t\t\t\tShareHelper(snackbarHost.context).shareImage(uri)\n\t\t\t}\n\t\t}\n\t\tsnackbar.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport android.content.Context\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.list.ui.MangaSelectionDecoration\n\nclass PagesSelectionDecoration(context: Context) : MangaSelectionDecoration(context) {\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return RecyclerView.NO_ID\n\t\tval item = holder.getItem(PageThumbnail::class.java) ?: return RecyclerView.NO_ID\n\t\treturn item.page.id\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt",
    "content": "package org.koitharu.kotatsu.details.ui.pager.pages\n\nimport android.net.Uri\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.firstNotNull\nimport org.koitharu.kotatsu.core.util.ext.requireValue\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.reader.domain.ChaptersLoader\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PagesViewModel @Inject constructor(\n\tprivate val chaptersLoader: ChaptersLoader,\n\tsettings: AppSettings,\n) : BaseViewModel() {\n\n\tprivate var loadingJob: Job? = null\n\tprivate var loadingPrevJob: Job? = null\n\tprivate var loadingNextJob: Job? = null\n\n\tprivate val state = MutableStateFlow<State?>(null)\n\tval thumbnails = MutableStateFlow<List<ListModel>>(emptyList())\n\tval isLoadingUp = MutableStateFlow(false)\n\tval isLoadingDown = MutableStateFlow(false)\n\tval onPageSaved = MutableEventFlow<Collection<Uri>>()\n\n\tval gridScale = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_GRID_SIZE_PAGES,\n\t\tvalueProducer = { gridSizePages / 100f },\n\t)\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tstate.filterNotNull()\n\t\t\t\t.collect {\n\t\t\t\t\tval prevJob = loadingJob\n\t\t\t\t\tloadingJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\t\t\t\tprevJob?.cancelAndJoin()\n\t\t\t\t\t\tdoInit(it)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t}\n\n\tfun updateState(newState: State?) {\n\t\tif (newState != null) {\n\t\t\tstate.value = newState\n\t\t}\n\t}\n\n\tfun loadPrevChapter() {\n\t\tif (loadingJob?.isActive == true || loadingPrevJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tloadingPrevJob = loadPrevNextChapter(isNext = false)\n\t}\n\n\tfun loadNextChapter() {\n\t\tif (loadingJob?.isActive == true || loadingNextJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tloadingNextJob = loadPrevNextChapter(isNext = true)\n\t}\n\n\tfun savePages(\n\t\tpageSaveHelper: PageSaveHelper,\n\t\tpages: Set<ReaderPage>,\n\t) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval manga = state.requireValue().details.toManga()\n\t\t\tval tasks = pages.map {\n\t\t\t\tPageSaveHelper.Task(\n\t\t\t\t\tmanga = manga,\n\t\t\t\t\tchapterId = it.chapterId,\n\t\t\t\t\tpageNumber = it.index + 1,\n\t\t\t\t\tpage = it.toMangaPage(),\n\t\t\t\t)\n\t\t\t}\n\t\t\tval dest = pageSaveHelper.save(tasks)\n\t\t\tonPageSaved.call(dest)\n\t\t}\n\t}\n\n\tprivate suspend fun doInit(state: State) {\n\t\tchaptersLoader.init(state.details)\n\t\tval initialChapterId = state.readerState?.chapterId?.takeIf {\n\t\t\tchaptersLoader.peekChapter(it) != null\n\t\t} ?: state.details.allChapters.firstOrNull()?.id ?: return\n\t\tif (!chaptersLoader.hasPages(initialChapterId)) {\n\t\t\tvar hasPages = chaptersLoader.loadSingleChapter(initialChapterId)\n\t\t\twhile (!hasPages) {\n\t\t\t\tif (chaptersLoader.loadPrevNextChapter(state.details, initialChapterId, isNext = true)) {\n\t\t\t\t\thasPages = chaptersLoader.snapshot().isNotEmpty()\n\t\t\t\t} else {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tupdateList(state.readerState)\n\t}\n\n\tprivate fun loadPrevNextChapter(isNext: Boolean): Job = launchJob(Dispatchers.Default) {\n\t\tval indicator = if (isNext) isLoadingDown else isLoadingUp\n\t\tindicator.value = true\n\t\ttry {\n\t\t\tval currentState = state.firstNotNull()\n\t\t\tval currentId = (if (isNext) chaptersLoader.last() else chaptersLoader.first()).chapterId\n\t\t\tchaptersLoader.loadPrevNextChapter(currentState.details, currentId, isNext)\n\t\t\tupdateList(currentState.readerState)\n\t\t} finally {\n\t\t\tindicator.value = false\n\t\t}\n\t}\n\n\tprivate fun updateList(readerState: ReaderState?) {\n\t\tval snapshot = chaptersLoader.snapshot()\n\t\tval pages = buildList(snapshot.size + chaptersLoader.size + 2) {\n\t\t\tvar previousChapterId = 0L\n\t\t\tfor (page in snapshot) {\n\t\t\t\tif (page.chapterId != previousChapterId) {\n\t\t\t\t\tchaptersLoader.peekChapter(page.chapterId)?.let {\n\t\t\t\t\t\tadd(ListHeader(it))\n\t\t\t\t\t}\n\t\t\t\t\tpreviousChapterId = page.chapterId\n\t\t\t\t}\n\t\t\t\tthis += PageThumbnail(\n\t\t\t\t\tisCurrent = readerState?.let {\n\t\t\t\t\t\tpage.chapterId == it.chapterId && page.index == it.page\n\t\t\t\t\t} == true,\n\t\t\t\t\tpage = page,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tthumbnails.value = pages\n\t}\n\n\tdata class State(\n\t\tval details: MangaDetails,\n\t\tval readerState: ReaderState?,\n\t\tval branch: String?\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedListFragment.kt",
    "content": "package org.koitharu.kotatsu.details.ui.related\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\n\n@AndroidEntryPoint\nclass RelatedListFragment : MangaListFragment() {\n\n\toverride val viewModel by viewModels<RelatedListViewModel>()\n\toverride val isSwipeRefreshEnabled = false\n\n\toverride fun onScrolledToEnd() = Unit\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_remote, menu)\n\t\treturn super.onCreateActionMode(controller, menuInflater, menu)\n\t}\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedListViewModel.kt",
    "content": "package org.koitharu.kotatsu.details.ui.related\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\n@HiltViewModel\nclass RelatedListViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n\tsettings: AppSettings,\n\tprivate val mangaListMapper: MangaListMapper,\n\tmangaDataRepository: MangaDataRepository,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges) {\n\n\tprivate val seed = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga\n\tprivate val repository = mangaRepositoryFactory.create(seed.source)\n\tprivate val mangaList = MutableStateFlow<List<Manga>?>(null)\n\tprivate val listError = MutableStateFlow<Throwable?>(null)\n\tprivate var loadingJob: Job? = null\n\n\toverride val content = combine(\n\t\tmangaList,\n\t\tobserveListModeWithTriggers(),\n\t\tlistError,\n\t) { list, mode, error ->\n\t\twhen {\n\t\t\tlist.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))\n\t\t\tlist == null -> listOf(LoadingState)\n\t\t\tlist.isEmpty() -> listOf(createEmptyState())\n\t\t\telse -> mangaListMapper.toListModelList(list, mode)\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tinit {\n\t\tloadList()\n\t}\n\n\toverride fun onRefresh() {\n\t\tloadList()\n\t}\n\n\toverride fun onRetry() {\n\t\tloadList()\n\t}\n\n\tprivate fun loadList(): Job {\n\t\tloadingJob?.let {\n\t\t\tif (it.isActive) return it\n\t\t}\n\t\treturn launchLoadingJob(Dispatchers.Default) {\n\t\t\ttry {\n\t\t\t\tlistError.value = null\n\t\t\t\tmangaList.value = repository.getRelated(seed)\n\t\t\t} catch (e: CancellationException) {\n\t\t\t\tthrow e\n\t\t\t} catch (e: Throwable) {\n\t\t\t\te.printStackTraceDebug()\n\t\t\t\tlistError.value = e\n\t\t\t\tif (!mangaList.value.isNullOrEmpty()) {\n\t\t\t\t\terrorEvent.call(e)\n\t\t\t\t}\n\t\t\t}\n\t\t}.also { loadingJob = it }\n\t}\n\n\tprivate fun createEmptyState() = EmptyState(\n\t\ticon = R.drawable.ic_empty_common,\n\t\ttextPrimary = R.string.nothing_found,\n\t\ttextSecondary = 0,\n\t\tactionStringRes = 0,\n\t)\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedMangaActivity.kt",
    "content": "package org.koitharu.kotatsu.details.ui.related\n\nimport org.koitharu.kotatsu.core.ui.FragmentContainerActivity\n\nclass RelatedMangaActivity : FragmentContainerActivity(RelatedListFragment::class.java)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt",
    "content": "package org.koitharu.kotatsu.details.ui.scrobbling\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.databinding.ItemScrobblingInfoBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\n\nfun scrobblingInfoAD(\n\trouter: AppRouter,\n) = adapterDelegateViewBinding<ScrobblingInfo, ListModel, ItemScrobblingInfoBinding>(\n\t{ layoutInflater, parent -> ItemScrobblingInfoBinding.inflate(layoutInflater, parent, false) },\n) {\n\tbinding.root.setOnClickListener {\n\t\trouter.showScrobblingInfoSheet(bindingAdapterPosition)\n\t}\n\n\tbind {\n\t\tbinding.imageViewCover.setImageAsync(item.coverUrl)\n\t\tbinding.textViewTitle.setText(item.scrobbler.titleResId)\n\t\tbinding.imageViewIcon.setImageResource(item.scrobbler.iconResId)\n\t\tbinding.ratingBar.rating = item.rating * binding.ratingBar.numStars\n\t\tbinding.textViewStatus.text = item.status?.let {\n\t\t\tcontext.resources.getStringArray(R.array.scrobbling_statuses).getOrNull(it.ordinal)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoSheet.kt",
    "content": "package org.koitharu.kotatsu.details.ui.scrobbling\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.AdapterView\nimport android.widget.RatingBar\nimport android.widget.Toast\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.activityViewModels\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.sanitize\nimport org.koitharu.kotatsu.databinding.SheetScrobblingBinding\nimport org.koitharu.kotatsu.details.ui.DetailsViewModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\n\n@AndroidEntryPoint\nclass ScrobblingInfoSheet :\n\tBaseAdaptiveSheet<SheetScrobblingBinding>(),\n\tAdapterView.OnItemSelectedListener,\n\tRatingBar.OnRatingBarChangeListener,\n\tView.OnClickListener,\n\tPopupMenu.OnMenuItemClickListener {\n\n\tprivate val viewModel by activityViewModels<DetailsViewModel>()\n\tprivate var scrobblerIndex: Int = -1\n\n\tprivate var menu: PopupMenu? = null\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tscrobblerIndex = requireArguments().getInt(AppRouter.KEY_INDEX, scrobblerIndex)\n\t}\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetScrobblingBinding {\n\t\treturn SheetScrobblingBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: SheetScrobblingBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tviewModel.scrobblingInfo.observe(viewLifecycleOwner, ::onScrobblingInfoChanged)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner) {\n\t\t\tToast.makeText(binding.root.context, it.getDisplayMessage(binding.root.resources), Toast.LENGTH_SHORT)\n\t\t\t\t.show()\n\t\t}\n\n\t\tbinding.spinnerStatus.onItemSelectedListener = this\n\t\tbinding.ratingBar.onRatingBarChangeListener = this\n\t\tbinding.buttonMenu.setOnClickListener(this)\n\t\tbinding.imageViewCover.setOnClickListener(this)\n\t\tbinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()\n\n\t\tmenu = PopupMenu(binding.root.context, binding.buttonMenu).apply {\n\t\t\tinflate(R.menu.opt_scrobbling)\n\t\t\tsetOnMenuItemClickListener(this@ScrobblingInfoSheet)\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\tmenu = null\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tviewBinding?.root?.updatePadding(\n\t\t\tbottom = insets.getInsets(typeMask).bottom,\n\t\t)\n\t\treturn insets.consume(v, typeMask, bottom = true)\n\t}\n\n\n\toverride fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {\n\t\tviewModel.updateScrobbling(\n\t\t\tindex = scrobblerIndex,\n\t\t\trating = requireViewBinding().ratingBar.rating / requireViewBinding().ratingBar.numStars,\n\t\t\tstatus = ScrobblingStatus.entries.getOrNull(position),\n\t\t)\n\t}\n\n\toverride fun onNothingSelected(parent: AdapterView<*>?) = Unit\n\n\toverride fun onRatingChanged(ratingBar: RatingBar, rating: Float, fromUser: Boolean) {\n\t\tif (fromUser) {\n\t\t\tviewModel.updateScrobbling(\n\t\t\t\tindex = scrobblerIndex,\n\t\t\t\trating = rating / ratingBar.numStars,\n\t\t\t\tstatus = ScrobblingStatus.entries.getOrNull(requireViewBinding().spinnerStatus.selectedItemPosition),\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_menu -> menu?.show()\n\t\t\tR.id.imageView_cover -> router.openImage(\n\t\t\t\turl = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.coverUrl ?: return,\n\t\t\t\tsource = null,\n\t\t\t\tanchor = v,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun onScrobblingInfoChanged(scrobblings: List<ScrobblingInfo>) {\n\t\tval scrobbling = scrobblings.getOrNull(scrobblerIndex)\n\t\tif (scrobbling == null) {\n\t\t\tdismissAllowingStateLoss()\n\t\t\treturn\n\t\t}\n\t\tval binding = viewBinding ?: return\n\t\tbinding.textViewTitle.text = scrobbling.title\n\t\tbinding.ratingBar.rating = scrobbling.rating * binding.ratingBar.numStars\n\t\tbinding.textViewDescription.text = scrobbling.description?.sanitize()\n\t\tbinding.spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1)\n\t\tbinding.imageViewLogo.contentDescription = getString(scrobbling.scrobbler.titleResId)\n\t\tbinding.imageViewLogo.setImageResource(scrobbling.scrobbler.iconResId)\n\t\tbinding.imageViewCover.setImageAsync(scrobbling.coverUrl)\n\t}\n\n\toverride fun onMenuItemClick(item: MenuItem): Boolean {\n\t\twhen (item.itemId) {\n\t\t\tR.id.action_browser -> {\n\t\t\t\tval url = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.externalUrl ?: return false\n\t\t\t\tif (!router.openExternalBrowser(url, getString(R.string.open_in_browser))) {\n\t\t\t\t\tSnackbar.make(\n\t\t\t\t\t\tviewBinding?.textViewDescription ?: return false,\n\t\t\t\t\t\tR.string.operation_not_supported,\n\t\t\t\t\t\tSnackbar.LENGTH_SHORT,\n\t\t\t\t\t).show()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tR.id.action_unregister -> {\n\t\t\t\tviewModel.unregisterScrobbling(scrobblerIndex)\n\t\t\t\tdismiss()\n\t\t\t}\n\n\t\t\tR.id.action_edit -> {\n\t\t\t\tval manga = viewModel.manga.value ?: return false\n\t\t\t\tval scrobblerService = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.scrobbler\n\t\t\t\tactivity?.router?.showScrobblingSelectorSheet(manga, scrobblerService)\n\t\t\t\tdismiss()\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingItemDecoration.kt",
    "content": "package org.koitharu.kotatsu.details.ui.scrobbling\n\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\n\nclass ScrobblingItemDecoration : RecyclerView.ItemDecoration() {\n\n\tprivate var spacing: Int = -1\n\n\toverride fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {\n\t\tif (spacing == -1) {\n\t\t\tspacing = parent.context.resources.getDimensionPixelOffset(R.dimen.scrobbling_list_spacing)\n\t\t}\n\t\toutRect.set(0, spacing, 0, 0)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt",
    "content": "package org.koitharu.kotatsu.details.ui.scrobbling\n\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass ScrollingInfoAdapter(\n\trouter: AppRouter,\n) : BaseListAdapter<ListModel>() {\n\n\tinit {\n\t\tdelegatesManager.addDelegate(scrobblingInfoAD(router))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/domain/DownloadProgress.kt",
    "content": "package org.koitharu.kotatsu.download.domain\n\ndata class DownloadProgress(\n\tval totalChapters: Int,\n\tval currentChapter: Int,\n\tval totalPages: Int,\n\tval currentPage: Int,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/domain/DownloadState.kt",
    "content": "package org.koitharu.kotatsu.download.domain\n\nimport androidx.work.Data\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.time.Instant\n\ndata class DownloadState(\n\tval manga: Manga,\n\tval isIndeterminate: Boolean,\n\tval isPaused: Boolean = false,\n\tval isStopped: Boolean = false,\n\tval error: Throwable? = null,\n\tval errorMessage: String? = null,\n\tval totalChapters: Int = 0,\n\tval currentChapter: Int = 0,\n\tval totalPages: Int = 0,\n\tval currentPage: Int = 0,\n\tval eta: Long = -1L,\n\tval isStuck: Boolean = false,\n\tval localManga: LocalManga? = null,\n\tval downloadedChapters: Int = 0,\n\tval timestamp: Long = System.currentTimeMillis(),\n) {\n\n\tval max: Int = totalChapters * totalPages\n\n\tval progress: Int = totalPages * currentChapter + currentPage + 1\n\n\tval percent: Float = if (max > 0) progress.toFloat() / max else PROGRESS_NONE\n\n\tval isFinalState: Boolean\n\t\tget() = localManga != null || (error != null && !isPaused)\n\n\tval isParticularProgress: Boolean\n\t\tget() = localManga == null && error == null && !isPaused && !isStopped && max > 0 && !isIndeterminate\n\n\tfun toWorkData() = Data.Builder()\n\t\t.putLong(DATA_MANGA_ID, manga.id)\n\t\t.putInt(DATA_MAX, max)\n\t\t.putInt(DATA_PROGRESS, progress)\n\t\t.putLong(DATA_ETA, eta)\n\t\t.putBoolean(DATA_STUCK, isStuck)\n\t\t.putLong(DATA_TIMESTAMP, timestamp)\n\t\t.putString(DATA_ERROR, errorMessage)\n\t\t.putInt(DATA_CHAPTERS, downloadedChapters)\n\t\t.putBoolean(DATA_INDETERMINATE, isIndeterminate)\n\t\t.putBoolean(DATA_PAUSED, isPaused)\n\t\t.build()\n\n\tcompanion object {\n\n\t\tprivate const val DATA_MANGA_ID = \"manga_id\"\n\t\tprivate const val DATA_MAX = \"max\"\n\t\tprivate const val DATA_PROGRESS = \"progress\"\n\t\tprivate const val DATA_CHAPTERS = \"chapter_cnt\"\n\t\tprivate const val DATA_ETA = \"eta\"\n\t\tprivate const val DATA_STUCK = \"stuck\"\n\t\tconst val DATA_TIMESTAMP = \"timestamp\"\n\t\tprivate const val DATA_ERROR = \"error\"\n\t\tprivate const val DATA_INDETERMINATE = \"indeterminate\"\n\t\tprivate const val DATA_PAUSED = \"paused\"\n\n\t\tfun getMangaId(data: Data): Long = data.getLong(DATA_MANGA_ID, 0L)\n\n\t\tfun isIndeterminate(data: Data): Boolean = data.getBoolean(DATA_INDETERMINATE, false)\n\n\t\tfun isPaused(data: Data): Boolean = data.getBoolean(DATA_PAUSED, false)\n\n\t\tfun getMax(data: Data): Int = data.getInt(DATA_MAX, 0)\n\n\t\tfun getError(data: Data): String? = data.getString(DATA_ERROR)\n\n\t\tfun getProgress(data: Data): Int = data.getInt(DATA_PROGRESS, 0)\n\n\t\tfun getEta(data: Data): Long = data.getLong(DATA_ETA, -1L)\n\n\t\tfun isStuck(data: Data): Boolean = data.getBoolean(DATA_STUCK, false)\n\n\t\tfun getTimestamp(data: Data): Instant = Instant.ofEpochMilli(data.getLong(DATA_TIMESTAMP, 0L))\n\n\t\tfun getDownloadedChapters(data: Data): Int = data.getInt(DATA_CHAPTERS, 0)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/dialog/ChapterSelectOptions.kt",
    "content": "package org.koitharu.kotatsu.download.ui.dialog\n\ndata class ChapterSelectOptions(\n\tval wholeManga: ChaptersSelectMacro.WholeManga,\n\tval wholeBranch: ChaptersSelectMacro.WholeBranch?,\n\tval firstChapters: ChaptersSelectMacro.FirstChapters?,\n\tval unreadChapters: ChaptersSelectMacro.UnreadChapters?,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/dialog/ChaptersSelectMacro.kt",
    "content": "package org.koitharu.kotatsu.download.ui.dialog\n\nimport androidx.collection.ArraySet\nimport androidx.collection.LongLongMap\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.util.mapNotNullToSet\n\ninterface ChaptersSelectMacro {\n\n\tfun getChaptersIds(mangaId: Long, chapters: List<MangaChapter>): Set<Long>?\n\n\tclass WholeManga(\n\t\tval chaptersCount: Int,\n\t) : ChaptersSelectMacro {\n\n\t\toverride fun getChaptersIds(mangaId: Long, chapters: List<MangaChapter>): Set<Long>? = null\n\t}\n\n\tclass WholeBranch(\n\t\tval branches: Map<String?, Int>,\n\t\tval selectedBranch: String?,\n\t) : ChaptersSelectMacro {\n\n\t\tval chaptersCount: Int = branches[selectedBranch] ?: 0\n\n\t\toverride fun getChaptersIds(\n\t\t\tmangaId: Long,\n\t\t\tchapters: List<MangaChapter>\n\t\t): Set<Long> = chapters.mapNotNullToSet { c ->\n\t\t\tif (c.branch == selectedBranch) {\n\t\t\t\tc.id\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}\n\n\t\tfun copy(branch: String?) = WholeBranch(branches, branch)\n\t}\n\n\tclass FirstChapters(\n\t\tval chaptersCount: Int,\n\t\tval maxAvailableCount: Int,\n\t\tval branch: String?,\n\t) : ChaptersSelectMacro {\n\n\t\toverride fun getChaptersIds(mangaId: Long, chapters: List<MangaChapter>): Set<Long> {\n\t\t\tval result = ArraySet<Long>(minOf(chaptersCount, chapters.size))\n\t\t\tfor (c in chapters) {\n\t\t\t\tif (c.branch == branch) {\n\t\t\t\t\tresult.add(c.id)\n\t\t\t\t\tif (result.size >= chaptersCount) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result\n\t\t}\n\n\t\tfun copy(count: Int) = FirstChapters(count, maxAvailableCount, branch)\n\t}\n\n\tclass UnreadChapters(\n\t\tval chaptersCount: Int,\n\t\tval maxAvailableCount: Int,\n\t\tprivate val currentChaptersIds: LongLongMap,\n\t) : ChaptersSelectMacro {\n\n\t\toverride fun getChaptersIds(mangaId: Long, chapters: List<MangaChapter>): Set<Long>? {\n\t\t\tif (chapters.isEmpty()) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tval currentChapterId = currentChaptersIds.getOrDefault(mangaId, chapters.first().id)\n\t\t\tvar branch: String? = null\n\t\t\tvar isAdding = false\n\t\t\tval result = ArraySet<Long>(minOf(chaptersCount, chapters.size))\n\t\t\tfor (c in chapters) {\n\t\t\t\tif (!isAdding) {\n\t\t\t\t\tif (c.id == currentChapterId) {\n\t\t\t\t\t\tbranch = c.branch\n\t\t\t\t\t\tisAdding = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (isAdding) {\n\t\t\t\t\tif (c.branch == branch) {\n\t\t\t\t\t\tresult.add(c.id)\n\t\t\t\t\t\tif (result.size >= chaptersCount) {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result\n\t\t}\n\n\t\tfun copy(count: Int) = UnreadChapters(count, maxAvailableCount, currentChaptersIds)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/dialog/DestinationsAdapter.kt",
    "content": "package org.koitharu.kotatsu.download.ui.dialog\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ArrayAdapter\nimport android.widget.TextView\nimport androidx.core.view.isVisible\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemStorageConfigBinding\nimport org.koitharu.kotatsu.settings.storage.DirectoryModel\n\nclass DestinationsAdapter(context: Context, dataset: List<DirectoryModel>) :\n\tArrayAdapter<DirectoryModel>(context, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1, dataset) {\n\n\tinit {\n\t\tsetDropDownViewResource(R.layout.item_storage_config)\n\t}\n\n\toverride fun getView(position: Int, convertView: View?, parent: ViewGroup): View {\n\t\tval view = convertView ?: LayoutInflater.from(parent.context)\n\t\t\t.inflate(android.R.layout.simple_spinner_dropdown_item, parent, false)\n\t\tval item = getItem(position) ?: return view\n\t\tview.findViewById<TextView>(android.R.id.text1).text = item.title ?: view.context.getString(item.titleRes)\n\t\treturn view\n\t}\n\n\toverride fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {\n\t\tval view = convertView ?: LayoutInflater.from(parent.context)\n\t\t\t.inflate(R.layout.item_storage_config, parent, false)\n\t\tval item = getItem(position) ?: return view\n\t\tval binding =\n\t\t\tview.tag as? ItemStorageConfigBinding ?: ItemStorageConfigBinding.bind(view).also { view.tag = it }\n\t\tbinding.buttonRemove.isVisible = false\n\t\tbinding.textViewTitle.text = item.title ?: view.context.getString(item.titleRes)\n\t\tbinding.textViewSubtitle.textAndVisible = item.file?.path\n\t\treturn view\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/dialog/DownloadDialogFragment.kt",
    "content": "package org.koitharu.kotatsu.download.ui.dialog\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Spinner\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentResultListener\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.LifecycleOwner\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.DownloadFormat\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView\nimport org.koitharu.kotatsu.core.util.ext.findActivity\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.parentView\nimport org.koitharu.kotatsu.core.util.ext.showOrHide\nimport org.koitharu.kotatsu.databinding.DialogDownloadBinding\nimport org.koitharu.kotatsu.main.ui.owners.BottomNavOwner\nimport org.koitharu.kotatsu.parsers.util.format\nimport org.koitharu.kotatsu.settings.storage.DirectoryModel\n\n@AndroidEntryPoint\nclass DownloadDialogFragment : AlertDialogFragment<DialogDownloadBinding>(), View.OnClickListener {\n\n\tprivate val viewModel by viewModels<DownloadDialogViewModel>()\n\tprivate var optionViews: Array<out TwoLinesItemView>? = null\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?) =\n\t\tDialogDownloadBinding.inflate(inflater, container, false)\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder)\n\t\t\t.setTitle(R.string.save_manga)\n\t\t\t.setCancelable(true)\n\t}\n\n\toverride fun onViewBindingCreated(binding: DialogDownloadBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\toptionViews = arrayOf(\n\t\t\tbinding.optionWholeManga,\n\t\t\tbinding.optionWholeBranch,\n\t\t\tbinding.optionFirstChapters,\n\t\t\tbinding.optionUnreadChapters,\n\t\t).onEach {\n\t\t\tit.setOnClickListener(this)\n\t\t\tit.setOnButtonClickListener(this)\n\t\t}\n\t\tbinding.buttonCancel.setOnClickListener(this)\n\t\tbinding.buttonConfirm.setOnClickListener(this)\n\t\tbinding.textViewMore.setOnClickListener(this)\n\n\t\tbinding.textViewTip.isVisible = viewModel.manga.size == 1\n\t\tbinding.textViewSummary.text = viewModel.manga.joinToStringWithLimit(binding.root.context, 120) { it.title }\n\n\t\tviewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)\n\t\tviewModel.onScheduled.observeEvent(viewLifecycleOwner, this::onDownloadScheduled)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, this::onError)\n\t\tviewModel.defaultFormat.observe(viewLifecycleOwner, this::onDefaultFormatChanged)\n\t\tviewModel.availableDestinations.observe(viewLifecycleOwner, this::onDestinationsChanged)\n\t\tviewModel.chaptersSelectOptions.observe(viewLifecycleOwner, this::onChapterSelectOptionsChanged)\n\t\tviewModel.isOptionsLoading.observe(viewLifecycleOwner, binding.progressBar::showOrHide)\n\t}\n\n\toverride fun onViewStateRestored(savedInstanceState: Bundle?) {\n\t\tsuper.onViewStateRestored(savedInstanceState)\n\t\tshowMoreOptions(requireViewBinding().textViewMore.isChecked)\n\t\tsetCheckedOption(\n\t\t\tsavedInstanceState?.getInt(KEY_CHECKED_OPTION, R.id.option_whole_manga) ?: R.id.option_whole_manga,\n\t\t)\n\t}\n\n\toverride fun onSaveInstanceState(outState: Bundle) {\n\t\tsuper.onSaveInstanceState(outState)\n\t\toptionViews?.find { it.isChecked }?.let {\n\t\t\toutState.putInt(KEY_CHECKED_OPTION, it.id)\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\toptionViews = null\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_cancel -> dialog?.cancel()\n\t\t\tR.id.button_confirm -> router.askForDownloadOverMeteredNetwork(::schedule)\n\n\t\t\tR.id.textView_more -> {\n\t\t\t\tval binding = viewBinding ?: return\n\t\t\t\tbinding.textViewMore.toggle()\n\t\t\t\tshowMoreOptions(binding.textViewMore.isChecked)\n\t\t\t}\n\n\t\t\tR.id.button -> when (v.parentView?.id ?: return) {\n\t\t\t\tR.id.option_whole_branch -> showBranchSelection(v)\n\t\t\t\tR.id.option_first_chapters -> showFirstChaptersCountSelection(v)\n\t\t\t\tR.id.option_unread_chapters -> showUnreadChaptersCountSelection(v)\n\t\t\t}\n\n\t\t\telse -> if (v is TwoLinesItemView) {\n\t\t\t\tsetCheckedOption(v.id)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun schedule(allowMeteredNetwork: Boolean) {\n\t\tviewBinding?.run {\n\t\t\tval options = viewModel.chaptersSelectOptions.value\n\t\t\tviewModel.confirm(\n\t\t\t\tstartNow = switchStart.isChecked,\n\t\t\t\tchaptersMacro = when {\n\t\t\t\t\toptionWholeManga.isChecked -> options.wholeManga\n\t\t\t\t\toptionWholeBranch.isChecked -> options.wholeBranch ?: return@run\n\t\t\t\t\toptionFirstChapters.isChecked -> options.firstChapters ?: return@run\n\t\t\t\t\toptionUnreadChapters.isChecked -> options.unreadChapters ?: return@run\n\t\t\t\t\telse -> return@run\n\t\t\t\t},\n\t\t\t\tformat = DownloadFormat.entries.getOrNull(spinnerFormat.selectedItemPosition),\n\t\t\t\tdestination = viewModel.availableDestinations.value.getOrNull(spinnerDestination.selectedItemPosition),\n\t\t\t\tallowMetered = allowMeteredNetwork,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tMaterialAlertDialogBuilder(context ?: return)\n\t\t\t.setNegativeButton(R.string.close, null)\n\t\t\t.setTitle(R.string.error)\n\t\t\t.setMessage(e.getDisplayMessage(resources))\n\t\t\t.show()\n\t\tdismiss()\n\t}\n\n\tprivate fun onLoadingStateChanged(value: Boolean) {\n\t\twith(requireViewBinding()) {\n\t\t\tbuttonConfirm.isEnabled = !value\n\t\t}\n\t}\n\n\tprivate fun onDefaultFormatChanged(format: DownloadFormat?) {\n\t\tval spinner = viewBinding?.spinnerFormat ?: return\n\t\tspinner.setSelection(format?.ordinal ?: Spinner.INVALID_POSITION)\n\t}\n\n\tprivate fun onDestinationsChanged(directories: List<DirectoryModel>) {\n\t\tviewBinding?.spinnerDestination?.run {\n\t\t\tadapter = DestinationsAdapter(context, directories)\n\t\t\tsetSelection(directories.indexOfFirst { it.isChecked })\n\t\t}\n\t}\n\n\tprivate fun onChapterSelectOptionsChanged(options: ChapterSelectOptions) {\n\t\twith(viewBinding ?: return) {\n\t\t\t// Whole manga\n\t\t\toptionWholeManga.subtitle = if (options.wholeManga.chaptersCount > 0) {\n\t\t\t\tresources.getQuantityStringSafe(\n\t\t\t\t\tR.plurals.chapters,\n\t\t\t\t\toptions.wholeManga.chaptersCount,\n\t\t\t\t\toptions.wholeManga.chaptersCount,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t\t// All chapters for branch\n\t\t\toptionWholeBranch.isVisible = options.wholeBranch != null\n\t\t\toptions.wholeBranch?.let {\n\t\t\t\toptionWholeBranch.title = resources.getString(\n\t\t\t\t\tR.string.download_option_all_chapters,\n\t\t\t\t\tit.selectedBranch,\n\t\t\t\t)\n\t\t\t\toptionWholeBranch.subtitle = if (it.chaptersCount > 0) {\n\t\t\t\t\tresources.getQuantityStringSafe(\n\t\t\t\t\t\tR.plurals.chapters,\n\t\t\t\t\t\tit.chaptersCount,\n\t\t\t\t\t\tit.chaptersCount,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t\t\t// First N chapters\n\t\t\toptionFirstChapters.isVisible = options.firstChapters != null\n\t\t\toptions.firstChapters?.let {\n\t\t\t\toptionFirstChapters.title = resources.getString(\n\t\t\t\t\tR.string.download_option_first_n_chapters,\n\t\t\t\t\tresources.getQuantityStringSafe(\n\t\t\t\t\t\tR.plurals.chapters,\n\t\t\t\t\t\tit.chaptersCount,\n\t\t\t\t\t\tit.chaptersCount,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\toptionFirstChapters.subtitle = it.branch\n\t\t\t}\n\t\t\t// Next N unread chapters\n\t\t\toptionUnreadChapters.isVisible = options.unreadChapters != null\n\t\t\toptions.unreadChapters?.let {\n\t\t\t\toptionUnreadChapters.title = if (it.chaptersCount == Int.MAX_VALUE) {\n\t\t\t\t\tresources.getString(R.string.download_option_all_unread)\n\t\t\t\t} else {\n\t\t\t\t\tresources.getString(\n\t\t\t\t\t\tR.string.download_option_next_unread_n_chapters,\n\t\t\t\t\t\tresources.getQuantityStringSafe(\n\t\t\t\t\t\t\tR.plurals.chapters,\n\t\t\t\t\t\t\tit.chaptersCount,\n\t\t\t\t\t\t\tit.chaptersCount,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onDownloadScheduled(isStarted: Boolean) {\n\t\tval bundle = Bundle(1)\n\t\tbundle.putBoolean(ARG_STARTED, isStarted)\n\t\tsetFragmentResult(RESULT_KEY, bundle)\n\t\tdismiss()\n\t}\n\n\tprivate fun showMoreOptions(isVisible: Boolean) = viewBinding?.apply {\n\t\tcardFormat.isVisible = isVisible\n\t\ttextViewFormat.isVisible = isVisible\n\t\tcardDestination.isVisible = isVisible\n\t\ttextViewDestination.isVisible = isVisible\n\t}\n\n\tprivate fun setCheckedOption(id: Int) {\n\t\tfor (optionView in optionViews ?: return) {\n\t\t\toptionView.isChecked = id == optionView.id\n\t\t\toptionView.isButtonEnabled = optionView.isChecked\n\t\t}\n\t}\n\n\tprivate fun showBranchSelection(v: View) {\n\t\tval option = viewModel.chaptersSelectOptions.value.wholeBranch ?: return\n\t\tval branches = option.branches.keys.toList()\n\t\tif (branches.size <= 1) {\n\t\t\treturn\n\t\t}\n\t\tval menu = PopupMenu(v.context, v)\n\t\tfor ((i, branch) in branches.withIndex()) {\n\t\t\tmenu.menu.add(Menu.NONE, Menu.NONE, i, branch ?: getString(R.string.unknown))\n\t\t}\n\t\tmenu.setOnMenuItemClickListener {\n\t\t\tviewModel.setSelectedBranch(branches.getOrNull(it.order))\n\t\t\ttrue\n\t\t}\n\t\tmenu.show()\n\t}\n\n\tprivate fun showFirstChaptersCountSelection(v: View) {\n\t\tval option = viewModel.chaptersSelectOptions.value.firstChapters ?: return\n\t\tval menu = PopupMenu(v.context, v)\n\t\tchaptersCount(option.maxAvailableCount).forEach { i ->\n\t\t\tmenu.menu.add(i.format())\n\t\t}\n\t\tmenu.setOnMenuItemClickListener {\n\t\t\tviewModel.setFirstChaptersCount(\n\t\t\t\tit.title?.toString()?.toIntOrNull() ?: return@setOnMenuItemClickListener false,\n\t\t\t)\n\t\t\ttrue\n\t\t}\n\t\tmenu.show()\n\t}\n\n\tprivate fun showUnreadChaptersCountSelection(v: View) {\n\t\tval option = viewModel.chaptersSelectOptions.value.unreadChapters ?: return\n\t\tval menu = PopupMenu(v.context, v)\n\t\tchaptersCount(option.maxAvailableCount).forEach { i ->\n\t\t\tmenu.menu.add(i.format())\n\t\t}\n\t\tmenu.menu.add(getString(R.string.chapters_all))\n\t\tmenu.setOnMenuItemClickListener {\n\t\t\tviewModel.setUnreadChaptersCount(it.title?.toString()?.toIntOrNull() ?: Int.MAX_VALUE)\n\t\t\ttrue\n\t\t}\n\t\tmenu.show()\n\t}\n\n\tprivate fun chaptersCount(max: Int) = sequence {\n\t\tyield(1)\n\t\tvar seed = 5\n\t\tvar step = 5\n\t\twhile (seed + step <= max) {\n\t\t\tyield(seed)\n\t\t\tstep = when {\n\t\t\t\tseed < 20 -> 5\n\t\t\t\tseed < 60 -> 10\n\t\t\t\telse -> 20\n\t\t\t}\n\t\t\tseed += step\n\t\t}\n\t\tif (seed < max) {\n\t\t\tyield(max)\n\t\t}\n\t}\n\n\tprivate class SnackbarResultListener(\n\t\tprivate val host: View,\n\t) : FragmentResultListener {\n\n\t\toverride fun onFragmentResult(requestKey: String, result: Bundle) {\n\t\t\tval isStarted = result.getBoolean(ARG_STARTED, true)\n\t\t\tval snackbar = Snackbar.make(\n\t\t\t\thost,\n\t\t\t\tif (isStarted) R.string.download_started else R.string.download_added,\n\t\t\t\tSnackbar.LENGTH_LONG,\n\t\t\t)\n\t\t\t(host.context.findActivity() as? BottomNavOwner)?.let {\n\t\t\t\tsnackbar.anchorView = it.bottomNav\n\t\t\t}\n\t\t\tval router = AppRouter.from(host)\n\t\t\tif (router != null) {\n\t\t\t\tsnackbar.setAction(R.string.details) { router.openDownloads() }\n\t\t\t}\n\t\t\tsnackbar.show()\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val RESULT_KEY = \"DOWNLOAD_STARTED\"\n\t\tprivate const val ARG_STARTED = \"started\"\n\t\tprivate const val KEY_CHECKED_OPTION = \"checked_opt\"\n\n\t\tfun registerCallback(\n\t\t\tfm: FragmentManager,\n\t\t\tlifecycleOwner: LifecycleOwner,\n\t\t\tsnackbarHost: View\n\t\t) = fm.setFragmentResultListener(RESULT_KEY, lifecycleOwner, SnackbarResultListener(snackbarHost))\n\n\t\tfun unregisterCallback(fm: FragmentManager) = fm.clearFragmentResultListener(RESULT_KEY)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/dialog/DownloadDialogViewModel.kt",
    "content": "package org.koitharu.kotatsu.download.ui.dialog\n\nimport androidx.collection.ArrayMap\nimport androidx.collection.ArraySet\nimport androidx.collection.MutableLongLongMap\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getPreferredBranch\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.DownloadFormat\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.download.ui.worker.DownloadTask\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.sizeOrZero\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport org.koitharu.kotatsu.settings.storage.DirectoryModel\nimport javax.inject.Inject\n\n@HiltViewModel\nclass DownloadDialogViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val scheduler: DownloadWorker.Scheduler,\n\tprivate val localStorageManager: LocalStorageManager,\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val settings: AppSettings,\n) : BaseViewModel() {\n\n\tval manga = savedStateHandle.require<Array<ParcelableManga>>(AppRouter.KEY_MANGA).map {\n\t\tit.manga\n\t}\n\tprivate val mangaDetails = suspendLazy {\n\t\tcoroutineScope {\n\t\t\tmanga.map { m ->\n\t\t\t\tasync { m.getDetails() }\n\t\t\t}.awaitAll()\n\t\t}\n\t}\n\tval onScheduled = MutableEventFlow<Boolean>()\n\tval defaultFormat = MutableStateFlow<DownloadFormat?>(null)\n\tval availableDestinations = MutableStateFlow(listOf(defaultDestination()))\n\tval chaptersSelectOptions = MutableStateFlow(\n\t\tChapterSelectOptions(\n\t\t\twholeManga = ChaptersSelectMacro.WholeManga(0),\n\t\t\twholeBranch = null,\n\t\t\tfirstChapters = null,\n\t\t\tunreadChapters = null,\n\t\t),\n\t)\n\tval isOptionsLoading = MutableStateFlow(true)\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tdefaultFormat.value = settings.preferredDownloadFormat\n\t\t}\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\ttry {\n\t\t\t\tloadAvailableOptions()\n\t\t\t} finally {\n\t\t\t\tisOptionsLoading.value = false\n\t\t\t}\n\t\t}\n\t\tloadAvailableDestinations()\n\t}\n\n\tfun confirm(\n\t\tstartNow: Boolean,\n\t\tchaptersMacro: ChaptersSelectMacro,\n\t\tformat: DownloadFormat?,\n\t\tdestination: DirectoryModel?,\n\t\tallowMetered: Boolean,\n\t) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval tasks = mangaDetails.get().map { m ->\n\t\t\t\tval chapters = checkNotNull(m.chapters) { \"Manga \\\"${m.title}\\\" cannot be loaded\" }\n\t\t\t\tm to DownloadTask(\n\t\t\t\t\tmangaId = m.id,\n\t\t\t\t\tisPaused = !startNow,\n\t\t\t\t\tisSilent = false,\n\t\t\t\t\tchaptersIds = chaptersMacro.getChaptersIds(m.id, chapters)?.toLongArray(),\n\t\t\t\t\tdestination = destination?.file,\n\t\t\t\t\tformat = format,\n\t\t\t\t\tallowMeteredNetwork = allowMetered,\n\t\t\t\t)\n\t\t\t}\n\t\t\tscheduler.schedule(tasks)\n\t\t\tonScheduled.call(startNow)\n\t\t}\n\t}\n\n\tfun setSelectedBranch(branch: String?) {\n\t\tval snapshot = chaptersSelectOptions.value\n\t\tchaptersSelectOptions.value = snapshot.copy(\n\t\t\twholeBranch = snapshot.wholeBranch?.copy(branch),\n\t\t)\n\t}\n\n\tfun setFirstChaptersCount(count: Int) {\n\t\tval snapshot = chaptersSelectOptions.value\n\t\tchaptersSelectOptions.value = snapshot.copy(\n\t\t\tfirstChapters = snapshot.firstChapters?.copy(count),\n\t\t)\n\t}\n\n\tfun setUnreadChaptersCount(count: Int) {\n\t\tval snapshot = chaptersSelectOptions.value\n\t\tchaptersSelectOptions.value = snapshot.copy(\n\t\t\tunreadChapters = snapshot.unreadChapters?.copy(count),\n\t\t)\n\t}\n\n\tprivate fun defaultDestination() = DirectoryModel(\n\t\ttitle = null,\n\t\ttitleRes = R.string.system_default,\n\t\tfile = null,\n\t\tisRemovable = false,\n\t\tisChecked = true,\n\t\tisAvailable = true,\n\t)\n\n\tprivate suspend fun loadAvailableOptions() {\n\t\tval details = mangaDetails.get()\n\t\tvar totalChapters = 0\n\t\tval branches = ArrayMap<String?, Int>()\n\t\tvar maxChapters = 0\n\t\tvar maxUnreadChapters = 0\n\t\tval preferredBranches = ArraySet<String?>(details.size)\n\t\tval currentChaptersIds = MutableLongLongMap(details.size)\n\n\t\tdetails.forEach { m ->\n\t\t\tval history = historyRepository.getOne(m)\n\t\t\tif (history != null) {\n\t\t\t\tcurrentChaptersIds[m.id] = history.chapterId\n\t\t\t\tval unreadChaptersCount = m.chapters?.dropWhile { it.id != history.chapterId }.sizeOrZero()\n\t\t\t\tmaxUnreadChapters = maxOf(maxUnreadChapters, unreadChaptersCount)\n\t\t\t} else {\n\t\t\t\tmaxUnreadChapters = maxOf(maxUnreadChapters, m.chapters.sizeOrZero())\n\t\t\t}\n\t\t\tmaxChapters = maxOf(maxChapters, m.chapters.sizeOrZero())\n\t\t\tpreferredBranches.add(m.getPreferredBranch(history))\n\t\t\tm.chapters?.forEach { c ->\n\t\t\t\ttotalChapters++\n\t\t\t\tbranches.increment(c.branch)\n\t\t\t}\n\t\t}\n\t\tval defaultBranch = preferredBranches.firstOrNull()\n\t\tchaptersSelectOptions.value = ChapterSelectOptions(\n\t\t\twholeManga = ChaptersSelectMacro.WholeManga(totalChapters),\n\t\t\twholeBranch = if (branches.size > 1) {\n\t\t\t\tChaptersSelectMacro.WholeBranch(\n\t\t\t\t\tbranches = branches,\n\t\t\t\t\tselectedBranch = defaultBranch,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tfirstChapters = if (maxChapters > 0) {\n\t\t\t\tChaptersSelectMacro.FirstChapters(\n\t\t\t\t\tchaptersCount = minOf(5, maxChapters),\n\t\t\t\t\tmaxAvailableCount = maxChapters,\n\t\t\t\t\tbranch = defaultBranch,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tunreadChapters = if (currentChaptersIds.isNotEmpty()) {\n\t\t\t\tChaptersSelectMacro.UnreadChapters(\n\t\t\t\t\tchaptersCount = minOf(5, maxUnreadChapters),\n\t\t\t\t\tmaxAvailableCount = maxUnreadChapters,\n\t\t\t\t\tcurrentChaptersIds = currentChaptersIds,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate fun loadAvailableDestinations() = launchJob(Dispatchers.Default) {\n\t\tval defaultDir = manga.mapToSet {\n\t\t\tlocalMangaRepository.getOutputDir(it, null)\n\t\t}.singleOrNull()\n\t\tval dirs = localStorageManager.getWriteableDirs()\n\t\tavailableDestinations.value = buildList(dirs.size + 1) {\n\t\t\tif (defaultDir == null) {\n\t\t\t\tadd(defaultDestination())\n\t\t\t} else if (defaultDir !in dirs) {\n\t\t\t\tadd(\n\t\t\t\t\tDirectoryModel(\n\t\t\t\t\t\ttitle = localStorageManager.getDirectoryDisplayName(defaultDir, isFullPath = false),\n\t\t\t\t\t\ttitleRes = 0,\n\t\t\t\t\t\tfile = defaultDir,\n\t\t\t\t\t\tisChecked = true,\n\t\t\t\t\t\tisAvailable = true,\n\t\t\t\t\t\tisRemovable = false,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdirs.mapTo(this) { dir ->\n\t\t\t\tDirectoryModel(\n\t\t\t\t\ttitle = localStorageManager.getDirectoryDisplayName(dir, isFullPath = false),\n\t\t\t\t\ttitleRes = 0,\n\t\t\t\t\tfile = dir,\n\t\t\t\t\tisChecked = dir == defaultDir,\n\t\t\t\t\tisAvailable = true,\n\t\t\t\t\tisRemovable = false,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun Manga.getDetails(): Manga = runCatchingCancellable {\n\t\tmangaRepositoryFactory.create(source).getDetails(this)\n\t}.onFailure { e ->\n\t\te.printStackTraceDebug()\n\t}.getOrDefault(this)\n\n\tprivate fun <T> MutableMap<T, Int>.increment(key: T) {\n\t\tput(key, getOrDefault(key, 0) + 1)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemAD.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport android.view.View\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.work.WorkInfo\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.setContentDescriptionAndTooltip\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemDownloadBinding\nimport org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter\nimport org.koitharu.kotatsu.download.ui.list.chapters.downloadChapterAD\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.util.format\n\nfun downloadItemAD(\n\tlifecycleOwner: LifecycleOwner,\n\tlistener: DownloadItemListener,\n) = adapterDelegateViewBinding<DownloadItemModel, ListModel, ItemDownloadBinding>(\n\t{ inflater, parent -> ItemDownloadBinding.inflate(inflater, parent, false) },\n) {\n\n\tval percentPattern = context.resources.getString(R.string.percent_string_pattern)\n\tvar chaptersJob: Job? = null\n\n\tval clickListener = object : View.OnClickListener, View.OnLongClickListener {\n\t\toverride fun onClick(v: View) {\n\t\t\twhen (v.id) {\n\t\t\t\tR.id.button_cancel -> listener.onCancelClick(item)\n\t\t\t\tR.id.button_resume -> listener.onResumeClick(item)\n\t\t\t\tR.id.button_skip -> listener.onSkipClick(item)\n\t\t\t\tR.id.button_skip_all -> listener.onSkipAllClick(item)\n\t\t\t\tR.id.button_pause -> listener.onPauseClick(item)\n\t\t\t\tR.id.button_expand -> listener.onExpandClick(item)\n\t\t\t\telse -> listener.onItemClick(item, v)\n\t\t\t}\n\t\t}\n\n\t\toverride fun onLongClick(v: View): Boolean {\n\t\t\treturn listener.onItemLongClick(item, v)\n\t\t}\n\t}\n\tval chaptersAdapter = BaseListAdapter<DownloadChapter>()\n\t\t.addDelegate(ListItemType.CHAPTER_LIST, downloadChapterAD())\n\n\tbinding.recyclerViewChapters.adapter = chaptersAdapter\n\tbinding.buttonCancel.setOnClickListener(clickListener)\n\tbinding.buttonPause.setOnClickListener(clickListener)\n\tbinding.buttonResume.setOnClickListener(clickListener)\n\tbinding.buttonSkip.setOnClickListener(clickListener)\n\tbinding.buttonSkipAll.setOnClickListener(clickListener)\n\tbinding.buttonExpand.setOnClickListener(clickListener)\n\titemView.setOnClickListener(clickListener)\n\titemView.setOnLongClickListener(clickListener)\n\n\tfun scrollToCurrentChapter() {\n\t\tval rv = binding.recyclerViewChapters\n\t\tif (!rv.isVisible) {\n\t\t\treturn\n\t\t}\n\t\tval chapters = chaptersAdapter.items\n\t\tif (chapters.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tval targetPos = item.chaptersDownloaded.coerceIn(chapters.indices)\n\t\t(rv.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(targetPos, rv.height / 3)\n\t}\n\n\tbind { payloads ->\n\t\tbinding.textViewTitle.text = item.manga?.title ?: getString(R.string.unknown)\n\t\tbinding.imageViewCover.setImageAsync(item.manga?.coverUrl, item.manga)\n\t\tif (chaptersJob == null || payloads.isEmpty()) {\n\t\t\tchaptersJob?.cancel()\n\t\t\tchaptersJob = lifecycleOwner.lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) {\n\t\t\t\titem.chapters.collect { chapters ->\n\t\t\t\t\tbinding.buttonExpand.isGone = chapters.isNullOrEmpty()\n\t\t\t\t\tchaptersAdapter.emit(chapters)\n\t\t\t\t\tscrollToCurrentChapter()\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED in payloads) {\n\t\t\tbinding.recyclerViewChapters.post {\n\t\t\t\tscrollToCurrentChapter()\n\t\t\t}\n\t\t}\n\t\tbinding.buttonExpand.isChecked = item.isExpanded\n\t\tbinding.buttonExpand.setContentDescriptionAndTooltip(if (item.isExpanded) R.string.collapse else R.string.expand)\n\t\tbinding.recyclerViewChapters.isVisible = item.isExpanded\n\t\twhen (item.workState) {\n\t\t\tWorkInfo.State.ENQUEUED,\n\t\t\tWorkInfo.State.BLOCKED -> {\n\t\t\t\tbinding.textViewStatus.setText(R.string.queued)\n\t\t\t\tbinding.progressBar.isIndeterminate = false\n\t\t\t\tbinding.progressBar.isVisible = false\n\t\t\t\tbinding.progressBar.isEnabled = true\n\t\t\t\tbinding.textViewPercent.isVisible = false\n\t\t\t\tbinding.textViewDetails.isVisible = false\n\t\t\t\tbinding.buttonCancel.isVisible = true\n\t\t\t\tbinding.buttonResume.isVisible = false\n\t\t\t\tbinding.buttonSkip.isVisible = false\n\t\t\t\tbinding.buttonSkipAll.isVisible = false\n\t\t\t\tbinding.buttonPause.isVisible = false\n\t\t\t}\n\n\t\t\tWorkInfo.State.RUNNING -> {\n\t\t\t\tbinding.textViewStatus.setText(\n\t\t\t\t\tif (item.isPaused) R.string.paused else R.string.manga_downloading_,\n\t\t\t\t)\n\t\t\t\tbinding.progressBar.isIndeterminate = item.isIndeterminate\n\t\t\t\tbinding.progressBar.isVisible = true\n\t\t\t\tbinding.progressBar.max = item.max\n\t\t\t\tbinding.progressBar.isEnabled = !item.isPaused\n\t\t\t\tbinding.progressBar.setProgressCompat(item.progress, payloads.isNotEmpty())\n\t\t\t\tbinding.textViewPercent.text = percentPattern.format((item.percent * 100f).format(1))\n\t\t\t\tbinding.textViewPercent.isVisible = true\n\t\t\t\tbinding.textViewDetails.textAndVisible = when {\n\t\t\t\t\titem.isPaused -> item.getErrorMessage(context)\n\t\t\t\t\titem.isStuck -> context.getString(R.string.stuck)\n\t\t\t\t\telse -> item.getEtaString()\n\t\t\t\t}\n\t\t\t\tbinding.buttonCancel.isVisible = true\n\t\t\t\tbinding.buttonResume.isVisible = item.isPaused\n\t\t\t\tbinding.buttonResume.setText(if (item.error == null) R.string.resume else R.string.retry)\n\t\t\t\tbinding.buttonSkip.isVisible = item.isPaused && item.error != null\n\t\t\t\tbinding.buttonSkipAll.isVisible = item.isPaused && item.error != null\n\t\t\t\tbinding.buttonPause.isVisible = item.canPause\n\t\t\t}\n\n\t\t\tWorkInfo.State.SUCCEEDED -> {\n\t\t\t\tbinding.textViewStatus.setText(R.string.download_complete)\n\t\t\t\tbinding.progressBar.isIndeterminate = false\n\t\t\t\tbinding.progressBar.isVisible = false\n\t\t\t\tbinding.progressBar.isEnabled = true\n\t\t\t\tbinding.textViewPercent.isVisible = false\n\t\t\t\tif (item.chaptersDownloaded > 0) {\n\t\t\t\t\tbinding.textViewDetails.text = context.resources.getQuantityStringSafe(\n\t\t\t\t\t\tR.plurals.chapters,\n\t\t\t\t\t\titem.chaptersDownloaded,\n\t\t\t\t\t\titem.chaptersDownloaded,\n\t\t\t\t\t)\n\t\t\t\t\tbinding.textViewDetails.isVisible = true\n\t\t\t\t} else {\n\t\t\t\t\tbinding.textViewDetails.isVisible = false\n\t\t\t\t}\n\t\t\t\tbinding.buttonCancel.isVisible = false\n\t\t\t\tbinding.buttonResume.isVisible = false\n\t\t\t\tbinding.buttonSkip.isVisible = false\n\t\t\t\tbinding.buttonSkipAll.isVisible = false\n\t\t\t\tbinding.buttonPause.isVisible = false\n\t\t\t}\n\n\t\t\tWorkInfo.State.FAILED -> {\n\t\t\t\tbinding.textViewStatus.setText(R.string.error_occurred)\n\t\t\t\tbinding.progressBar.isIndeterminate = false\n\t\t\t\tbinding.progressBar.isVisible = false\n\t\t\t\tbinding.progressBar.isEnabled = true\n\t\t\t\tbinding.textViewPercent.isVisible = false\n\t\t\t\tbinding.textViewDetails.textAndVisible = item.getErrorMessage(context)\n\t\t\t\tbinding.buttonCancel.isVisible = false\n\t\t\t\tbinding.buttonResume.isVisible = false\n\t\t\t\tbinding.buttonSkip.isVisible = false\n\t\t\t\tbinding.buttonSkipAll.isVisible = false\n\t\t\t\tbinding.buttonPause.isVisible = false\n\t\t\t}\n\n\t\t\tWorkInfo.State.CANCELLED -> {\n\t\t\t\tbinding.textViewStatus.setText(R.string.canceled)\n\t\t\t\tbinding.progressBar.isIndeterminate = false\n\t\t\t\tbinding.progressBar.isVisible = false\n\t\t\t\tbinding.progressBar.isEnabled = true\n\t\t\t\tbinding.textViewPercent.isVisible = false\n\t\t\t\tbinding.textViewDetails.isVisible = false\n\t\t\t\tbinding.buttonCancel.isVisible = false\n\t\t\t\tbinding.buttonResume.isVisible = false\n\t\t\t\tbinding.buttonSkip.isVisible = false\n\t\t\t\tbinding.buttonSkipAll.isVisible = false\n\t\t\t\tbinding.buttonPause.isVisible = false\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemListener.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\n\ninterface DownloadItemListener : OnListItemClickListener<DownloadItemModel> {\n\n\tfun onCancelClick(item: DownloadItemModel)\n\n\tfun onPauseClick(item: DownloadItemModel)\n\n\tfun onResumeClick(item: DownloadItemModel)\n\n\tfun onSkipClick(item: DownloadItemModel)\n\n\tfun onSkipAllClick(item: DownloadItemModel)\n\n\tfun onExpandClick(item: DownloadItemModel)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemModel.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.text.format.DateUtils\nimport androidx.core.text.bold\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.color\nimport androidx.work.WorkInfo\nimport kotlinx.coroutines.flow.StateFlow\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.time.Instant\nimport java.util.UUID\nimport androidx.appcompat.R as appcompatR\n\ndata class DownloadItemModel(\n\tval id: UUID,\n\tval workState: WorkInfo.State,\n\tval isIndeterminate: Boolean,\n\tval isPaused: Boolean,\n\tval manga: Manga?,\n\tval error: String?,\n\tval max: Int,\n\tval progress: Int,\n\tval eta: Long,\n\tval isStuck: Boolean,\n\tval timestamp: Instant,\n\tval chaptersDownloaded: Int,\n\tval isExpanded: Boolean,\n\tval chapters: StateFlow<List<DownloadChapter>?>,\n) : ListModel, Comparable<DownloadItemModel> {\n\n\tval percent: Float\n\t\tget() = if (max > 0) progress / max.toFloat() else 0f\n\n\tval hasEta: Boolean\n\t\tget() = workState == WorkInfo.State.RUNNING && !isPaused && eta > 0L\n\n\tval canPause: Boolean\n\t\tget() = workState == WorkInfo.State.RUNNING && !isPaused && error == null\n\n\tval canResume: Boolean\n\t\tget() = workState == WorkInfo.State.RUNNING && isPaused\n\n\tfun getEtaString(): CharSequence? = if (hasEta) {\n\t\tDateUtils.getRelativeTimeSpanString(\n\t\t\teta,\n\t\t\tSystem.currentTimeMillis(),\n\t\t\tDateUtils.SECOND_IN_MILLIS,\n\t\t)\n\t} else {\n\t\tnull\n\t}\n\n\tfun getErrorMessage(context: Context): CharSequence? = if (error != null) {\n\t\tbuildSpannedString {\n\t\t\tbold {\n\t\t\t\tcolor(context.getThemeColor(appcompatR.attr.colorError, Color.RED)) {\n\t\t\t\t\tappend(error)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tnull\n\t}\n\n\toverride fun compareTo(other: DownloadItemModel): Int {\n\t\treturn timestamp compareTo other.timestamp\n\t}\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is DownloadItemModel && other.id == id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is DownloadItemModel -> super.getChangePayload(previousState)\n\t\tworkState != previousState.workState -> null\n\t\tisExpanded != previousState.isExpanded -> ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\telse -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.graphics.Insets\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport coil3.ImageLoader\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.databinding.ActivityDownloadsBinding\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),\n\tDownloadItemListener,\n\tListSelectionController.Callback {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\t@Inject\n\tlateinit var scheduler: DownloadWorker.Scheduler\n\n\tprivate val viewModel by viewModels<DownloadsViewModel>()\n\tprivate lateinit var selectionController: ListSelectionController\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityDownloadsBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tval downloadsAdapter = DownloadsAdapter(this, this)\n\t\tval decoration = TypedListSpacingDecoration(this, false)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = delegate,\n\t\t\tdecoration = DownloadsSelectionDecoration(this),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = this,\n\t\t)\n\t\twith(viewBinding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\taddItemDecoration(decoration)\n\t\t\tadapter = downloadsAdapter\n\t\t\tselectionController.attachToRecyclerView(this)\n\t\t\tRecyclerScrollKeeper(this).attach()\n\t\t}\n\t\taddMenuProvider(DownloadsMenuProvider(this, viewModel))\n\t\tviewModel.items.observe(this, downloadsAdapter)\n\t\tviewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView))\n\t\tval menuInvalidator = MenuInvalidator(this)\n\t\tviewModel.hasActiveWorks.observe(this, menuInvalidator)\n\t\tviewModel.hasPausedWorks.observe(this, menuInvalidator)\n\t\tviewModel.hasCancellableWorks.observe(this, menuInvalidator)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tviewBinding.recyclerView.updatePadding(\n\t\t\tleft = bars.left,\n\t\t\tright = bars.right,\n\t\t\tbottom = bars.bottom,\n\t\t)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = bars.left,\n\t\t\tright = bars.right,\n\t\t\ttop = bars.top,\n\t\t)\n\t\treturn WindowInsetsCompat.Builder(insets)\n\t\t\t.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)\n\t\t\t.build()\n\t}\n\n\toverride fun onItemClick(item: DownloadItemModel, view: View) {\n\t\tif (selectionController.onItemClick(item.id.mostSignificantBits)) {\n\t\t\treturn\n\t\t}\n\t\trouter.openDetails(item.manga ?: return)\n\t}\n\n\toverride fun onItemLongClick(item: DownloadItemModel, view: View): Boolean {\n\t\treturn selectionController.onItemLongClick(view, item.id.mostSignificantBits)\n\t}\n\n\toverride fun onItemContextClick(item: DownloadItemModel, view: View): Boolean {\n\t\treturn selectionController.onItemContextClick(view, item.id.mostSignificantBits)\n\t}\n\n\toverride fun onExpandClick(item: DownloadItemModel) {\n\t\tif (!selectionController.onItemClick(item.id.mostSignificantBits)) {\n\t\t\tviewModel.expandCollapse(item)\n\t\t}\n\t}\n\n\toverride fun onCancelClick(item: DownloadItemModel) {\n\t\tviewModel.cancel(item.id)\n\t}\n\n\toverride fun onPauseClick(item: DownloadItemModel) {\n\t\tscheduler.pause(item.id)\n\t}\n\n\toverride fun onResumeClick(item: DownloadItemModel) {\n\t\tscheduler.resume(item.id)\n\t}\n\n\toverride fun onSkipClick(item: DownloadItemModel) {\n\t\tscheduler.skip(item.id)\n\t}\n\n\toverride fun onSkipAllClick(item: DownloadItemModel) {\n\t\tscheduler.skipAll(item.id)\n\t}\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\tviewBinding.recyclerView.invalidateItemDecorations()\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_downloads, menu)\n\t\treturn true\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_resume -> {\n\t\t\t\tviewModel.resume(controller.snapshot())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_pause -> {\n\t\t\t\tviewModel.pause(controller.snapshot())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_cancel -> {\n\t\t\t\tviewModel.cancel(controller.snapshot())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_remove -> {\n\t\t\t\tviewModel.remove(controller.snapshot())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_select_all -> {\n\t\t\t\tcontroller.addAll(viewModel.allIds())\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\toverride fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {\n\t\tval snapshot = viewModel.snapshot(controller.peekCheckedIds())\n\t\tvar canPause = true\n\t\tvar canResume = true\n\t\tvar canCancel = true\n\t\tvar canRemove = true\n\t\tfor (item in snapshot) {\n\t\t\tcanPause = canPause and item.canPause\n\t\t\tcanResume = canResume and item.canResume\n\t\t\tcanCancel = canCancel and !item.workState.isFinished\n\t\t\tcanRemove = canRemove and item.workState.isFinished\n\t\t}\n\t\tmenu.findItem(R.id.action_pause)?.isVisible = canPause\n\t\tmenu.findItem(R.id.action_resume)?.isVisible = canResume\n\t\tmenu.findItem(R.id.action_cancel)?.isVisible = canCancel\n\t\tmenu.findItem(R.id.action_remove)?.isVisible = canRemove\n\t\treturn super.onPrepareActionMode(controller, mode, menu)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.listHeaderAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass DownloadsAdapter(\n\tlifecycleOwner: LifecycleOwner,\n\tlistener: DownloadItemListener,\n) : BaseListAdapter<ListModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.DOWNLOAD, downloadItemAD(lifecycleOwner, listener))\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null))\n\t\taddDelegate(ListItemType.HEADER, listHeaderAD(null))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.FragmentActivity\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\n\nclass DownloadsMenuProvider(\n\tprivate val activity: FragmentActivity,\n\tprivate val viewModel: DownloadsViewModel,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_downloads, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\twhen (menuItem.itemId) {\n\t\t\tR.id.action_pause -> viewModel.pauseAll()\n\t\t\tR.id.action_resume -> viewModel.resumeAll()\n\t\t\tR.id.action_cancel_all -> confirmCancelAll()\n\t\t\tR.id.action_remove_completed -> confirmRemoveCompleted()\n\t\t\tR.id.action_settings -> activity.router.openDownloadsSetting()\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tmenu.findItem(R.id.action_pause)?.isVisible = viewModel.hasActiveWorks.value == true\n\t\tmenu.findItem(R.id.action_resume)?.isVisible = viewModel.hasPausedWorks.value == true\n\t\tmenu.findItem(R.id.action_cancel_all)?.isVisible = viewModel.hasCancellableWorks.value == true\n\t}\n\n\tprivate fun confirmCancelAll() {\n\t\tbuildAlertDialog(activity, isCentered = true) {\n\t\t\tsetTitle(R.string.cancel_all)\n\t\t\tsetMessage(R.string.cancel_all_downloads_confirm)\n\t\t\tsetIcon(R.drawable.ic_cancel_multiple)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.confirm) { _, _ -> viewModel.cancelAll() }\n\t\t}.show()\n\t}\n\n\tprivate fun confirmRemoveCompleted() {\n\t\tbuildAlertDialog(activity, isCentered = true) {\n\t\t\tsetTitle(R.string.remove_completed)\n\t\t\tsetMessage(R.string.remove_completed_downloads_confirm)\n\t\t\tsetIcon(R.drawable.ic_clear_all)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.clear) { _, _ -> viewModel.removeCompleted() }\n\t\t}.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.view.View\nimport androidx.cardview.widget.CardView\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.ColorUtils\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.NO_ID\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nclass DownloadsSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle)\n\tprivate val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.card_indicator_offset)\n\tprivate val iconSize = context.resources.getDimensionPixelOffset(R.dimen.card_indicator_size)\n\tprivate val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)\n\tprivate val fillColor = ColorUtils.setAlphaComponent(\n\t\tColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),\n\t\t0x74,\n\t)\n\tprivate val defaultRadius = context.resources.getDimension(R.dimen.list_selector_corner)\n\n\tinit {\n\t\thasBackground = false\n\t\thasForeground = true\n\t\tisIncludeDecorAndMargins = false\n\n\t\tpaint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)\n\t\tcheckIcon?.setTint(strokeColor)\n\t}\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return NO_ID\n\t\tval item = holder.getItem(DownloadItemModel::class.java) ?: return NO_ID\n\t\treturn item.id.mostSignificantBits\n\t}\n\n\toverride fun onDrawForeground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\tval isCard = child is CardView\n\t\tval radius = (child as? CardView)?.radius ?: defaultRadius\n\t\tpaint.color = fillColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t\tpaint.color = strokeColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t\tif (isCard) {\n\t\t\tcheckIcon?.run {\n\t\t\t\tsetBounds(\n\t\t\t\t\t(bounds.right - iconSize - iconOffset).toInt(),\n\t\t\t\t\t(bounds.top + iconOffset).toInt(),\n\t\t\t\t\t(bounds.right - iconOffset).toInt(),\n\t\t\t\t\t(bounds.top + iconOffset + iconSize).toInt(),\n\t\t\t\t)\n\t\t\t\tdraw(canvas)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list\n\nimport androidx.collection.ArrayMap\nimport androidx.collection.LongSet\nimport androidx.collection.LongSparseArray\nimport androidx.collection.getOrElse\nimport androidx.collection.set\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkInfo\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.plus\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.model.DateTimeAgo\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.calculateTimeAgo\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.isEmpty\nimport org.koitharu.kotatsu.download.domain.DownloadState\nimport org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.util.LinkedList\nimport java.util.UUID\nimport javax.inject.Inject\n\n@HiltViewModel\nclass DownloadsViewModel @Inject constructor(\n\tprivate val workScheduler: DownloadWorker.Scheduler,\n\tprivate val mangaDataRepository: MangaDataRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\t@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,\n\tprivate val localMangaRepository: LocalMangaRepository,\n) : BaseViewModel() {\n\n\tprivate val mangaCache = LongSparseArray<Manga>()\n\tprivate val cacheMutex = Mutex()\n\tprivate val expanded = MutableStateFlow(emptySet<UUID>())\n\tprivate val chaptersCache = ArrayMap<UUID, StateFlow<List<DownloadChapter>?>>()\n\n\tprivate val works = combine(\n\t\tworkScheduler.observeWorks(),\n\t\texpanded,\n\t) { list, exp ->\n\t\tlist.toDownloadsList(exp)\n\t}.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)\n\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\n\tval items = works.map {\n\t\tit?.toUiList() ?: listOf(LoadingState)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tval hasPausedWorks = works.map {\n\t\tit?.any { x -> x.canResume } == true\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), false)\n\n\tval hasActiveWorks = works.map {\n\t\tit?.any { x -> x.canPause } == true\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), false)\n\n\tval hasCancellableWorks = works.map {\n\t\tit?.any { x -> !x.workState.isFinished } == true\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), false)\n\n\tfun cancel(id: UUID) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tworkScheduler.cancel(id)\n\t\t}\n\t}\n\n\tfun cancel(ids: Set<Long>) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval snapshot = works.value ?: return@launchJob\n\t\t\tfor (work in snapshot) {\n\t\t\t\tif (work.id.mostSignificantBits in ids) {\n\t\t\t\t\tworkScheduler.cancel(work.id)\n\t\t\t\t}\n\t\t\t}\n\t\t\tonActionDone.call(ReversibleAction(R.string.downloads_cancelled, null))\n\t\t}\n\t}\n\n\tfun cancelAll() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tworkScheduler.cancelAll()\n\t\t\tonActionDone.call(ReversibleAction(R.string.downloads_cancelled, null))\n\t\t}\n\t}\n\n\tfun pause(ids: Set<Long>) {\n\t\tval snapshot = works.value ?: return\n\t\tfor (work in snapshot) {\n\t\t\tif (work.id.mostSignificantBits in ids) {\n\t\t\t\tworkScheduler.pause(work.id)\n\t\t\t}\n\t\t}\n\t\tonActionDone.call(ReversibleAction(R.string.downloads_paused, null))\n\t}\n\n\tfun pauseAll() {\n\t\tval snapshot = works.value ?: return\n\t\tvar isPaused = false\n\t\tfor (work in snapshot) {\n\t\t\tif (work.canPause) {\n\t\t\t\tworkScheduler.pause(work.id)\n\t\t\t\tisPaused = true\n\t\t\t}\n\t\t}\n\t\tif (isPaused) {\n\t\t\tonActionDone.call(ReversibleAction(R.string.downloads_paused, null))\n\t\t}\n\t}\n\n\tfun resumeAll() {\n\t\tval snapshot = works.value ?: return\n\t\tvar isResumed = false\n\t\tfor (work in snapshot) {\n\t\t\tif (work.workState == WorkInfo.State.RUNNING && work.isPaused) {\n\t\t\t\tworkScheduler.resume(work.id)\n\t\t\t\tisResumed = true\n\t\t\t}\n\t\t}\n\t\tif (isResumed) {\n\t\t\tonActionDone.call(ReversibleAction(R.string.downloads_resumed, null))\n\t\t}\n\t}\n\n\tfun resume(ids: Set<Long>) {\n\t\tval snapshot = works.value ?: return\n\t\tfor (work in snapshot) {\n\t\t\tif (work.id.mostSignificantBits in ids) {\n\t\t\t\tworkScheduler.resume(work.id)\n\t\t\t}\n\t\t}\n\t\tonActionDone.call(ReversibleAction(R.string.downloads_resumed, null))\n\t}\n\n\tfun remove(ids: Set<Long>) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval snapshot = works.value ?: return@launchJob\n\t\t\tval uuids = HashSet<UUID>(ids.size)\n\t\t\tfor (work in snapshot) {\n\t\t\t\tif (work.id.mostSignificantBits in ids) {\n\t\t\t\t\tuuids.add(work.id)\n\t\t\t\t}\n\t\t\t}\n\t\t\tworkScheduler.delete(uuids)\n\t\t\tonActionDone.call(ReversibleAction(R.string.downloads_removed, null))\n\t\t}\n\t}\n\n\tfun removeCompleted() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tworkScheduler.removeCompleted()\n\t\t\tonActionDone.call(ReversibleAction(R.string.downloads_removed, null))\n\t\t}\n\t}\n\n\tfun snapshot(ids: LongSet): Collection<DownloadItemModel> {\n\t\treturn works.value?.filterTo(ArrayList(ids.size)) { x -> x.id.mostSignificantBits in ids }.orEmpty()\n\t}\n\n\tfun allIds(): Set<Long> = works.value?.mapToSet {\n\t\tit.id.mostSignificantBits\n\t} ?: emptySet()\n\n\tfun expandCollapse(item: DownloadItemModel) {\n\t\texpanded.update {\n\t\t\tif (item.id in it) {\n\t\t\t\tit - item.id\n\t\t\t} else {\n\t\t\t\tit + item.id\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun List<WorkInfo>.toDownloadsList(exp: Set<UUID>): List<DownloadItemModel> {\n\t\tif (isEmpty()) {\n\t\t\treturn emptyList()\n\t\t}\n\t\tval list = mapNotNullTo(ArrayList(size)) { it.toUiModel(it.id in exp) }\n\t\tlist.sortByDescending { it.timestamp }\n\t\treturn list\n\t}\n\n\tprivate fun List<DownloadItemModel>.toUiList(): List<ListModel> {\n\t\tif (isEmpty()) {\n\t\t\treturn emptyStateList()\n\t\t}\n\t\tval queued = LinkedList<ListModel>()\n\t\tval running = LinkedList<ListModel>()\n\t\tval destination = ArrayDeque<ListModel>((size * 1.4).toInt())\n\t\tvar prevDate: DateTimeAgo? = null\n\t\tfor (item in this) {\n\t\t\twhen (item.workState) {\n\t\t\t\tWorkInfo.State.RUNNING -> running += item\n\t\t\t\tWorkInfo.State.BLOCKED,\n\t\t\t\tWorkInfo.State.ENQUEUED -> queued += item\n\n\t\t\t\telse -> {\n\t\t\t\t\tval date = calculateTimeAgo(item.timestamp)\n\t\t\t\t\tif (prevDate != date) {\n\t\t\t\t\t\tdestination += if (date != null) {\n\t\t\t\t\t\t\tListHeader(date)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tListHeader(R.string.unknown)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tprevDate = date\n\t\t\t\t\tdestination += item\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (running.isNotEmpty()) {\n\t\t\trunning.addFirst(ListHeader(R.string.in_progress))\n\t\t}\n\t\tdestination.addAll(0, running)\n\t\tif (queued.isNotEmpty()) {\n\t\t\tqueued.addFirst(ListHeader(R.string.queued))\n\t\t}\n\t\tdestination.addAll(0, queued)\n\t\treturn destination\n\t}\n\n\tprivate suspend fun WorkInfo.toUiModel(isExpanded: Boolean): DownloadItemModel? {\n\t\tval workData = outputData.takeUnless { it.isEmpty }\n\t\t\t?: progress.takeUnless { it.isEmpty }\n\t\t\t?: workScheduler.getInputData(id)\n\t\t\t?: return null\n\t\tval mangaId = DownloadState.getMangaId(workData)\n\t\tif (mangaId == 0L) return null\n\t\tval manga = getManga(mangaId) ?: return null\n\t\tval chapters = synchronized(chaptersCache) {\n\t\t\tchaptersCache.getOrPut(id) {\n\t\t\t\tobserveChapters(manga, id)\n\t\t\t}\n\t\t}\n\t\treturn DownloadItemModel(\n\t\t\tid = id,\n\t\t\tworkState = state,\n\t\t\tmanga = manga,\n\t\t\terror = DownloadState.getError(workData),\n\t\t\tisIndeterminate = DownloadState.isIndeterminate(workData),\n\t\t\tisPaused = DownloadState.isPaused(workData),\n\t\t\tmax = DownloadState.getMax(workData),\n\t\t\tprogress = DownloadState.getProgress(workData),\n\t\t\teta = DownloadState.getEta(workData),\n\t\t\tisStuck = DownloadState.isStuck(workData),\n\t\t\ttimestamp = DownloadState.getTimestamp(workData),\n\t\t\tchaptersDownloaded = DownloadState.getDownloadedChapters(workData),\n\t\t\tisExpanded = isExpanded,\n\t\t\tchapters = chapters,\n\t\t)\n\t}\n\n\tprivate fun emptyStateList() = listOf(\n\t\tEmptyState(\n\t\t\ticon = R.drawable.ic_empty_common,\n\t\t\ttextPrimary = R.string.text_downloads_list_holder,\n\t\t\ttextSecondary = 0,\n\t\t\tactionStringRes = 0,\n\t\t),\n\t)\n\n\tprivate suspend fun getManga(mangaId: Long): Manga? {\n\t\tmangaCache[mangaId]?.let {\n\t\t\treturn it\n\t\t}\n\t\treturn cacheMutex.withLock {\n\t\t\tmangaCache.getOrElse(mangaId) {\n\t\t\t\tmangaDataRepository.findMangaById(mangaId, withChapters = true)?.also {\n\t\t\t\t\tmangaCache[mangaId] = it\n\t\t\t\t} ?: return null\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun observeChapters(manga: Manga, workId: UUID): StateFlow<List<DownloadChapter>?> = flow {\n\t\tval chapterIds = workScheduler.getTask(workId)?.chaptersIds\n\t\tval chapters = (tryLoad(manga) ?: manga).chapters ?: return@flow\n\n\t\tsuspend fun mapChapters(): List<DownloadChapter> {\n\t\t\tval size = chapterIds?.size ?: chapters.size\n\t\t\tval localChapters =\n\t\t\t\tlocalMangaRepository.findSavedManga(manga)?.manga?.chapters?.mapToSet { it.id }.orEmpty()\n\t\t\treturn chapters.mapNotNullTo(ArrayList(size)) {\n\t\t\t\tif (chapterIds == null || it.id in chapterIds) {\n\t\t\t\t\tDownloadChapter(\n\t\t\t\t\t\tnumber = it.numberString(),\n\t\t\t\t\t\tname = it.name,\n\t\t\t\t\t\tisDownloaded = it.id in localChapters,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\temit(mapChapters())\n\t\tlocalStorageChanges.collect {\n\t\t\tif (it?.manga?.id == manga.id) {\n\t\t\t\temit(mapChapters())\n\t\t\t}\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)\n\n\tprivate suspend fun tryLoad(manga: Manga) = runCatchingCancellable {\n\t\tmangaRepositoryFactory.create(manga.source).getDetails(manga)\n\t}.getOrNull()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/chapters/DownloadChapter.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list.chapters\n\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class DownloadChapter(\n\tval number: String?,\n\tval name: String,\n\tval isDownloaded: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is DownloadChapter && other.name == name\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\treturn if (previousState is DownloadChapter && previousState.name == name && previousState.number == number) {\n\t\t\tListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/chapters/DownloadChapterAD.kt",
    "content": "package org.koitharu.kotatsu.download.ui.list.chapters\n\nimport androidx.core.content.ContextCompat\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.drawableEnd\nimport org.koitharu.kotatsu.databinding.ItemChapterDownloadBinding\n\nfun downloadChapterAD() = adapterDelegateViewBinding<DownloadChapter, DownloadChapter, ItemChapterDownloadBinding>(\n\t{ layoutInflater, parent -> ItemChapterDownloadBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tval iconDone = ContextCompat.getDrawable(context, R.drawable.ic_check)\n\n\tbind {\n\t\tbinding.textViewNumber.text = item.number\n\t\tbinding.textViewTitle.text = item.name\n\t\tbinding.textViewTitle.drawableEnd = if (item.isDownloaded) iconDone else null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt",
    "content": "package org.koitharu.kotatsu.download.ui.worker\n\nimport android.app.Notification\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.drawable.Drawable\nimport android.text.format.DateUtils\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.graphics.drawable.toBitmap\nimport androidx.work.WorkManager\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport coil3.request.allowHardware\nimport coil3.size.Scale\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ErrorReporterReceiver\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow\nimport org.koitharu.kotatsu.core.util.ext.getNotificationIconSize\nimport org.koitharu.kotatsu.core.util.ext.isReportable\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.download.domain.DownloadState\nimport org.koitharu.kotatsu.download.ui.list.DownloadsActivity\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.format\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.util.UUID\nimport androidx.appcompat.R as appcompatR\n\nprivate const val CHANNEL_ID_DEFAULT = \"download\"\nprivate const val CHANNEL_ID_SILENT = \"download_bg\"\nprivate const val GROUP_ID = \"downloads\"\n\nclass DownloadNotificationFactory @AssistedInject constructor(\n\t@LocalizedAppContext private val context: Context,\n\tprivate val workManager: WorkManager,\n\tprivate val coil: ImageLoader,\n\t@Assisted private val uuid: UUID,\n\t@Assisted val isSilent: Boolean,\n) {\n\n\tprivate val covers = HashMap<Manga, Drawable>() // TODO cache\n\tprivate val builder = NotificationCompat.Builder(context, if (isSilent) CHANNEL_ID_SILENT else CHANNEL_ID_DEFAULT)\n\tprivate val mutex = Mutex()\n\n\tprivate val queueIntent = PendingIntentCompat.getActivity(\n\t\tcontext,\n\t\t0,\n\t\tIntent(context, DownloadsActivity::class.java),\n\t\t0,\n\t\tfalse,\n\t)\n\n\tprivate val actionCancel by lazy {\n\t\tNotificationCompat.Action(\n\t\t\tappcompatR.drawable.abc_ic_clear_material,\n\t\t\tcontext.getString(android.R.string.cancel),\n\t\t\tworkManager.createCancelPendingIntent(uuid),\n\t\t)\n\t}\n\n\tprivate val actionPause by lazy {\n\t\tNotificationCompat.Action(\n\t\t\tR.drawable.ic_action_pause,\n\t\t\tcontext.getString(R.string.pause),\n\t\t\tPausingReceiver.createPausePendingIntent(context, uuid),\n\t\t)\n\t}\n\n\tprivate val actionResume by lazy {\n\t\tNotificationCompat.Action(\n\t\t\tR.drawable.ic_action_resume,\n\t\t\tcontext.getString(R.string.resume),\n\t\t\tPausingReceiver.createResumePendingIntent(context, uuid),\n\t\t)\n\t}\n\n\tprivate val actionRetry by lazy {\n\t\tNotificationCompat.Action(\n\t\t\tR.drawable.ic_retry,\n\t\t\tcontext.getString(R.string.retry),\n\t\t\tactionResume.actionIntent,\n\t\t)\n\t}\n\n\tprivate val actionSkip by lazy {\n\t\tNotificationCompat.Action(\n\t\t\tR.drawable.ic_action_skip,\n\t\t\tcontext.getString(R.string.skip),\n\t\t\tPausingReceiver.createSkipPendingIntent(context, uuid),\n\t\t)\n\t}\n\n\tinit {\n\t\tcreateChannels()\n\t\tbuilder.setOnlyAlertOnce(true)\n\t\tbuilder.setDefaults(0)\n\t\tbuilder.foregroundServiceBehavior = if (isSilent) {\n\t\t\tNotificationCompat.FOREGROUND_SERVICE_DEFERRED\n\t\t} else {\n\t\t\tNotificationCompat.FOREGROUND_SERVICE_IMMEDIATE\n\t\t}\n\t\tbuilder.setSilent(true)\n\t\tbuilder.setGroup(GROUP_ID)\n\t\tbuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)\n\t\tbuilder.priority = if (isSilent) NotificationCompat.PRIORITY_MIN else NotificationCompat.PRIORITY_DEFAULT\n\t}\n\n\tsuspend fun create(state: DownloadState?): Notification = mutex.withLock {\n\t\tif (state == null) {\n\t\t\tbuilder.setContentTitle(context.getString(R.string.manga_downloading_))\n\t\t\tbuilder.setContentText(context.getString(R.string.preparing_))\n\t\t} else {\n\t\t\tbuilder.setContentTitle(state.manga.title)\n\t\t\tbuilder.setContentText(context.getString(R.string.manga_downloading_))\n\t\t}\n\t\tbuilder.setProgress(1, 0, true)\n\t\tbuilder.setSmallIcon(android.R.drawable.stat_sys_download)\n\t\tbuilder.setContentIntent(queueIntent)\n\t\tbuilder.setStyle(null)\n\t\tbuilder.setLargeIcon(if (state != null) getCover(state.manga)?.toBitmap() else null)\n\t\tbuilder.clearActions()\n\t\tbuilder.setSubText(null)\n\t\tbuilder.setShowWhen(false)\n\t\tbuilder.setVisibility(\n\t\t\tif (state != null && state.manga.isNsfw()) {\n\t\t\t\tNotificationCompat.VISIBILITY_SECRET\n\t\t\t} else {\n\t\t\t\tNotificationCompat.VISIBILITY_PRIVATE\n\t\t\t},\n\t\t)\n\t\twhen {\n\t\t\tstate == null -> Unit\n\t\t\tstate.localManga != null -> { // downloaded, final state\n\t\t\t\tbuilder.setProgress(0, 0, false)\n\t\t\t\tbuilder.setContentText(context.getString(R.string.download_complete))\n\t\t\t\tbuilder.setContentIntent(createMangaIntent(context, state.localManga.manga))\n\t\t\t\tbuilder.setAutoCancel(true)\n\t\t\t\tbuilder.setSmallIcon(android.R.drawable.stat_sys_download_done)\n\t\t\t\tbuilder.setCategory(null)\n\t\t\t\tbuilder.setStyle(null)\n\t\t\t\tbuilder.setOngoing(false)\n\t\t\t\tbuilder.setShowWhen(true)\n\t\t\t\tbuilder.setWhen(System.currentTimeMillis())\n\t\t\t}\n\n\t\t\tstate.isStopped -> {\n\t\t\t\tbuilder.setProgress(0, 0, false)\n\t\t\t\tbuilder.setContentText(context.getString(R.string.queued))\n\t\t\t\tbuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS)\n\t\t\t\tbuilder.setStyle(null)\n\t\t\t\tbuilder.setOngoing(true)\n\t\t\t\tbuilder.setSmallIcon(R.drawable.ic_stat_paused)\n\t\t\t\tbuilder.addAction(actionCancel)\n\t\t\t}\n\n\t\t\tstate.isPaused -> { // paused (with error or manually)\n\t\t\t\tbuilder.setProgress(state.max, state.progress, false)\n\t\t\t\tval percent = if (state.percent >= 0) {\n\t\t\t\t\tcontext.getString(R.string.percent_string_pattern, (state.percent * 100).format())\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t\tif (state.errorMessage != null) {\n\t\t\t\t\tbuilder.setContentText(\n\t\t\t\t\t\tcontext.getString(\n\t\t\t\t\t\t\tR.string.download_summary_pattern,\n\t\t\t\t\t\t\tpercent,\n\t\t\t\t\t\t\tstate.errorMessage,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tbuilder.setContentText(percent)\n\t\t\t\t}\n\t\t\t\tbuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS)\n\t\t\t\tbuilder.setStyle(null)\n\t\t\t\tbuilder.setOngoing(true)\n\t\t\t\tbuilder.setSmallIcon(R.drawable.ic_stat_paused)\n\t\t\t\tbuilder.addAction(actionCancel)\n\t\t\t\tif (state.errorMessage != null) {\n\t\t\t\t\tbuilder.addAction(actionRetry)\n\t\t\t\t\tbuilder.addAction(actionSkip)\n\t\t\t\t} else {\n\t\t\t\t\tbuilder.addAction(actionResume)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstate.error != null -> { // error, final state\n\t\t\t\tbuilder.setProgress(0, 0, false)\n\t\t\t\tbuilder.setSmallIcon(android.R.drawable.stat_notify_error)\n\t\t\t\tbuilder.setSubText(context.getString(R.string.error))\n\t\t\t\tbuilder.setContentText(state.errorMessage)\n\t\t\t\tbuilder.setAutoCancel(true)\n\t\t\t\tbuilder.setOngoing(false)\n\t\t\t\tbuilder.setCategory(NotificationCompat.CATEGORY_ERROR)\n\t\t\t\tbuilder.setShowWhen(true)\n\t\t\t\tbuilder.setWhen(System.currentTimeMillis())\n\t\t\t\tbuilder.setStyle(NotificationCompat.BigTextStyle().bigText(state.errorMessage))\n\t\t\t\tif (state.error.isReportable()) {\n\t\t\t\t\tErrorReporterReceiver.getPendingIntent(context, state.error)?.let { reportIntent ->\n\t\t\t\t\t\tbuilder.addAction(\n\t\t\t\t\t\t\tNotificationCompat.Action(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tcontext.getString(R.string.report),\n\t\t\t\t\t\t\t\treportIntent,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tbuilder.setProgress(state.max, state.progress, false)\n\t\t\t\tbuilder.setContentText(getProgressString(state.percent, state.eta, state.isStuck))\n\t\t\t\tbuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS)\n\t\t\t\tbuilder.setStyle(null)\n\t\t\t\tbuilder.setOngoing(true)\n\t\t\t\tbuilder.addAction(actionCancel)\n\t\t\t\tbuilder.addAction(actionPause)\n\t\t\t}\n\t\t}\n\t\treturn builder.build()\n\t}\n\n\tprivate fun getProgressString(percent: Float, eta: Long, isStuck: Boolean): CharSequence? {\n\t\tval percentString = if (percent >= 0f) {\n\t\t\tcontext.getString(R.string.percent_string_pattern, (percent * 100).format())\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t\tval etaString = when {\n\t\t\teta <= 0L -> null\n\t\t\tisStuck -> context.getString(R.string.stuck)\n\t\t\telse -> DateUtils.getRelativeTimeSpanString(\n\t\t\t\teta,\n\t\t\t\tSystem.currentTimeMillis(),\n\t\t\t\tDateUtils.SECOND_IN_MILLIS,\n\t\t\t)\n\t\t}\n\t\treturn when {\n\t\t\tpercentString == null && etaString == null -> null\n\t\t\tpercentString != null && etaString == null -> percentString\n\t\t\tpercentString == null && etaString != null -> etaString\n\t\t\telse -> context.getString(R.string.download_summary_pattern, percentString, etaString)\n\t\t}\n\t}\n\n\tprivate fun createMangaIntent(context: Context, manga: Manga?) = PendingIntentCompat.getActivity(\n\t\tcontext,\n\t\tmanga.hashCode(),\n\t\tif (manga != null) {\n\t\t\tAppRouter.detailsIntent(context, manga)\n\t\t} else {\n\t\t\tAppRouter.listIntent(context, LocalMangaSource, null, null)\n\t\t},\n\t\tPendingIntent.FLAG_CANCEL_CURRENT,\n\t\tfalse,\n\t)\n\n\tprivate suspend fun getCover(manga: Manga) = covers[manga] ?: run {\n\t\trunCatchingCancellable {\n\t\t\tcoil.execute(\n\t\t\t\tImageRequest.Builder(context)\n\t\t\t\t\t.data(manga.coverUrl)\n\t\t\t\t\t.allowHardware(false)\n\t\t\t\t\t.mangaSourceExtra(manga.source)\n\t\t\t\t\t.size(context.resources.getNotificationIconSize())\n\t\t\t\t\t.scale(Scale.FILL)\n\t\t\t\t\t.build(),\n\t\t\t).getDrawableOrThrow()\n\t\t}.onSuccess {\n\t\t\tcovers[manga] = it\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}.getOrNull()\n\t}\n\n\tprivate fun createChannels() {\n\t\tval manager = NotificationManagerCompat.from(context)\n\t\tmanager.createNotificationChannel(\n\t\t\tNotificationChannelCompat.Builder(CHANNEL_ID_DEFAULT, NotificationManagerCompat.IMPORTANCE_LOW)\n\t\t\t\t.setName(context.getString(R.string.downloads))\n\t\t\t\t.setVibrationEnabled(false)\n\t\t\t\t.setLightsEnabled(false)\n\t\t\t\t.setSound(null, null)\n\t\t\t\t.build(),\n\t\t)\n\t\tmanager.createNotificationChannel(\n\t\t\tNotificationChannelCompat.Builder(CHANNEL_ID_SILENT, NotificationManagerCompat.IMPORTANCE_MIN)\n\t\t\t\t.setName(context.getString(R.string.downloads_background))\n\t\t\t\t.setVibrationEnabled(false)\n\t\t\t\t.setLightsEnabled(false)\n\t\t\t\t.setSound(null, null)\n\t\t\t\t.setShowBadge(false)\n\t\t\t\t.build(),\n\t\t)\n\t}\n\n\t@AssistedFactory\n\tinterface Factory {\n\n\t\tfun create(uuid: UUID, isSilent: Boolean): DownloadNotificationFactory\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt",
    "content": "package org.koitharu.kotatsu.download.ui.worker\n\nimport android.os.SystemClock\nimport androidx.collection.MutableObjectLongMap\nimport kotlinx.coroutines.delay\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass DownloadSlowdownDispatcher @Inject constructor(\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\tprivate val timeMap = MutableObjectLongMap<MangaSource>()\n\tprivate val defaultDelay = 1_600L\n\n\tsuspend fun delay(source: MangaSource) {\n\t\tval repo = mangaRepositoryFactory.create(source) as? ParserMangaRepository ?: return\n\t\tif (!repo.isSlowdownEnabled()) {\n\t\t\treturn\n\t\t}\n\t\tval lastRequest = synchronized(timeMap) {\n\t\t\tval res = timeMap.getOrDefault(source, 0L)\n\t\t\ttimeMap[source] = SystemClock.elapsedRealtime()\n\t\t\tres\n\t\t}\n\t\tif (lastRequest != 0L) {\n\t\t\tdelay(lastRequest + defaultDelay - SystemClock.elapsedRealtime())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadStartedObserver.kt",
    "content": "package org.koitharu.kotatsu.download.ui.worker\n\nimport android.view.View\nimport com.google.android.material.snackbar.Snackbar\nimport kotlinx.coroutines.flow.FlowCollector\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.util.ext.findActivity\nimport org.koitharu.kotatsu.main.ui.owners.BottomNavOwner\n\nclass DownloadStartedObserver(\n\tprivate val snackbarHost: View,\n) : FlowCollector<Unit> {\n\n\toverride suspend fun emit(value: Unit) {\n\t\tval snackbar = Snackbar.make(snackbarHost, R.string.download_started, Snackbar.LENGTH_LONG)\n\t\t(snackbarHost.context.findActivity() as? BottomNavOwner)?.let {\n\t\t\tsnackbar.anchorView = it.bottomNav\n\t\t}\n\t\tval router = AppRouter.from(snackbarHost)\n\t\tif (router != null) {\n\t\t\tsnackbar.setAction(R.string.details) { router.openDownloads() }\n\t\t}\n\t\tsnackbar.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadTask.kt",
    "content": "package org.koitharu.kotatsu.download.ui.worker\n\nimport android.os.Parcelable\nimport androidx.work.Data\nimport kotlinx.parcelize.Parcelize\nimport org.koitharu.kotatsu.core.prefs.DownloadFormat\nimport org.koitharu.kotatsu.parsers.util.find\nimport java.io.File\n\n@Parcelize\nclass DownloadTask(\n\tval mangaId: Long,\n\tval isPaused: Boolean,\n\tval isSilent: Boolean,\n\tval chaptersIds: LongArray?,\n\tval destination: File?,\n\tval format: DownloadFormat?,\n\tval allowMeteredNetwork: Boolean,\n) : Parcelable {\n\n\tconstructor(data: Data) : this(\n\t\tmangaId = data.getLong(MANGA_ID, 0L),\n\t\tisPaused = data.getBoolean(START_PAUSED, false),\n\t\tisSilent = data.getBoolean(IS_SILENT, false),\n\t\tchaptersIds = data.getLongArray(CHAPTERS)?.takeUnless(LongArray::isEmpty),\n\t\tdestination = data.getString(DESTINATION)?.let { File(it) },\n\t\tformat = data.getString(FORMAT)?.let { DownloadFormat.entries.find(it) },\n\t\tallowMeteredNetwork = data.getBoolean(ALLOW_METERED, true),\n\t)\n\n\tfun toData(): Data = Data.Builder()\n\t\t.putLong(MANGA_ID, mangaId)\n\t\t.putBoolean(START_PAUSED, isPaused)\n\t\t.putBoolean(IS_SILENT, isSilent)\n\t\t.putLongArray(CHAPTERS, chaptersIds ?: LongArray(0))\n\t\t.putString(DESTINATION, destination?.path)\n\t\t.putString(FORMAT, format?.name)\n\t\t.build()\n\n\toverride fun equals(other: Any?): Boolean {\n\t\tif (this === other) return true\n\t\tif (javaClass != other?.javaClass) return false\n\n\t\tother as DownloadTask\n\n\t\tif (mangaId != other.mangaId) return false\n\t\tif (isPaused != other.isPaused) return false\n\t\tif (isSilent != other.isSilent) return false\n\t\tif (!(chaptersIds contentEquals other.chaptersIds)) return false\n\t\tif (destination != other.destination) return false\n\t\tif (format != other.format) return false\n\t\tif (allowMeteredNetwork != other.allowMeteredNetwork) return false\n\n\t\treturn true\n\t}\n\n\toverride fun hashCode(): Int {\n\t\tvar result = mangaId.hashCode()\n\t\tresult = 31 * result + isPaused.hashCode()\n\t\tresult = 31 * result + isSilent.hashCode()\n\t\tresult = 31 * result + (chaptersIds?.contentHashCode() ?: 0)\n\t\tresult = 31 * result + (destination?.hashCode() ?: 0)\n\t\tresult = 31 * result + (format?.hashCode() ?: 0)\n\t\tresult = 31 * result + allowMeteredNetwork.hashCode()\n\t\treturn result\n\t}\n\n\tprivate companion object {\n\n\t\tconst val MANGA_ID = \"manga_id\"\n\t\tconst val IS_SILENT = \"silent\"\n\t\tconst val START_PAUSED = \"paused\"\n\t\tconst val CHAPTERS = \"chapters\"\n\t\tconst val DESTINATION = \"dest\"\n\t\tconst val FORMAT = \"format\"\n\t\tconst val ALLOW_METERED = \"metered\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt",
    "content": "package org.koitharu.kotatsu.download.ui.worker\n\nimport android.annotation.SuppressLint\nimport android.app.NotificationManager\nimport android.content.Context\nimport android.content.pm.ServiceInfo\nimport android.os.Build\nimport androidx.core.content.ContextCompat\nimport androidx.core.net.toUri\nimport androidx.hilt.work.HiltWorker\nimport androidx.work.BackoffPolicy\nimport androidx.work.Constraints\nimport androidx.work.CoroutineWorker\nimport androidx.work.Data\nimport androidx.work.ForegroundInfo\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.OutOfQuotaPolicy\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport androidx.work.await\nimport dagger.Reusable\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.NonCancellable\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport kotlinx.coroutines.withContext\nimport okhttp3.OkHttpClient\nimport okhttp3.internal.closeQuietly\nimport okio.IOException\nimport okio.buffer\nimport okio.sink\nimport okio.use\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.image.BitmapDecoderCompat\nimport org.koitharu.kotatsu.core.model.ids\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.network.MangaHttpClient\nimport org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.Throttler\nimport org.koitharu.kotatsu.core.util.ext.MimeType\nimport org.koitharu.kotatsu.core.util.ext.awaitFinishedWorkInfosByTag\nimport org.koitharu.kotatsu.core.util.ext.awaitUpdateWork\nimport org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag\nimport org.koitharu.kotatsu.core.util.ext.deleteAwait\nimport org.koitharu.kotatsu.core.util.ext.deleteWork\nimport org.koitharu.kotatsu.core.util.ext.deleteWorks\nimport org.koitharu.kotatsu.core.util.ext.ensureSuccess\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getWorkInputData\nimport org.koitharu.kotatsu.core.util.ext.getWorkSpec\nimport org.koitharu.kotatsu.core.util.ext.openSource\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toFileOrNull\nimport org.koitharu.kotatsu.core.util.ext.toMimeType\nimport org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull\nimport org.koitharu.kotatsu.core.util.ext.withTicker\nimport org.koitharu.kotatsu.core.util.ext.writeAllCancellable\nimport org.koitharu.kotatsu.core.util.progress.RealtimeEtaEstimator\nimport org.koitharu.kotatsu.download.domain.DownloadProgress\nimport org.koitharu.kotatsu.download.domain.DownloadState\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.data.LocalStorageCache\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.data.PageCache\nimport org.koitharu.kotatsu.local.data.TempFileFilter\nimport org.koitharu.kotatsu.local.data.input.LocalMangaParser\nimport org.koitharu.kotatsu.local.data.output.LocalMangaOutput\nimport org.koitharu.kotatsu.local.domain.MangaLock\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.requireBody\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport java.io.File\nimport java.util.UUID\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.atomic.AtomicInteger\nimport javax.inject.Inject\n\n@HiltWorker\nclass DownloadWorker @AssistedInject constructor(\n\t@Assisted appContext: Context,\n\t@Assisted params: WorkerParameters,\n\t@MangaHttpClient private val okHttp: OkHttpClient,\n\t@PageCache private val cache: LocalStorageCache,\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val mangaLock: MangaLock,\n\tprivate val mangaDataRepository: MangaDataRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val settings: AppSettings,\n\t@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,\n\tprivate val slowdownDispatcher: DownloadSlowdownDispatcher,\n\tprivate val imageProxyInterceptor: ImageProxyInterceptor,\n\tnotificationFactoryFactory: DownloadNotificationFactory.Factory,\n) : CoroutineWorker(appContext, params) {\n\n\tprivate val task = DownloadTask(params.inputData)\n\tprivate val notificationFactory = notificationFactoryFactory.create(uuid = params.id, isSilent = task.isSilent)\n\tprivate val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager\n\n\t@Volatile\n\tprivate var lastPublishedState: DownloadState? = null\n\tprivate val currentState: DownloadState\n\t\tget() = checkNotNull(lastPublishedState)\n\n\tprivate val etaEstimator = RealtimeEtaEstimator()\n\tprivate val notificationThrottler = Throttler(400)\n\n\toverride suspend fun doWork(): Result {\n\t\tsetForeground(getForegroundInfo())\n\t\tval manga = mangaDataRepository.findMangaById(task.mangaId, withChapters = true) ?: return Result.failure()\n\t\tpublishState(DownloadState(manga = manga, isIndeterminate = true).also { lastPublishedState = it })\n\t\tval downloadedIds = getDoneChapters(manga)\n\t\treturn try {\n\t\t\tval pausingHandle = PausingHandle()\n\t\t\tif (task.isPaused) {\n\t\t\t\tpausingHandle.pause()\n\t\t\t}\n\t\t\twithContext(pausingHandle) {\n\t\t\t\tdownloadMangaImpl(manga, task, downloadedIds)\n\t\t\t}\n\t\t\tResult.success(currentState.toWorkData())\n\t\t} catch (_: CancellationException) {\n\t\t\twithContext(NonCancellable) {\n\t\t\t\tval notification = notificationFactory.create(currentState.copy(isStopped = true))\n\t\t\t\tnotificationManager.notify(id.hashCode(), notification)\n\t\t\t}\n\t\t\tResult.failure(\n\t\t\t\tcurrentState.copy(eta = -1L, isStuck = false).toWorkData(),\n\t\t\t)\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTraceDebug()\n\t\t\tResult.failure(\n\t\t\t\tcurrentState.copy(\n\t\t\t\t\terror = e,\n\t\t\t\t\terrorMessage = e.getDisplayMessage(applicationContext.resources),\n\t\t\t\t\teta = -1L,\n\t\t\t\t\tisStuck = false,\n\t\t\t\t).toWorkData(),\n\t\t\t)\n\t\t} finally {\n\t\t\tnotificationManager.cancel(id.hashCode())\n\t\t}\n\t}\n\n\toverride suspend fun getForegroundInfo() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\tForegroundInfo(\n\t\t\tid.hashCode(),\n\t\t\tnotificationFactory.create(lastPublishedState),\n\t\t\tServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,\n\t\t)\n\t} else {\n\t\tForegroundInfo(\n\t\t\tid.hashCode(),\n\t\t\tnotificationFactory.create(lastPublishedState),\n\t\t)\n\t}\n\n\tprivate suspend fun downloadMangaImpl(\n\t\tsubject: Manga,\n\t\ttask: DownloadTask,\n\t\texcludedIds: Set<Long>,\n\t) {\n\t\tvar manga = subject\n\t\tval chaptersToSkip = excludedIds.toMutableSet()\n\t\tval pausingReceiver = PausingReceiver(id, PausingHandle.current())\n\t\tmangaLock.withLock(manga) {\n\t\t\tContextCompat.registerReceiver(\n\t\t\t\tapplicationContext,\n\t\t\t\tpausingReceiver,\n\t\t\t\tPausingReceiver.createIntentFilter(id),\n\t\t\t\tContextCompat.RECEIVER_NOT_EXPORTED,\n\t\t\t)\n\t\t\tval destination = localMangaRepository.getOutputDir(manga, task.destination)\n\t\t\tcheckNotNull(destination) { applicationContext.getString(R.string.cannot_find_available_storage) }\n\t\t\tvar output: LocalMangaOutput? = null\n\t\t\ttry {\n\t\t\t\tif (manga.isLocal) {\n\t\t\t\t\tmanga = localMangaRepository.getRemoteManga(manga)\n\t\t\t\t\t\t?: error(\"Cannot obtain remote manga instance\")\n\t\t\t\t}\n\t\t\t\tval repo = mangaRepositoryFactory.create(manga.source)\n\t\t\t\tval mangaDetails = if (manga.chapters.isNullOrEmpty() || manga.description.isNullOrEmpty()) repo.getDetails(manga) else manga\n\t\t\t\toutput = LocalMangaOutput.getOrCreate(\n\t\t\t\t\troot = destination,\n\t\t\t\t\tmanga = mangaDetails,\n\t\t\t\t\tformat = task.format ?: settings.preferredDownloadFormat,\n\t\t\t\t)\n\t\t\t\tval coverUrl = mangaDetails.largeCoverUrl.ifNullOrEmpty { mangaDetails.coverUrl }\n\t\t\t\tif (!coverUrl.isNullOrEmpty()) {\n\t\t\t\t\tdownloadFile(coverUrl, destination, repo.source).let { file ->\n\t\t\t\t\t\toutput.addCover(file, getMediaType(coverUrl, file))\n\t\t\t\t\t\tfile.deleteAwait()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tval chapters = getChapters(mangaDetails, task)\n\t\t\t\tfor ((chapterIndex, chapter) in chapters.withIndex()) {\n\t\t\t\t\tcheckIsPaused()\n\t\t\t\t\tif (chaptersToSkip.remove(chapter.value.id)) {\n\t\t\t\t\t\tpublishState(currentState.copy(downloadedChapters = currentState.downloadedChapters + 1))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tval pages = runFailsafe {\n\t\t\t\t\t\trepo.getPages(chapter.value)\n\t\t\t\t\t} ?: continue\n\t\t\t\t\tval pageCounter = AtomicInteger(0)\n\t\t\t\t\tchannelFlow {\n\t\t\t\t\t\tval semaphore = Semaphore(MAX_PAGES_PARALLELISM)\n\t\t\t\t\t\tfor ((pageIndex, page) in pages.withIndex()) {\n\t\t\t\t\t\t\tcheckIsPaused()\n\t\t\t\t\t\t\tlaunch {\n\t\t\t\t\t\t\t\tsemaphore.withPermit {\n\t\t\t\t\t\t\t\t\trunFailsafe {\n\t\t\t\t\t\t\t\t\t\tval url = repo.getPageUrl(page)\n\t\t\t\t\t\t\t\t\t\tval file = cache[url]\n\t\t\t\t\t\t\t\t\t\t\t?: downloadFile(url, destination, repo.source)\n\t\t\t\t\t\t\t\t\t\toutput.addPage(\n\t\t\t\t\t\t\t\t\t\t\tchapter = chapter,\n\t\t\t\t\t\t\t\t\t\t\tfile = file,\n\t\t\t\t\t\t\t\t\t\t\tpageNumber = pageIndex,\n\t\t\t\t\t\t\t\t\t\t\ttype = getMediaType(url, file),\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\tif (file.extension == \"tmp\") {\n\t\t\t\t\t\t\t\t\t\t\tfile.deleteAwait()\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tsend(pageIndex)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}.map {\n\t\t\t\t\t\tDownloadProgress(\n\t\t\t\t\t\t\ttotalChapters = chapters.size,\n\t\t\t\t\t\t\tcurrentChapter = chapterIndex,\n\t\t\t\t\t\t\ttotalPages = pages.size,\n\t\t\t\t\t\t\tcurrentPage = pageCounter.getAndIncrement(),\n\t\t\t\t\t\t)\n\t\t\t\t\t}.withTicker(2L, TimeUnit.SECONDS).collect { progress ->\n\t\t\t\t\t\tpublishState(\n\t\t\t\t\t\t\tcurrentState.copy(\n\t\t\t\t\t\t\t\ttotalChapters = progress.totalChapters,\n\t\t\t\t\t\t\t\tcurrentChapter = progress.currentChapter,\n\t\t\t\t\t\t\t\ttotalPages = progress.totalPages,\n\t\t\t\t\t\t\t\tcurrentPage = progress.currentPage,\n\t\t\t\t\t\t\t\tisIndeterminate = false,\n\t\t\t\t\t\t\t\teta = etaEstimator.getEta(),\n\t\t\t\t\t\t\t\tisStuck = etaEstimator.isStuck(),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tif (output.flushChapter(chapter.value)) {\n\t\t\t\t\t\trunCatchingCancellable {\n\t\t\t\t\t\t\tlocalStorageChanges.emit(LocalMangaParser(output.rootFile).getManga(withDetails = false))\n\t\t\t\t\t\t}.onFailure(Throwable::printStackTraceDebug)\n\t\t\t\t\t}\n\t\t\t\t\tpublishState(currentState.copy(downloadedChapters = currentState.downloadedChapters + 1))\n\t\t\t\t}\n\t\t\t\tpublishState(currentState.copy(isIndeterminate = true, eta = -1L, isStuck = false))\n\t\t\t\toutput.mergeWithExisting()\n\t\t\t\toutput.finish()\n\t\t\t\tval localManga = LocalMangaParser(output.rootFile).getManga(withDetails = false)\n\t\t\t\tlocalStorageChanges.emit(localManga)\n\t\t\t\tpublishState(currentState.copy(localManga = localManga, eta = -1L, isStuck = false))\n\t\t\t} catch (e: Exception) {\n\t\t\t\tif (e !is CancellationException) {\n\t\t\t\t\tpublishState(\n\t\t\t\t\t\tcurrentState.copy(\n\t\t\t\t\t\t\terror = e,\n\t\t\t\t\t\t\terrorMessage = e.getDisplayMessage(applicationContext.resources),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthrow e\n\t\t\t} finally {\n\t\t\t\twithContext(NonCancellable) {\n\t\t\t\t\tapplicationContext.unregisterReceiver(pausingReceiver)\n\t\t\t\t\toutput?.closeQuietly()\n\t\t\t\t\toutput?.cleanup()\n\t\t\t\t\tdestination.listFiles(TempFileFilter())?.forEach {\n\t\t\t\t\t\tit.deleteAwait()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun <R> runFailsafe(\n\t\tblock: suspend () -> R,\n\t): R? {\n\t\tcheckIsPaused()\n\t\tvar countDown = MAX_FAILSAFE_ATTEMPTS\n\t\tfailsafe@ while (true) {\n\t\t\ttry {\n\t\t\t\treturn block()\n\t\t\t} catch (e: IOException) {\n\t\t\t\tval retryDelay = if (e is TooManyRequestExceptions) {\n\t\t\t\t\te.getRetryDelay()\n\t\t\t\t} else {\n\t\t\t\t\tDOWNLOAD_ERROR_DELAY\n\t\t\t\t}\n\t\t\t\tif (countDown <= 0 || retryDelay < 0 || retryDelay > MAX_RETRY_DELAY) {\n\t\t\t\t\tval pausingHandle = PausingHandle.current()\n\t\t\t\t\tif (pausingHandle.skipAllErrors()) {\n\t\t\t\t\t\treturn null\n\t\t\t\t\t}\n\t\t\t\t\tpublishState(\n\t\t\t\t\t\tcurrentState.copy(\n\t\t\t\t\t\t\tisPaused = true,\n\t\t\t\t\t\t\terror = e,\n\t\t\t\t\t\t\terrorMessage = e.getDisplayMessage(applicationContext.resources),\n\t\t\t\t\t\t\teta = -1L,\n\t\t\t\t\t\t\tisStuck = false,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tcountDown = MAX_FAILSAFE_ATTEMPTS\n\t\t\t\t\tpausingHandle.pause()\n\t\t\t\t\ttry {\n\t\t\t\t\t\tpausingHandle.awaitResumed()\n\t\t\t\t\t\tif (pausingHandle.skipCurrentError()) {\n\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tpublishState(currentState.copy(isPaused = false, error = null, errorMessage = null))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tcountDown--\n\t\t\t\t\tdelay(retryDelay)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun checkIsPaused() {\n\t\tval pausingHandle = PausingHandle.current()\n\t\tif (pausingHandle.isPaused) {\n\t\t\tpublishState(currentState.copy(isPaused = true, eta = -1L, isStuck = false))\n\t\t\ttry {\n\t\t\t\tpausingHandle.awaitResumed()\n\t\t\t} finally {\n\t\t\t\tpublishState(currentState.copy(isPaused = false))\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun getMediaType(url: String, file: File): MimeType? = runInterruptible(Dispatchers.IO) {\n\t\tBitmapDecoderCompat.probeMimeType(file)?.let {\n\t\t\treturn@runInterruptible it\n\t\t}\n\t\tMimeTypes.getMimeTypeFromUrl(url)\n\t}\n\n\tprivate suspend fun downloadFile(\n\t\turl: String,\n\t\tdestination: File,\n\t\tsource: MangaSource,\n\t): File {\n\t\tif (url.startsWith(\"content:\", ignoreCase = true) || url.startsWith(\"file:\", ignoreCase = true)) {\n\t\t\tval uri = url.toUri()\n\t\t\tval cr = applicationContext.contentResolver\n\t\t\tval ext = uri.toFileOrNull()?.let {\n\t\t\t\tMimeTypes.getNormalizedExtension(it.name)\n\t\t\t} ?: cr.getType(uri)?.toMimeTypeOrNull()?.let { MimeTypes.getExtension(it) }\n\t\t\tval file = destination.createTempFile(ext)\n\t\t\ttry {\n\t\t\t\tcr.openSource(uri).use { input ->\n\t\t\t\t\tfile.sink(append = false).buffer().use {\n\t\t\t\t\t\tit.writeAllCancellable(input)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e: Exception) {\n\t\t\t\tfile.delete()\n\t\t\t\tthrow e\n\t\t\t}\n\t\t\treturn file\n\t\t}\n\t\tval request = PageLoader.createPageRequest(url, source)\n\t\tslowdownDispatcher.delay(source)\n\t\treturn imageProxyInterceptor.interceptPageRequest(request, okHttp)\n\t\t\t.ensureSuccess()\n\t\t\t.use { response ->\n\t\t\t\tvar file: File? = null\n\t\t\t\ttry {\n\t\t\t\t\tresponse.requireBody().use { body ->\n\t\t\t\t\t\tfile = destination.createTempFile(\n\t\t\t\t\t\t\text = MimeTypes.getExtension(body.contentType()?.toMimeType())\n\t\t\t\t\t\t)\n\t\t\t\t\t\tfile.sink(append = false).buffer().use {\n\t\t\t\t\t\t\tit.writeAllCancellable(body.source())\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (e: Exception) {\n\t\t\t\t\tfile?.delete()\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t\tcheckNotNull(file)\n\t\t\t}\n\t}\n\n\tprivate fun File.createTempFile(ext: String?) = File(\n\t\tthis,\n\t\tbuildString {\n\t\t\tappend(UUID.randomUUID().toString())\n\t\t\tif (!ext.isNullOrEmpty()) {\n\t\t\t\tappend('.')\n\t\t\t\tappend(ext)\n\t\t\t}\n\t\t\tappend(\".tmp\")\n\t\t},\n\t)\n\n\tprivate suspend fun publishState(state: DownloadState) {\n\t\tval previousState = currentState\n\t\tlastPublishedState = state\n\t\tif (previousState.isParticularProgress && state.isParticularProgress) {\n\t\t\tetaEstimator.onProgressChanged(state.progress, state.max)\n\t\t} else {\n\t\t\tetaEstimator.reset()\n\t\t\tnotificationThrottler.reset()\n\t\t}\n\t\tval notification = notificationFactory.create(state)\n\t\tif (state.isFinalState) {\n\t\t\tif (!notificationFactory.isSilent) {\n\t\t\t\tnotificationManager.notify(id.toString(), id.hashCode(), notification)\n\t\t\t}\n\t\t} else if (notificationThrottler.throttle()) {\n\t\t\tnotificationManager.notify(id.hashCode(), notification)\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t\tsetProgress(state.toWorkData())\n\t}\n\n\tprivate suspend fun getDoneChapters(manga: Manga) = runCatchingCancellable {\n\t\tlocalMangaRepository.getDetails(manga).chapters?.ids()\n\t}.getOrNull().orEmpty()\n\n\tprivate fun getChapters(\n\t\tmanga: Manga,\n\t\ttask: DownloadTask,\n\t): List<IndexedValue<MangaChapter>> {\n\t\tval chapters = checkNotNull(manga.chapters) { \"Chapters list must not be null\" }\n\t\tval chaptersIdsSet = task.chaptersIds?.toMutableSet()\n\t\tval result = ArrayList<IndexedValue<MangaChapter>>((chaptersIdsSet ?: chapters).size)\n\t\tval counters = HashMap<String?, Int>()\n\t\tfor (chapter in chapters) {\n\t\t\tval index = counters[chapter.branch] ?: 0\n\t\t\tcounters[chapter.branch] = index + 1\n\t\t\tif (chaptersIdsSet != null && !chaptersIdsSet.remove(chapter.id)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult.add(IndexedValue(index, chapter))\n\t\t}\n\t\tif (chaptersIdsSet != null) {\n\t\t\tcheck(chaptersIdsSet.isEmpty()) {\n\t\t\t\t\"${chaptersIdsSet.size} of ${task.chaptersIds.size} requested chapters not found in manga\"\n\t\t\t}\n\t\t}\n\t\tcheck(result.isNotEmpty()) { \"Chapters list must not be empty\" }\n\t\treturn result\n\t}\n\n\t@Reusable\n\tclass Scheduler @Inject constructor(\n\t\t@ApplicationContext private val context: Context,\n\t\tprivate val mangaDataRepository: MangaDataRepository,\n\t\tprivate val workManager: WorkManager,\n\t) {\n\n\t\tfun observeWorks(): Flow<List<WorkInfo>> = workManager\n\t\t\t.getWorkInfosByTagFlow(TAG)\n\n\t\t@SuppressLint(\"RestrictedApi\")\n\t\tsuspend fun getInputData(id: UUID): Data? {\n\t\t\tval spec = workManager.getWorkSpec(id) ?: return null\n\t\t\treturn Data.Builder()\n\t\t\t\t.putAll(spec.input)\n\t\t\t\t.putLong(DownloadState.DATA_TIMESTAMP, spec.scheduleRequestedAt)\n\t\t\t\t.build()\n\t\t}\n\n\t\tsuspend fun getTask(workId: UUID): DownloadTask? {\n\t\t\treturn workManager.getWorkInputData(workId)?.let { DownloadTask(it) }\n\t\t}\n\n\t\tsuspend fun cancel(id: UUID) {\n\t\t\tworkManager.cancelWorkById(id).await()\n\t\t}\n\n\t\tsuspend fun cancelAll() {\n\t\t\tworkManager.cancelAllWorkByTag(TAG).await()\n\t\t}\n\n\t\tfun pause(id: UUID) = context.sendBroadcast(\n\t\t\tPausingReceiver.getPauseIntent(context, id),\n\t\t)\n\n\t\tfun resume(id: UUID) = context.sendBroadcast(\n\t\t\tPausingReceiver.getResumeIntent(context, id),\n\t\t)\n\n\t\tfun skip(id: UUID) = context.sendBroadcast(\n\t\t\tPausingReceiver.getSkipIntent(context, id),\n\t\t)\n\n\t\tfun skipAll(id: UUID) = context.sendBroadcast(\n\t\t\tPausingReceiver.getSkipAllIntent(context, id),\n\t\t)\n\n\t\tsuspend fun delete(id: UUID) {\n\t\t\tworkManager.deleteWork(id)\n\t\t}\n\n\t\tsuspend fun delete(ids: Collection<UUID>) {\n\t\t\tval wm = workManager\n\t\t\tids.forEach { id -> wm.cancelWorkById(id).await() }\n\t\t\tworkManager.deleteWorks(ids)\n\t\t}\n\n\t\tsuspend fun removeCompleted() {\n\t\t\tval finishedWorks = workManager.awaitFinishedWorkInfosByTag(TAG)\n\t\t\tworkManager.deleteWorks(finishedWorks.mapToSet { it.id })\n\t\t}\n\n\t\tsuspend fun updateConstraints(allowMeteredNetwork: Boolean) {\n\t\t\tval constraints = createConstraints(allowMeteredNetwork)\n\t\t\tval works = workManager.awaitWorkInfosByTag(TAG)\n\t\t\tfor (work in works) {\n\t\t\t\tif (work.state.isFinished) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tval request = OneTimeWorkRequestBuilder<DownloadWorker>()\n\t\t\t\t\t.setConstraints(constraints)\n\t\t\t\t\t.addTag(TAG)\n\t\t\t\t\t.setId(work.id)\n\t\t\t\t\t.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)\n\t\t\t\t\t.build()\n\t\t\t\tworkManager.awaitUpdateWork(request)\n\t\t\t}\n\t\t}\n\n\t\tsuspend fun schedule(tasks: Collection<Pair<Manga, DownloadTask>>) {\n\t\t\tif (tasks.isEmpty()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval requests = tasks.map { (manga, task) ->\n\t\t\t\tmangaDataRepository.storeManga(manga, replaceExisting = true)\n\t\t\t\tOneTimeWorkRequestBuilder<DownloadWorker>()\n\t\t\t\t\t.setConstraints(createConstraints(task.allowMeteredNetwork))\n\t\t\t\t\t.addTag(TAG)\n\t\t\t\t\t.keepResultsForAtLeast(30, TimeUnit.DAYS)\n\t\t\t\t\t.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)\n\t\t\t\t\t.setInputData(task.toData())\n\t\t\t\t\t.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)\n\t\t\t\t\t.build()\n\t\t\t}\n\t\t\tworkManager.enqueue(requests).await()\n\t\t}\n\n\t\tprivate fun createConstraints(allowMeteredNetwork: Boolean) = Constraints.Builder()\n\t\t\t.setRequiredNetworkType(if (allowMeteredNetwork) NetworkType.CONNECTED else NetworkType.UNMETERED)\n\t\t\t.build()\n\t}\n\n\tprivate companion object {\n\n\t\tconst val MAX_FAILSAFE_ATTEMPTS = 2\n\t\tconst val MAX_PAGES_PARALLELISM = 4\n\t\tconst val DOWNLOAD_ERROR_DELAY = 2_000L\n\t\tconst val MAX_RETRY_DELAY = 7_200_000L // 2 hours\n\t\tconst val TAG = \"download\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/PausingHandle.kt",
    "content": "package org.koitharu.kotatsu.download.ui.worker\n\nimport androidx.annotation.AnyThread\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.first\nimport kotlin.coroutines.AbstractCoroutineContextElement\nimport kotlin.coroutines.CoroutineContext\n\nclass PausingHandle : AbstractCoroutineContextElement(PausingHandle) {\n\n\tprivate val paused = MutableStateFlow(false)\n\tprivate val skipError = MutableStateFlow(false)\n\n\t@Volatile\n\tprivate var skipAllErrors = false\n\n\t@get:AnyThread\n\tval isPaused: Boolean\n\t\tget() = paused.value\n\n\t@AnyThread\n\tsuspend fun awaitResumed() {\n\t\tpaused.first { !it }\n\t}\n\n\t@AnyThread\n\tfun pause() {\n\t\tpaused.value = true\n\t}\n\n\t@AnyThread\n\tfun resume() {\n\t\tskipError.value = false\n\t\tpaused.value = false\n\t}\n\n\t@AnyThread\n\tfun skip() {\n\t\tskipError.value = true\n\t\tpaused.value = false\n\t}\n\n\t@AnyThread\n\tfun skipAll() {\n\t\tskipAllErrors = true\n\t\tskip()\n\t}\n\n\tsuspend fun yield() {\n\t\tif (paused.value) {\n\t\t\tpaused.first { !it }\n\t\t}\n\t}\n\n\tfun skipAllErrors(): Boolean = skipAllErrors\n\n\tfun skipCurrentError(): Boolean = skipError.compareAndSet(expect = true, update = false)\n\n\tcompanion object : CoroutineContext.Key<PausingHandle> {\n\n\t\tsuspend fun current() = checkNotNull(currentCoroutineContext()[this]) {\n\t\t\t\"PausingHandle not found in current context\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/PausingReceiver.kt",
    "content": "package org.koitharu.kotatsu.download.ui.worker\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.PatternMatcher\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.core.util.ext.toUUIDOrNull\nimport java.util.UUID\n\nclass PausingReceiver(\n\tprivate val id: UUID,\n\tprivate val pausingHandle: PausingHandle,\n) : BroadcastReceiver() {\n\n\toverride fun onReceive(context: Context, intent: Intent?) {\n\t\tval uuid = intent?.getStringExtra(EXTRA_UUID)?.toUUIDOrNull()\n\t\tif (uuid != id) {\n\t\t\treturn\n\t\t}\n\t\twhen (intent.action) {\n\t\t\tACTION_RESUME -> pausingHandle.resume()\n\t\t\tACTION_SKIP -> pausingHandle.skip()\n\t\t\tACTION_SKIP_ALL -> pausingHandle.skipAll()\n\t\t\tACTION_PAUSE -> pausingHandle.pause()\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val ACTION_PAUSE = \"org.koitharu.kotatsu.download.PAUSE\"\n\t\tprivate const val ACTION_RESUME = \"org.koitharu.kotatsu.download.RESUME\"\n\t\tprivate const val ACTION_SKIP = \"org.koitharu.kotatsu.download.SKIP\"\n\t\tprivate const val ACTION_SKIP_ALL = \"org.koitharu.kotatsu.download.SKIP_ALL\"\n\t\tprivate const val EXTRA_UUID = \"uuid\"\n\t\tprivate const val SCHEME = \"workuid\"\n\n\t\tfun createIntentFilter(id: UUID) = IntentFilter().apply {\n\t\t\taddAction(ACTION_PAUSE)\n\t\t\taddAction(ACTION_RESUME)\n\t\t\taddAction(ACTION_SKIP)\n\t\t\taddAction(ACTION_SKIP_ALL)\n\t\t\taddDataScheme(SCHEME)\n\t\t\taddDataPath(id.toString(), PatternMatcher.PATTERN_LITERAL)\n\t\t}\n\n\t\tfun getPauseIntent(context: Context, id: UUID) = createIntent(context, id, ACTION_PAUSE)\n\n\t\tfun getResumeIntent(context: Context, id: UUID) = createIntent(context, id, ACTION_RESUME)\n\n\t\tfun getSkipIntent(context: Context, id: UUID) = createIntent(context, id, ACTION_SKIP)\n\n\t\tfun getSkipAllIntent(context: Context, id: UUID) = createIntent(context, id, ACTION_SKIP_ALL)\n\n\t\tfun createPausePendingIntent(context: Context, id: UUID) = PendingIntentCompat.getBroadcast(\n\t\t\tcontext,\n\t\t\t0,\n\t\t\tgetPauseIntent(context, id),\n\t\t\t0,\n\t\t\tfalse,\n\t\t)\n\n\t\tfun createResumePendingIntent(context: Context, id: UUID) =\n\t\t\tPendingIntentCompat.getBroadcast(\n\t\t\t\tcontext,\n\t\t\t\t0,\n\t\t\t\tgetResumeIntent(context, id),\n\t\t\t\t0,\n\t\t\t\tfalse,\n\t\t\t)\n\n\t\tfun createSkipPendingIntent(context: Context, id: UUID) =\n\t\t\tPendingIntentCompat.getBroadcast(\n\t\t\t\tcontext,\n\t\t\t\t0,\n\t\t\t\tgetSkipIntent(context, id),\n\t\t\t\t0,\n\t\t\t\tfalse,\n\t\t\t)\n\n\t\tprivate fun createIntent(context: Context, id: UUID, action: String) = Intent(action)\n\t\t\t.setData(\"$SCHEME://$id\".toUri())\n\t\t\t.setPackage(context.packageName)\n\t\t\t.putExtra(EXTRA_UUID, id.toString())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt",
    "content": "package org.koitharu.kotatsu.explore.data\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport androidx.core.content.ContextCompat\nimport androidx.room.withTransaction\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.channels.trySendBlocking\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onStart\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.dao.MangaSourcesDao\nimport org.koitharu.kotatsu.core.db.entity.MangaSourceEntity\nimport org.koitharu.kotatsu.core.model.MangaSourceInfo\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaSource\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.util.ReversibleHandle\nimport org.koitharu.kotatsu.core.util.ext.flattenLatest\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.network.CloudFlareHelper\nimport org.koitharu.kotatsu.parsers.util.mapNotNullToSet\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport java.util.Collections\nimport java.util.EnumSet\nimport java.util.concurrent.atomic.AtomicBoolean\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MangaSourcesRepository @Inject constructor(\n\t@LocalizedAppContext private val context: Context,\n\tprivate val db: MangaDatabase,\n\tprivate val settings: AppSettings,\n) {\n\n\tprivate val isNewSourcesAssimilated = AtomicBoolean(false)\n\tprivate val dao: MangaSourcesDao\n\t\tget() = db.getSourcesDao()\n\n\tval allMangaSources: Set<MangaParserSource> = Collections.unmodifiableSet(\n\t\tEnumSet.noneOf<MangaParserSource>(MangaParserSource::class.java).also {\n            MangaParserSource.entries.filterNotTo(it, MangaParserSource::isBroken)\n        }\n\t)\n\n\tsuspend fun getEnabledSources(): List<MangaSource> {\n\t\tassimilateNewSources()\n\t\tval order = settings.sourcesSortOrder\n\t\treturn dao.findAll(!settings.isAllSourcesEnabled, order).toSources(settings.isNsfwContentDisabled, order)\n\t\t\t.let { enabled ->\n\t\t\t\tval external = getExternalSources()\n\t\t\t\tval list = ArrayList<MangaSourceInfo>(enabled.size + external.size)\n\t\t\t\texternal.mapTo(list) { MangaSourceInfo(it, isEnabled = true, isPinned = true) }\n\t\t\t\tlist.addAll(enabled)\n\t\t\t\tlist\n\t\t\t}\n\t}\n\n\tsuspend fun getPinnedSources(): Set<MangaSource> {\n\t\tassimilateNewSources()\n\t\tval skipNsfw = settings.isNsfwContentDisabled\n\t\treturn dao.findAllPinned().mapNotNullToSet {\n\t\t\tit.source.toMangaSourceOrNull()?.takeUnless { x -> skipNsfw && x.isNsfw() }\n\t\t}\n\t}\n\n\tsuspend fun getTopSources(limit: Int): List<MangaSource> {\n\t\tassimilateNewSources()\n\t\treturn dao.findLastUsed(limit).toSources(settings.isNsfwContentDisabled, null)\n\t}\n\n\tsuspend fun getDisabledSources(): Set<MangaSource> {\n\t\tassimilateNewSources()\n\t\tif (settings.isAllSourcesEnabled) {\n\t\t\treturn emptySet()\n\t\t}\n\t\tval result = EnumSet.copyOf(allMangaSources)\n\t\tval enabled = dao.findAllEnabledNames()\n\t\tfor (name in enabled) {\n\t\t\tval source = name.toMangaSourceOrNull() ?: continue\n\t\t\tresult.remove(source)\n\t\t}\n\t\treturn result\n\t}\n\n\tsuspend fun queryParserSources(\n\t\tisDisabledOnly: Boolean,\n\t\tisNewOnly: Boolean,\n\t\texcludeBroken: Boolean,\n\t\ttypes: Set<ContentType>,\n\t\tquery: String?,\n\t\tlocale: String?,\n\t\tsortOrder: SourcesSortOrder?,\n\t): List<MangaParserSource> {\n\t\tassimilateNewSources()\n\t\tval entities = dao.findAll().toMutableList()\n\t\tif (isDisabledOnly && !settings.isAllSourcesEnabled) {\n\t\t\tentities.removeAll { it.isEnabled }\n\t\t}\n\t\tif (isNewOnly) {\n\t\t\tentities.retainAll { it.addedIn == BuildConfig.VERSION_CODE }\n\t\t}\n\t\tval sources = entities.toSources(\n\t\t\tskipNsfwSources = settings.isNsfwContentDisabled,\n\t\t\tsortOrder = sortOrder,\n\t\t).run {\n\t\t\tmapNotNullTo(ArrayList(size)) { it.mangaSource as? MangaParserSource }\n\t\t}\n\t\tif (locale != null) {\n\t\t\tsources.retainAll { it.locale == locale }\n\t\t}\n\t\tif (excludeBroken) {\n\t\t\tsources.removeAll { it.isBroken }\n\t\t}\n\t\tif (types.isNotEmpty()) {\n\t\t\tsources.retainAll { it.contentType in types }\n\t\t}\n\t\tif (!query.isNullOrEmpty()) {\n\t\t\tsources.retainAll {\n\t\t\t\tit.getTitle(context).contains(query, ignoreCase = true) || it.name.contains(query, ignoreCase = true)\n\t\t\t}\n\t\t}\n\t\treturn sources\n\t}\n\n\tfun observeIsEnabled(source: MangaSource): Flow<Boolean> {\n\t\treturn dao.observeIsEnabled(source.name).onStart { assimilateNewSources() }\n\t}\n\n\tfun observeEnabledSourcesCount(): Flow<Int> {\n\t\treturn combine(\n\t\t\tobserveIsNsfwDisabled(),\n\t\t\tobserveAllEnabled().flatMapLatest { isAllSourcesEnabled ->\n\t\t\t\tdao.observeAll(!isAllSourcesEnabled, SourcesSortOrder.MANUAL)\n\t\t\t},\n\t\t) { skipNsfw, sources ->\n\t\t\tsources.count {\n\t\t\t\tit.source.toMangaSourceOrNull()?.let { s -> !skipNsfw || !s.isNsfw() } == true\n\t\t\t}\n\t\t}.distinctUntilChanged().onStart { assimilateNewSources() }\n\t}\n\n\tfun observeAvailableSourcesCount(): Flow<Int> {\n\t\treturn combine(\n\t\t\tobserveIsNsfwDisabled(),\n\t\t\tobserveAllEnabled().flatMapLatest { isAllSourcesEnabled ->\n\t\t\t\tdao.observeAll(!isAllSourcesEnabled, SourcesSortOrder.MANUAL)\n\t\t\t},\n\t\t) { skipNsfw, enabledSources ->\n\t\t\tval enabled = enabledSources.mapToSet { it.source }\n\t\t\tallMangaSources.count { x ->\n\t\t\t\tx.name !in enabled && (!skipNsfw || !x.isNsfw())\n\t\t\t}\n\t\t}.distinctUntilChanged().onStart { assimilateNewSources() }\n\t}\n\n\tfun observeEnabledSources(): Flow<List<MangaSourceInfo>> = combine(\n\t\tobserveIsNsfwDisabled(),\n\t\tobserveAllEnabled(),\n\t\tobserveSortOrder(),\n\t) { skipNsfw, allEnabled, order ->\n\t\tdao.observeAll(!allEnabled, order).map {\n\t\t\tit.toSources(skipNsfw, order)\n\t\t}\n\t}.flattenLatest()\n\t\t.onStart { assimilateNewSources() }\n\t\t.combine(observeExternalSources()) { enabled, external ->\n\t\t\tval list = ArrayList<MangaSourceInfo>(enabled.size + external.size)\n\t\t\texternal.mapTo(list) { MangaSourceInfo(it, isEnabled = true, isPinned = true) }\n\t\t\tlist.addAll(enabled)\n\t\t\tlist\n\t\t}\n\n\tfun observeAll(): Flow<List<Pair<MangaSource, Boolean>>> = dao.observeAll().map { entities ->\n\t\tval result = ArrayList<Pair<MangaSource, Boolean>>(entities.size)\n\t\tfor (entity in entities) {\n\t\t\tval source = entity.source.toMangaSourceOrNull() ?: continue\n\t\t\tif (source in allMangaSources) {\n\t\t\t\tresult.add(source to entity.isEnabled)\n\t\t\t}\n\t\t}\n\t\tresult\n\t}.onStart { assimilateNewSources() }\n\n\tsuspend fun setSourcesEnabled(sources: Collection<MangaSource>, isEnabled: Boolean): ReversibleHandle {\n\t\tsetSourcesEnabledImpl(sources, isEnabled)\n\t\treturn ReversibleHandle {\n\t\t\tsetSourcesEnabledImpl(sources, !isEnabled)\n\t\t}\n\t}\n\n\tsuspend fun setSourcesEnabledExclusive(sources: Set<MangaSource>) {\n\t\tdb.withTransaction {\n\t\t\tassimilateNewSources()\n\t\t\tfor (s in allMangaSources) {\n\t\t\t\tdao.setEnabled(s.name, s in sources)\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun disableAllSources() {\n\t\tdb.withTransaction {\n\t\t\tassimilateNewSources()\n\t\t\tdao.disableAllSources()\n\t\t}\n\t}\n\n\tsuspend fun setPositions(sources: List<MangaSource>) {\n\t\tdb.withTransaction {\n\t\t\tfor ((index, item) in sources.withIndex()) {\n\t\t\t\tdao.setSortKey(item.name, index)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun observeHasNewSources(): Flow<Boolean> = observeIsNsfwDisabled().map { skipNsfw ->\n\t\tval sources = dao.findAllFromVersion(BuildConfig.VERSION_CODE).toSources(skipNsfw, null)\n\t\tsources.isNotEmpty() && sources.size != allMangaSources.size\n\t}.onStart { assimilateNewSources() }\n\n\tfun observeHasNewSourcesForBadge(): Flow<Boolean> = combine(\n\t\tsettings.observeAsFlow(AppSettings.KEY_SOURCES_VERSION) { sourcesVersion },\n\t\tobserveIsNsfwDisabled(),\n\t) { version, skipNsfw ->\n\t\tif (version < BuildConfig.VERSION_CODE) {\n\t\t\tval sources = dao.findAllFromVersion(version).toSources(skipNsfw, null)\n\t\t\tsources.isNotEmpty()\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}.onStart { assimilateNewSources() }\n\n\tfun clearNewSourcesBadge() {\n\t\tsettings.sourcesVersion = BuildConfig.VERSION_CODE\n\t}\n\n\tprivate suspend fun assimilateNewSources(): Boolean {\n\t\tif (isNewSourcesAssimilated.getAndSet(true)) {\n\t\t\treturn false\n\t\t}\n\t\tval new = getNewSources()\n\t\tif (new.isEmpty()) {\n\t\t\treturn false\n\t\t}\n\t\tvar maxSortKey = dao.getMaxSortKey()\n\t\tval isAllEnabled = settings.isAllSourcesEnabled\n\t\tval entities = new.map { x ->\n\t\t\tMangaSourceEntity(\n\t\t\t\tsource = x.name,\n\t\t\t\tisEnabled = isAllEnabled,\n\t\t\t\tsortKey = ++maxSortKey,\n\t\t\t\taddedIn = BuildConfig.VERSION_CODE,\n\t\t\t\tlastUsedAt = 0,\n\t\t\t\tisPinned = false,\n\t\t\t\tcfState = CloudFlareHelper.PROTECTION_NOT_DETECTED,\n\t\t\t)\n\t\t}\n\t\tdao.insertIfAbsent(entities)\n\t\treturn true\n\t}\n\n\tsuspend fun isSetupRequired(): Boolean {\n\t\treturn settings.sourcesVersion == 0 && dao.findAllEnabledNames().isEmpty()\n\t}\n\n\tsuspend fun setIsPinned(sources: Collection<MangaSource>, isPinned: Boolean): ReversibleHandle {\n\t\tsetSourcesPinnedImpl(sources, isPinned)\n\t\treturn ReversibleHandle {\n\t\t\tsetSourcesEnabledImpl(sources, !isPinned)\n\t\t}\n\t}\n\n\tsuspend fun trackUsage(source: MangaSource) {\n\t\tif (!settings.isIncognitoModeEnabled(source.isNsfw())) {\n\t\t\tdao.setLastUsed(source.name, System.currentTimeMillis())\n\t\t}\n\t}\n\n\tprivate suspend fun setSourcesEnabledImpl(sources: Collection<MangaSource>, isEnabled: Boolean) {\n\t\tif (sources.size == 1) { // fast path\n\t\t\tdao.setEnabled(sources.first().name, isEnabled)\n\t\t\treturn\n\t\t}\n\t\tdb.withTransaction {\n\t\t\tfor (source in sources) {\n\t\t\t\tdao.setEnabled(source.name, isEnabled)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun getNewSources(): MutableSet<out MangaSource> {\n\t\tval entities = dao.findAll()\n\t\tval result = EnumSet.copyOf(allMangaSources)\n\t\tfor (e in entities) {\n\t\t\tresult.remove(e.source.toMangaSourceOrNull() ?: continue)\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate suspend fun setSourcesPinnedImpl(sources: Collection<MangaSource>, isPinned: Boolean) {\n\t\tif (sources.size == 1) { // fast path\n\t\t\tdao.setPinned(sources.first().name, isPinned)\n\t\t\treturn\n\t\t}\n\t\tdb.withTransaction {\n\t\t\tfor (source in sources) {\n\t\t\t\tdao.setPinned(source.name, isPinned)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun observeExternalSources(): Flow<List<ExternalMangaSource>> {\n\t\treturn callbackFlow {\n\t\t\tval receiver = object : BroadcastReceiver() {\n\t\t\t\toverride fun onReceive(context: Context?, intent: Intent?) {\n\t\t\t\t\ttrySendBlocking(intent)\n\t\t\t\t}\n\t\t\t}\n\t\t\tContextCompat.registerReceiver(\n\t\t\t\tcontext,\n\t\t\t\treceiver,\n\t\t\t\tIntentFilter().apply {\n\t\t\t\t\taddAction(Intent.ACTION_PACKAGE_ADDED)\n\t\t\t\t\taddAction(Intent.ACTION_PACKAGE_VERIFIED)\n\t\t\t\t\taddAction(Intent.ACTION_PACKAGE_REPLACED)\n\t\t\t\t\taddAction(Intent.ACTION_PACKAGE_REMOVED)\n\t\t\t\t\taddAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)\n\t\t\t\t\taddDataScheme(\"package\")\n\t\t\t\t},\n\t\t\t\tContextCompat.RECEIVER_EXPORTED,\n\t\t\t)\n\t\t\tawaitClose { context.unregisterReceiver(receiver) }\n\t\t}.onStart {\n\t\t\temit(null)\n\t\t}.map {\n\t\t\tgetExternalSources()\n\t\t}.distinctUntilChanged()\n\t\t\t.conflate()\n\t}\n\n\tfun getExternalSources(): List<ExternalMangaSource> = context.packageManager.queryIntentContentProviders(\n\t\tIntent(\"app.kotatsu.parser.PROVIDE_MANGA\"), 0,\n\t).map { resolveInfo ->\n\t\tExternalMangaSource(\n\t\t\tpackageName = resolveInfo.providerInfo.packageName,\n\t\t\tauthority = resolveInfo.providerInfo.authority,\n\t\t)\n\t}\n\n\tprivate fun List<MangaSourceEntity>.toSources(\n\t\tskipNsfwSources: Boolean,\n\t\tsortOrder: SourcesSortOrder?,\n\t): MutableList<MangaSourceInfo> {\n\t\tval isAllEnabled = settings.isAllSourcesEnabled\n\t\tval result = ArrayList<MangaSourceInfo>(size)\n\t\tfor (entity in this) {\n\t\t\tval source = entity.source.toMangaSourceOrNull() ?: continue\n\t\t\tif (skipNsfwSources && source.isNsfw()) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (source in allMangaSources) {\n\t\t\t\tresult.add(\n\t\t\t\t\tMangaSourceInfo(\n\t\t\t\t\t\tmangaSource = source,\n\t\t\t\t\t\tisEnabled = entity.isEnabled || isAllEnabled,\n\t\t\t\t\t\tisPinned = entity.isPinned,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (sortOrder == SourcesSortOrder.ALPHABETIC) {\n\t\t\tresult.sortWith(compareBy<MangaSourceInfo> { !it.isPinned }.thenBy { it.getTitle(context) })\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate fun observeIsNsfwDisabled() = settings.observeAsFlow(AppSettings.KEY_DISABLE_NSFW) {\n\t\tisNsfwContentDisabled\n\t}\n\n\tprivate fun observeSortOrder() = settings.observeAsFlow(AppSettings.KEY_SOURCES_ORDER) {\n\t\tsourcesSortOrder\n\t}\n\n\tprivate fun observeAllEnabled() = settings.observeAsFlow(AppSettings.KEY_SOURCES_ENABLED_ALL) {\n\t\tisAllSourcesEnabled\n\t}\n\n\tprivate fun String.toMangaSourceOrNull(): MangaParserSource? = MangaParserSource.entries.find { it.name == this }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/data/SourcesSortOrder.kt",
    "content": "package org.koitharu.kotatsu.explore.data\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\n\nenum class SourcesSortOrder(\n\t@StringRes val titleResId: Int,\n) {\n\tALPHABETIC(R.string.by_name),\n\tPOPULARITY(R.string.popular),\n\tMANUAL(R.string.manual),\n\tLAST_USED(R.string.last_used),\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt",
    "content": "package org.koitharu.kotatsu.explore.domain\n\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.asArrayList\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.almostEquals\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.suggestions.domain.TagsBlacklist\nimport javax.inject.Inject\n\nclass ExploreRepository @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val sourcesRepository: MangaSourcesRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\n\tsuspend fun findRandomManga(tagsLimit: Int): Manga {\n\t\tval tagsBlacklist = TagsBlacklist(settings.suggestionsTagsBlacklist, 0.4f)\n\t\tval tags = historyRepository.getPopularTags(tagsLimit).mapNotNull {\n\t\t\tif (it in tagsBlacklist) null else it.title\n\t\t}\n\t\tval sources = sourcesRepository.getEnabledSources()\n\t\tcheck(sources.isNotEmpty()) { \"No sources available\" }\n\t\tfor (i in 0..4) {\n\t\t\tval list = getList(sources.random(), tags, tagsBlacklist)\n\t\t\tval manga = list.randomOrNull() ?: continue\n\t\t\tval details = runCatchingCancellable {\n\t\t\t\tmangaRepositoryFactory.create(manga.source).getDetails(manga)\n\t\t\t}.getOrNull() ?: continue\n\t\t\tif ((settings.isSuggestionsExcludeNsfw && details.isNsfw()) || details in tagsBlacklist) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn details\n\t\t}\n\t\tthrow NoSuchElementException()\n\t}\n\n\tsuspend fun findRandomManga(source: MangaSource, tagsLimit: Int): Manga {\n\t\tval tagsBlacklist = TagsBlacklist(settings.suggestionsTagsBlacklist, 0.4f)\n\t\tval skipNsfw = settings.isSuggestionsExcludeNsfw && !source.isNsfw()\n\t\tval tags = historyRepository.getPopularTags(tagsLimit).mapNotNull {\n\t\t\tif (it in tagsBlacklist) null else it.title\n\t\t}\n\t\tfor (i in 0..4) {\n\t\t\tval list = getList(source, tags, tagsBlacklist)\n\t\t\tval manga = list.randomOrNull() ?: continue\n\t\t\tval details = runCatchingCancellable {\n\t\t\t\tmangaRepositoryFactory.create(manga.source).getDetails(manga)\n\t\t\t}.getOrNull() ?: continue\n\t\t\tif ((skipNsfw && details.isNsfw()) || details in tagsBlacklist) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn details\n\t\t}\n\t\tthrow NoSuchElementException()\n\t}\n\n\tprivate suspend fun getList(\n\t\tsource: MangaSource,\n\t\ttags: List<String>,\n\t\tblacklist: TagsBlacklist,\n\t): List<Manga> = runCatchingCancellable {\n\t\tval repository = mangaRepositoryFactory.create(source)\n\t\tval order = repository.sortOrders.random()\n\t\tval availableTags = repository.getFilterOptions().availableTags\n\t\tval tag = tags.firstNotNullOfOrNull { title ->\n\t\t\tavailableTags.find { x -> x.title.almostEquals(title, 0.4f) }\n\t\t}\n\t\tval list = repository.getList(\n\t\t\toffset = 0,\n\t\t\torder = order,\n\t\t\tfilter = MangaListFilter(tags = setOfNotNull(tag)),\n\t\t).asArrayList()\n\t\tif (settings.isSuggestionsExcludeNsfw) {\n\t\t\tlist.removeAll { it.isNsfw() }\n\t\t}\n\t\tif (blacklist.isNotEmpty()) {\n\t\t\tlist.removeAll { manga -> manga in blacklist }\n\t\t}\n\t\tlist.shuffle()\n\t\tlist\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrDefault(emptyList())\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/RecoverMangaUseCase.kt",
    "content": "package org.koitharu.kotatsu.explore.domain\n\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\nclass RecoverMangaUseCase @Inject constructor(\n\tprivate val mangaDataRepository: MangaDataRepository,\n\tprivate val repositoryFactory: MangaRepository.Factory,\n) {\n\n\tsuspend operator fun invoke(manga: Manga): Manga? = runCatchingCancellable {\n\t\tif (manga.isLocal) {\n\t\t\treturn@runCatchingCancellable null\n\t\t}\n\t\tval repository = repositoryFactory.create(manga.source)\n\t\tval list = repository.getList(offset = 0, null, MangaListFilter(query = manga.title))\n\t\tval newManga = list.find { x -> x.title == manga.title }?.let {\n\t\t\trepository.getDetails(it)\n\t\t} ?: return@runCatchingCancellable null\n\t\tval merged = merge(manga, newManga)\n\t\tmangaDataRepository.storeManga(merged, replaceExisting = true)\n\t\tmerged\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n\n\tprivate fun merge(\n\t\tbroken: Manga,\n\t\tcurrent: Manga,\n\t) = Manga(\n\t\tid = broken.id,\n\t\ttitle = current.title,\n\t\taltTitles = current.altTitles,\n\t\turl = current.url,\n\t\tpublicUrl = current.publicUrl,\n\t\trating = current.rating,\n\t\tcontentRating = current.contentRating,\n\t\tcoverUrl = current.coverUrl,\n\t\ttags = current.tags,\n\t\tstate = current.state,\n\t\tauthors = current.authors,\n\t\tlargeCoverUrl = current.largeCoverUrl,\n\t\tdescription = current.description,\n\t\tchapters = current.chapters,\n\t\tsource = current.source,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt",
    "content": "package org.koitharu.kotatsu.explore.ui\n\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaSource\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.dialog.BigButtonsAlertDialog\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.ui.util.SpanSizeResolver\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.FragmentExploreBinding\nimport org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter\nimport org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener\nimport org.koitharu.kotatsu.explore.ui.model.MangaSourceItem\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\n\n@AndroidEntryPoint\nclass ExploreFragment :\n\tBaseFragment<FragmentExploreBinding>(),\n\tRecyclerViewOwner,\n\tExploreListEventListener,\n\tOnListItemClickListener<MangaSourceItem>, ListSelectionController.Callback {\n\n\tprivate val viewModel by viewModels<ExploreViewModel>()\n\tprivate var exploreAdapter: ExploreAdapter? = null\n\tprivate var sourceSelectionController: ListSelectionController? = null\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerView\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentExploreBinding {\n\t\treturn FragmentExploreBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\texploreAdapter = ExploreAdapter(this, this) { manga, view ->\n\t\t\trouter.openDetails(manga)\n\t\t}\n\t\tsourceSelectionController = ListSelectionController(\n\t\t\tappCompatDelegate = checkNotNull(findAppCompatDelegate()),\n\t\t\tdecoration = SourceSelectionDecoration(binding.root.context),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = this,\n\t\t)\n\t\twith(binding.recyclerView) {\n\t\t\tadapter = exploreAdapter\n\t\t\tsetHasFixedSize(true)\n\t\t\tSpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach()\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\tcheckNotNull(sourceSelectionController).attachToRecyclerView(this)\n\t\t}\n\t\taddMenuProvider(ExploreMenuProvider(router))\n\t\tviewModel.content.observe(viewLifecycleOwner, checkNotNull(exploreAdapter))\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))\n\t\tviewModel.onOpenManga.observeEvent(viewLifecycleOwner, ::onOpenManga)\n\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))\n\t\tviewModel.isGrid.observe(viewLifecycleOwner, ::onGridModeChanged)\n\t\tviewModel.onShowSuggestionsTip.observeEvent(viewLifecycleOwner) {\n\t\t\tshowSuggestionsTip()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval basePadding = v.resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\t/* left = */ barsInsets.left + basePadding,\n\t\t\t/* top = */ basePadding,\n\t\t\t/* right = */ barsInsets.right + basePadding,\n\t\t\t/* bottom = */ barsInsets.bottom + basePadding,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\tsourceSelectionController = null\n\t\texploreAdapter = null\n\t}\n\n\toverride fun onListHeaderClick(item: ListHeader, view: View) {\n\t\tif (item.payload == R.id.nav_suggestions) {\n\t\t\trouter.openSuggestions()\n\t\t} else if (viewModel.isAllSourcesEnabled.value) {\n\t\t\trouter.openManageSources()\n\t\t} else {\n\t\t\trouter.openSourcesCatalog()\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_local -> router.openList(LocalMangaSource, null, null)\n\t\t\tR.id.button_bookmarks -> router.openBookmarks()\n\t\t\tR.id.button_more -> router.openSuggestions()\n\t\t\tR.id.button_downloads -> router.openDownloads()\n\t\t\tR.id.button_random -> viewModel.openRandom()\n\t\t}\n\t}\n\n\toverride fun onItemClick(item: MangaSourceItem, view: View) {\n\t\tif (sourceSelectionController?.onItemClick(item.id) == true) {\n\t\t\treturn\n\t\t}\n\t\trouter.openList(item.source, null, null)\n\t}\n\n\toverride fun onItemLongClick(item: MangaSourceItem, view: View): Boolean {\n\t\treturn sourceSelectionController?.onItemLongClick(view, item.id) == true\n\t}\n\n\toverride fun onItemContextClick(item: MangaSourceItem, view: View): Boolean {\n\t\treturn sourceSelectionController?.onItemContextClick(view, item.id) == true\n\t}\n\n\toverride fun onRetryClick(error: Throwable) = Unit\n\n\toverride fun onEmptyActionClick() = router.openSourcesCatalog()\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\tviewBinding?.recyclerView?.invalidateItemDecorations()\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_source, menu)\n\t\treturn true\n\t}\n\n\toverride fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {\n\t\tval selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds())\n\t\tval isSingleSelection = selectedSources.size == 1\n\t\tmenu.findItem(R.id.action_settings).isVisible = isSingleSelection\n\t\tmenu.findItem(R.id.action_shortcut).isVisible = isSingleSelection\n\t\tmenu.findItem(R.id.action_pin).isVisible = selectedSources.all { !it.isPinned }\n\t\tmenu.findItem(R.id.action_unpin).isVisible = selectedSources.all { it.isPinned }\n\t\tmenu.findItem(R.id.action_disable)?.isVisible = !viewModel.isAllSourcesEnabled.value &&\n\t\t\tselectedSources.all { it.mangaSource is MangaParserSource }\n\t\tmenu.findItem(R.id.action_delete)?.isVisible = selectedSources.all { it.mangaSource is ExternalMangaSource }\n\t\treturn super.onPrepareActionMode(controller, mode, menu)\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\tval selectedSources = viewModel.sourcesSnapshot(controller.peekCheckedIds())\n\t\tif (selectedSources.isEmpty()) {\n\t\t\treturn false\n\t\t}\n\t\twhen (item.itemId) {\n\t\t\tR.id.action_settings -> {\n\t\t\t\tval source = selectedSources.singleOrNull() ?: return false\n\t\t\t\trouter.openSourceSettings(source)\n\t\t\t\tmode?.finish()\n\t\t\t}\n\n\t\t\tR.id.action_disable -> {\n\t\t\t\tviewModel.disableSources(selectedSources)\n\t\t\t\tmode?.finish()\n\t\t\t}\n\n\t\t\tR.id.action_delete -> {\n\t\t\t\tselectedSources.forEach {\n\t\t\t\t\t(it.mangaSource as? ExternalMangaSource)?.let { uninstallExternalSource(it) }\n\t\t\t\t}\n\t\t\t\tmode?.finish()\n\t\t\t}\n\n\t\t\tR.id.action_shortcut -> {\n\t\t\t\tval source = selectedSources.singleOrNull() ?: return false\n\t\t\t\tviewModel.requestPinShortcut(source)\n\t\t\t\tmode?.finish()\n\t\t\t}\n\n\t\t\tR.id.action_pin -> {\n\t\t\t\tviewModel.setSourcesPinned(selectedSources, isPinned = true)\n\t\t\t\tmode?.finish()\n\t\t\t}\n\n\t\t\tR.id.action_unpin -> {\n\t\t\t\tviewModel.setSourcesPinned(selectedSources, isPinned = false)\n\t\t\t\tmode?.finish()\n\t\t\t}\n\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n\n\tprivate fun onOpenManga(manga: Manga) {\n\t\trouter.openDetails(manga)\n\t}\n\n\tprivate fun onGridModeChanged(isGrid: Boolean) {\n\t\trequireViewBinding().recyclerView.layoutManager = if (isGrid) {\n\t\t\tGridLayoutManager(requireContext(), 4).also { lm ->\n\t\t\t\tlm.spanSizeLookup = ExploreGridSpanSizeLookup(checkNotNull(exploreAdapter), lm)\n\t\t\t}\n\t\t} else {\n\t\t\tLinearLayoutManager(requireContext())\n\t\t}\n\t}\n\n\tprivate fun showSuggestionsTip() {\n\t\tval listener = DialogInterface.OnClickListener { _, which ->\n\t\t\tviewModel.respondSuggestionTip(which == DialogInterface.BUTTON_POSITIVE)\n\t\t}\n\t\tBigButtonsAlertDialog.Builder(requireContext())\n\t\t\t.setIcon(R.drawable.ic_suggestion)\n\t\t\t.setTitle(R.string.suggestions_enable_prompt)\n\t\t\t.setPositiveButton(R.string.enable, listener)\n\t\t\t.setNegativeButton(R.string.no_thanks, listener)\n\t\t\t.create()\n\t\t\t.show()\n\t}\n\n\tprivate fun uninstallExternalSource(source: ExternalMangaSource) {\n\t\tval uri = Uri.fromParts(\"package\", source.packageName, null)\n\t\tval action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\t\tIntent.ACTION_DELETE\n\t\t} else {\n\t\t\t@Suppress(\"DEPRECATION\")\n\t\t\tIntent.ACTION_UNINSTALL_PACKAGE\n\t\t}\n\t\tcontext?.startActivity(Intent(action, uri))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreGridSpanSizeLookup.kt",
    "content": "package org.koitharu.kotatsu.explore.ui\n\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup\nimport org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\n\nclass ExploreGridSpanSizeLookup(\n\tprivate val adapter: ExploreAdapter,\n\tprivate val layoutManager: GridLayoutManager,\n) : SpanSizeLookup() {\n\n\toverride fun getSpanSize(position: Int): Int {\n\t\tval itemType = adapter.getItemViewType(position)\n\t\treturn if (itemType == ListItemType.EXPLORE_SOURCE_GRID.ordinal) 1 else layoutManager.spanCount\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.explore.ui\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\n\nclass ExploreMenuProvider(\n\tprivate val router: AppRouter,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_explore, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\treturn when (menuItem.itemId) {\n\t\t\tR.id.action_manage -> {\n\t\t\t\trouter.openSourcesSettings()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt",
    "content": "package org.koitharu.kotatsu.explore.ui\n\nimport androidx.collection.LongSet\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.mapLatest\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.MangaSourceInfo\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.combine\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.explore.domain.ExploreRepository\nimport org.koitharu.kotatsu.explore.ui.model.ExploreButtons\nimport org.koitharu.kotatsu.explore.ui.model.MangaSourceItem\nimport org.koitharu.kotatsu.explore.ui.model.RecommendationsItem\nimport org.koitharu.kotatsu.list.ui.model.EmptyHint\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.MangaCompactListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.suggestions.domain.SuggestionRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ExploreViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val suggestionRepository: SuggestionRepository,\n\tprivate val exploreRepository: ExploreRepository,\n\tprivate val sourcesRepository: MangaSourcesRepository,\n\tprivate val shortcutManager: AppShortcutManager,\n) : BaseViewModel() {\n\n\tval isGrid = settings.observeAsStateFlow(\n\t\tkey = AppSettings.KEY_SOURCES_GRID,\n\t\tscope = viewModelScope + Dispatchers.IO,\n\t\tvalueProducer = { isSourcesGridMode },\n\t)\n\n\tval isAllSourcesEnabled = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.IO,\n\t\tkey = AppSettings.KEY_SOURCES_ENABLED_ALL,\n\t\tvalueProducer = { isAllSourcesEnabled },\n\t)\n\n\tprivate val isSuggestionsEnabled = settings.observeAsFlow(\n\t\tkey = AppSettings.KEY_SUGGESTIONS,\n\t\tvalueProducer = { isSuggestionsEnabled },\n\t)\n\n\tval onOpenManga = MutableEventFlow<Manga>()\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\tval onShowSuggestionsTip = MutableEventFlow<Unit>()\n\tprivate val isRandomLoading = MutableStateFlow(false)\n\n\tval content: StateFlow<List<ListModel>> = isLoading.flatMapLatest { loading ->\n\t\tif (loading) {\n\t\t\tflowOf(getLoadingStateList())\n\t\t} else {\n\t\t\tcreateContentFlow()\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, getLoadingStateList())\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tif (!settings.isSuggestionsEnabled && settings.isTipEnabled(TIP_SUGGESTIONS)) {\n\t\t\t\tonShowSuggestionsTip.call(Unit)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun openRandom() {\n\t\tif (isRandomLoading.value) {\n\t\t\treturn\n\t\t}\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tisRandomLoading.value = true\n\t\t\ttry {\n\t\t\t\tval manga = exploreRepository.findRandomManga(tagsLimit = 8)\n\t\t\t\tonOpenManga.call(manga)\n\t\t\t} finally {\n\t\t\t\tisRandomLoading.value = false\n\t\t\t}\n\t\t}\n\t}\n\n\tfun disableSources(sources: Collection<MangaSource>) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval rollback = sourcesRepository.setSourcesEnabled(sources, isEnabled = false)\n\t\t\tval message = if (sources.size == 1) R.string.source_disabled else R.string.sources_disabled\n\t\t\tonActionDone.call(ReversibleAction(message, rollback))\n\t\t}\n\t}\n\n\tfun requestPinShortcut(source: MangaSource) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tshortcutManager.requestPinShortcut(source)\n\t\t}\n\t}\n\n\tfun setSourcesPinned(sources: Collection<MangaSource>, isPinned: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tsourcesRepository.setIsPinned(sources, isPinned)\n\t\t\tval message = if (sources.size == 1) {\n\t\t\t\tif (isPinned) R.string.source_pinned else R.string.source_unpinned\n\t\t\t} else {\n\t\t\t\tif (isPinned) R.string.sources_pinned else R.string.sources_unpinned\n\t\t\t}\n\t\t\tonActionDone.call(ReversibleAction(message, null))\n\t\t}\n\t}\n\n\tfun respondSuggestionTip(isAccepted: Boolean) {\n\t\tsettings.isSuggestionsEnabled = isAccepted\n\t\tsettings.closeTip(TIP_SUGGESTIONS)\n\t}\n\n\tfun sourcesSnapshot(ids: LongSet): List<MangaSourceInfo> {\n\t\treturn content.value.mapNotNull {\n\t\t\t(it as? MangaSourceItem)?.takeIf { x -> x.id in ids }?.source\n\t\t}\n\t}\n\n\tprivate fun createContentFlow() = combine(\n\t\tsourcesRepository.observeEnabledSources(),\n\t\tgetSuggestionFlow(),\n\t\tisGrid,\n\t\tisRandomLoading,\n\t\tisAllSourcesEnabled,\n\t\tsourcesRepository.observeHasNewSourcesForBadge(),\n\t) { content, suggestions, grid, randomLoading, allSourcesEnabled, newSources ->\n\t\tbuildList(content, suggestions, grid, randomLoading, allSourcesEnabled, newSources)\n\t}.withErrorHandling()\n\n\tprivate fun buildList(\n\t\tsources: List<MangaSourceInfo>,\n\t\trecommendation: List<Manga>,\n\t\tisGrid: Boolean,\n\t\trandomLoading: Boolean,\n\t\tallSourcesEnabled: Boolean,\n\t\thasNewSources: Boolean,\n\t): List<ListModel> {\n\t\tval result = ArrayList<ListModel>(sources.size + 3)\n\t\tresult += ExploreButtons(randomLoading)\n\t\tif (recommendation.isNotEmpty()) {\n\t\t\tresult += ListHeader(R.string.suggestions, R.string.more, R.id.nav_suggestions)\n\t\t\tresult += RecommendationsItem(recommendation.toRecommendationList())\n\t\t}\n\t\tif (sources.isNotEmpty()) {\n\t\t\tresult += ListHeader(\n\t\t\t\ttextRes = R.string.remote_sources,\n\t\t\t\tbuttonTextRes = if (allSourcesEnabled) R.string.manage else R.string.catalog,\n\t\t\t\tbadge = if (!allSourcesEnabled && hasNewSources) \"\" else null,\n\t\t\t)\n\t\t\tsources.mapTo(result) { MangaSourceItem(it, isGrid) }\n\t\t} else {\n\t\t\tresult += EmptyHint(\n\t\t\t\ticon = R.drawable.ic_empty_common,\n\t\t\t\ttextPrimary = R.string.no_manga_sources,\n\t\t\t\ttextSecondary = R.string.no_manga_sources_text,\n\t\t\t\tactionStringRes = R.string.catalog,\n\t\t\t)\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate fun getLoadingStateList() = listOf(\n\t\tExploreButtons(isRandomLoading.value),\n\t\tLoadingState,\n\t)\n\n\tprivate fun getSuggestionFlow() = isSuggestionsEnabled.mapLatest { isEnabled ->\n\t\tif (isEnabled) {\n\t\t\trunCatchingCancellable {\n\t\t\t\tsuggestionRepository.getRandomList(SUGGESTIONS_COUNT)\n\t\t\t}.getOrDefault(emptyList())\n\t\t} else {\n\t\t\temptyList()\n\t\t}\n\t}\n\n\tprivate fun List<Manga>.toRecommendationList() = map { manga ->\n\t\tMangaCompactListModel(\n\t\t\tmanga = manga,\n\t\t\toverride = null,\n\t\t\tsubtitle = manga.tags.joinToString { it.title },\n\t\t\tcounter = 0,\n\t\t)\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val TIP_SUGGESTIONS = \"suggestions\"\n\t\tprivate const val SUGGESTIONS_COUNT = 8\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/SourceSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.explore.ui\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.view.View\nimport androidx.core.graphics.ColorUtils\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.NO_ID\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.explore.ui.model.MangaSourceItem\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nclass SourceSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)\n\tprivate val fillColor = ColorUtils.setAlphaComponent(\n\t\tColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),\n\t\t0x74,\n\t)\n\tprivate val defaultRadius = context.resources.getDimension(R.dimen.list_selector_corner)\n\n\tinit {\n\t\thasBackground = false\n\t\thasForeground = true\n\t\tisIncludeDecorAndMargins = false\n\t\tpaint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)\n\t}\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return NO_ID\n\t\tval item = holder.getItem(MangaSourceItem::class.java) ?: return NO_ID\n\t\treturn item.id\n\t}\n\n\toverride fun onDrawForeground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\tpaint.color = fillColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tcanvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint)\n\t\tpaint.color = strokeColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt",
    "content": "package org.koitharu.kotatsu.explore.ui.adapter\n\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.explore.ui.model.MangaSourceItem\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.emptyHintAD\nimport org.koitharu.kotatsu.list.ui.adapter.listHeaderAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nclass ExploreAdapter(\n\tlistener: ExploreListEventListener,\n\tclickListener: OnListItemClickListener<MangaSourceItem>,\n\tmangaClickListener: OnListItemClickListener<Manga>,\n) : BaseListAdapter<ListModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.EXPLORE_BUTTONS, exploreButtonsAD(listener))\n\t\taddDelegate(\n\t\t\tListItemType.EXPLORE_SUGGESTION,\n\t\t\texploreRecommendationItemAD(mangaClickListener),\n\t\t)\n\t\taddDelegate(ListItemType.HEADER, listHeaderAD(listener))\n\t\taddDelegate(ListItemType.EXPLORE_SOURCE_LIST, exploreSourceListItemAD(clickListener))\n\t\taddDelegate(ListItemType.EXPLORE_SOURCE_GRID, exploreSourceGridItemAD(clickListener))\n\t\taddDelegate(ListItemType.HINT_EMPTY, emptyHintAD(listener))\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt",
    "content": "package org.koitharu.kotatsu.explore.ui.adapter\n\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.text.bold\nimport androidx.core.text.buildSpannedString\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getSummary\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.recyclerView\nimport org.koitharu.kotatsu.core.util.ext.setProgressIcon\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding\nimport org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding\nimport org.koitharu.kotatsu.databinding.ItemExploreSourceListBinding\nimport org.koitharu.kotatsu.databinding.ItemRecommendationBinding\nimport org.koitharu.kotatsu.databinding.ItemRecommendationMangaBinding\nimport org.koitharu.kotatsu.explore.ui.model.ExploreButtons\nimport org.koitharu.kotatsu.explore.ui.model.MangaSourceItem\nimport org.koitharu.kotatsu.explore.ui.model.RecommendationsItem\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaCompactListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nfun exploreButtonsAD(\n\tclickListener: View.OnClickListener,\n) = adapterDelegateViewBinding<ExploreButtons, ListModel, ItemExploreButtonsBinding>(\n\t{ layoutInflater, parent -> ItemExploreButtonsBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.buttonBookmarks.setOnClickListener(clickListener)\n\tbinding.buttonDownloads.setOnClickListener(clickListener)\n\tbinding.buttonLocal.setOnClickListener(clickListener)\n\tbinding.buttonRandom.setOnClickListener(clickListener)\n\n\tbind {\n\t\tif (item.isRandomLoading) {\n\t\t\tbinding.buttonRandom.setProgressIcon()\n\t\t} else {\n\t\t\tbinding.buttonRandom.setIconResource(R.drawable.ic_dice)\n\t\t}\n\t\tbinding.buttonRandom.isClickable = !item.isRandomLoading\n\t}\n}\n\nfun exploreRecommendationItemAD(\n\titemClickListener: OnListItemClickListener<Manga>,\n) = adapterDelegateViewBinding<RecommendationsItem, ListModel, ItemRecommendationBinding>(\n\t{ layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tval adapter = BaseListAdapter<MangaCompactListModel>()\n\t\t.addDelegate(ListItemType.MANGA_LIST, recommendationMangaItemAD(itemClickListener))\n\tbinding.pager.adapter = adapter\n\tbinding.pager.recyclerView?.isNestedScrollingEnabled = false\n\tbinding.dots.bindToViewPager(binding.pager)\n\n\tbind {\n\t\tadapter.items = item.manga\n\t}\n}\n\nfun recommendationMangaItemAD(\n\titemClickListener: OnListItemClickListener<Manga>,\n) = adapterDelegateViewBinding<MangaCompactListModel, MangaCompactListModel, ItemRecommendationMangaBinding>(\n\t{ layoutInflater, parent -> ItemRecommendationMangaBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.root.setOnClickListener { v ->\n\t\titemClickListener.onItemClick(item.manga, v)\n\t}\n\tbind {\n\t\tbinding.textViewTitle.text = item.manga.title\n\t\tbinding.textViewSubtitle.textAndVisible = item.subtitle\n\t\tbinding.imageViewCover.setImageAsync(item.manga.coverUrl, item.manga.source)\n\t}\n}\n\n\nfun exploreSourceListItemAD(\n\tlistener: OnListItemClickListener<MangaSourceItem>,\n) = adapterDelegateViewBinding<MangaSourceItem, ListModel, ItemExploreSourceListBinding>(\n\t{ layoutInflater, parent ->\n\t\tItemExploreSourceListBinding.inflate(\n\t\t\tlayoutInflater,\n\t\t\tparent,\n\t\t\tfalse,\n\t\t)\n\t},\n\ton = { item, _, _ -> item is MangaSourceItem && !item.isGrid },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, listener).attach(itemView)\n\tval iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small)\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.source.getTitle(context)\n\t\tbinding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null\n\t\tbinding.textViewSubtitle.text = item.source.getSummary(context)\n\t\tbinding.imageViewIcon.setImageAsync(item.source)\n\t}\n}\n\nfun exploreSourceGridItemAD(\n\tlistener: OnListItemClickListener<MangaSourceItem>,\n) = adapterDelegateViewBinding<MangaSourceItem, ListModel, ItemExploreSourceGridBinding>(\n\t{ layoutInflater, parent ->\n\t\tItemExploreSourceGridBinding.inflate(\n\t\t\tlayoutInflater,\n\t\t\tparent,\n\t\t\tfalse,\n\t\t)\n\t},\n\ton = { item, _, _ -> item is MangaSourceItem && item.isGrid },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, listener).attach(itemView)\n\tval iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small)\n\n\tbind {\n\t\tval title = item.source.getTitle(context)\n\t\titemView.setTooltipCompat(\n\t\t\tbuildSpannedString {\n\t\t\t\tbold {\n\t\t\t\t\tappend(title)\n\t\t\t\t}\n\t\t\t\tappendLine()\n\t\t\t\tappend(item.source.getSummary(context))\n\t\t\t},\n\t\t)\n\t\tbinding.textViewTitle.text = title\n\t\tbinding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null\n\t\tbinding.imageViewIcon.setImageAsync(item.source)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreListEventListener.kt",
    "content": "package org.koitharu.kotatsu.explore.ui.adapter\n\nimport android.view.View\nimport org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\n\ninterface ExploreListEventListener : ListStateHolderListener, View.OnClickListener, ListHeaderClickListener\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt",
    "content": "package org.koitharu.kotatsu.explore.ui.model\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class ExploreButtons(\n\tval isRandomLoading: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ExploreButtons\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/MangaSourceItem.kt",
    "content": "package org.koitharu.kotatsu.explore.ui.model\n\nimport org.koitharu.kotatsu.core.model.MangaSourceInfo\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.util.longHashCode\n\ndata class MangaSourceItem(\n\tval source: MangaSourceInfo,\n\tval isGrid: Boolean,\n) : ListModel {\n\n\tval id: Long = source.name.longHashCode()\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is MangaSourceItem && other.source == source\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt",
    "content": "package org.koitharu.kotatsu.explore.ui.model\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaCompactListModel\n\ndata class RecommendationsItem(\n\tval manga: List<MangaCompactListModel>\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is RecommendationsItem\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/EntityMapping.kt",
    "content": "package org.koitharu.kotatsu.favourites.data\n\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport java.time.Instant\n\nfun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong()) = FavouriteCategory(\n\tid = id,\n\ttitle = title,\n\tsortKey = sortKey,\n\torder = ListSortOrder(order, ListSortOrder.NEWEST),\n\tcreatedAt = Instant.ofEpochMilli(createdAt),\n\tisTrackingEnabled = track,\n\tisVisibleInLibrary = isVisibleInLibrary,\n)\n\nfun FavouriteManga.toManga() = manga.toManga(tags.toMangaTags(), null)\n\nfun Collection<FavouriteManga>.toMangaList() = map { it.toManga() }\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt",
    "content": "package org.koitharu.kotatsu.favourites.data\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.RoomWarnings\nimport androidx.room.Upsert\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\nabstract class FavouriteCategoriesDao {\n\n\t@Query(\"SELECT * FROM favourite_categories WHERE category_id = :id AND deleted_at = 0\")\n\tabstract suspend fun find(id: Int): FavouriteCategoryEntity\n\n\t@Query(\"SELECT * FROM favourite_categories WHERE deleted_at = 0 ORDER BY sort_key\")\n\tabstract suspend fun findAll(): List<FavouriteCategoryEntity>\n\n\t@Query(\"SELECT * FROM favourite_categories WHERE deleted_at = 0 ORDER BY sort_key\")\n\tabstract fun observeAll(): Flow<List<FavouriteCategoryEntity>>\n\n\t@Query(\"SELECT * FROM favourite_categories WHERE deleted_at = 0 AND show_in_lib = 1 ORDER BY sort_key\")\n\tabstract fun observeAllVisible(): Flow<List<FavouriteCategoryEntity>>\n\n\t@Query(\"SELECT * FROM favourite_categories WHERE category_id = :id AND deleted_at = 0\")\n\tabstract fun observe(id: Long): Flow<FavouriteCategoryEntity?>\n\n\t@Insert(onConflict = OnConflictStrategy.ABORT)\n\tabstract suspend fun insert(category: FavouriteCategoryEntity): Long\n\n\tsuspend fun delete(id: Long) = setDeletedAt(id, System.currentTimeMillis())\n\n\t@Query(\"UPDATE favourite_categories SET title = :title, `order` = :order, `track` = :tracker, `show_in_lib` = :onShelf WHERE category_id = :id\")\n\tabstract suspend fun update(id: Long, title: String, order: String, tracker: Boolean, onShelf: Boolean)\n\n\t@Query(\"UPDATE favourite_categories SET `order` = :order WHERE category_id = :id\")\n\tabstract suspend fun updateOrder(id: Long, order: String)\n\n\t@Query(\"UPDATE favourite_categories SET `track` = :isEnabled WHERE category_id = :id\")\n\tabstract suspend fun updateTracking(id: Long, isEnabled: Boolean)\n\n\t@Query(\"UPDATE favourite_categories SET `show_in_lib` = :isEnabled WHERE category_id = :id\")\n\tabstract suspend fun updateVisibility(id: Long, isEnabled: Boolean)\n\n\t@Query(\"UPDATE favourite_categories SET sort_key = :sortKey WHERE category_id = :id\")\n\tabstract suspend fun updateSortKey(id: Long, sortKey: Int)\n\n\t@Query(\"DELETE FROM favourite_categories WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime\")\n\tabstract suspend fun gc(maxDeletionTime: Long)\n\n\t@Query(\"SELECT MAX(sort_key) FROM favourite_categories WHERE deleted_at = 0\")\n\tprotected abstract suspend fun getMaxSortKey(): Int?\n\n\t@SuppressWarnings(RoomWarnings.QUERY_MISMATCH) // for the new_chapters column\n\t@Query(\"SELECT favourite_categories.*, (SELECT SUM(chapters_new) FROM tracks WHERE tracks.manga_id IN (SELECT manga_id FROM favourites WHERE favourites.category_id = favourite_categories.category_id)) AS new_chapters FROM favourite_categories WHERE track = 1 AND show_in_lib = 1 AND deleted_at = 0 AND new_chapters > 0 ORDER BY new_chapters DESC LIMIT :limit\")\n\tabstract suspend fun getMostUpdatedCategories(limit: Int): List<FavouriteCategoryEntity>\n\n\tsuspend fun getNextSortKey(): Int {\n\t\treturn (getMaxSortKey() ?: 0) + 1\n\t}\n\n\t@Upsert\n\tabstract suspend fun upsert(entity: FavouriteCategoryEntity)\n\n\t@Query(\"UPDATE favourite_categories SET deleted_at = :deletedAt WHERE category_id = :id\")\n\tprotected abstract suspend fun setDeletedAt(id: Long, deletedAt: Long)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt",
    "content": "package org.koitharu.kotatsu.favourites.data\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES\n\n@Entity(tableName = TABLE_FAVOURITE_CATEGORIES)\ndata class FavouriteCategoryEntity(\n\t@PrimaryKey(autoGenerate = true)\n\t@ColumnInfo(name = \"category_id\") val categoryId: Int,\n\t@ColumnInfo(name = \"created_at\") val createdAt: Long,\n\t@ColumnInfo(name = \"sort_key\") val sortKey: Int,\n\t@ColumnInfo(name = \"title\") val title: String,\n\t@ColumnInfo(name = \"order\") val order: String,\n\t@ColumnInfo(name = \"track\") val track: Boolean,\n\t@ColumnInfo(name = \"show_in_lib\") val isVisibleInLibrary: Boolean,\n\t@ColumnInfo(name = \"deleted_at\") val deletedAt: Long,\n) {\n\n\toverride fun equals(other: Any?): Boolean {\n\t\tif (this === other) return true\n\t\tif (javaClass != other?.javaClass) return false\n\n\t\tother as FavouriteCategoryEntity\n\n\t\tif (categoryId != other.categoryId) return false\n\t\tif (createdAt != other.createdAt) return false\n\t\tif (sortKey != other.sortKey) return false\n\t\tif (title != other.title) return false\n\t\tif (order != other.order) return false\n\t\tif (track != other.track) return false\n\t\treturn isVisibleInLibrary == other.isVisibleInLibrary\n\t}\n\n\toverride fun hashCode(): Int {\n\t\tvar result = categoryId\n\t\tresult = 31 * result + createdAt.hashCode()\n\t\tresult = 31 * result + sortKey\n\t\tresult = 31 * result + title.hashCode()\n\t\tresult = 31 * result + order.hashCode()\n\t\tresult = 31 * result + track.hashCode()\n\t\tresult = 31 * result + isVisibleInLibrary.hashCode()\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt",
    "content": "package org.koitharu.kotatsu.favourites.data\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\n@Entity(\n\ttableName = TABLE_FAVOURITES,\n\tprimaryKeys = [\"manga_id\", \"category_id\"],\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE\n\t\t),\n\t\tForeignKey(\n\t\t\tentity = FavouriteCategoryEntity::class,\n\t\t\tparentColumns = [\"category_id\"],\n\t\t\tchildColumns = [\"category_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE\n\t\t)\n\t]\n)\ndata class FavouriteEntity(\n\t@ColumnInfo(name = \"manga_id\", index = true) val mangaId: Long,\n\t@ColumnInfo(name = \"category_id\", index = true) val categoryId: Long,\n\t@ColumnInfo(name = \"sort_key\") val sortKey: Int,\n\t@ColumnInfo(name = \"pinned\") val isPinned: Boolean,\n\t@ColumnInfo(name = \"created_at\") val createdAt: Long,\n\t@ColumnInfo(name = \"deleted_at\") val deletedAt: Long,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouriteManga.kt",
    "content": "package org.koitharu.kotatsu.favourites.data\n\nimport androidx.room.Embedded\nimport androidx.room.Junction\nimport androidx.room.Relation\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaTagsEntity\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\nclass FavouriteManga(\n\t@Embedded val favourite: FavouriteEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"manga_id\"\n\t)\n\tval manga: MangaEntity,\n\t@Relation(\n\t\tparentColumn = \"category_id\",\n\t\tentityColumn = \"category_id\"\n\t)\n\tval categories: List<FavouriteCategoryEntity>,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"tag_id\",\n\t\tassociateBy = Junction(MangaTagsEntity::class)\n\t)\n\tval tags: List<TagEntity>\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt",
    "content": "package org.koitharu.kotatsu.favourites.data\n\nimport android.database.DatabaseUtils.sqlEscapeString\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.RawQuery\nimport androidx.room.Transaction\nimport androidx.room.Upsert\nimport androidx.sqlite.db.SimpleSQLiteQuery\nimport androidx.sqlite.db.SupportSQLiteQuery\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\nimport org.intellij.lang.annotations.Language\nimport org.koitharu.kotatsu.core.db.MangaQueryBuilder\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.favourites.domain.model.Cover\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_COMPLETED\n\n@Dao\nabstract class FavouritesDao : MangaQueryBuilder.ConditionCallback {\n\n\t/** SELECT **/\n\n\t@Transaction\n\t@Query(\"SELECT * FROM favourites WHERE deleted_at = 0 GROUP BY manga_id ORDER BY created_at DESC\")\n\tabstract suspend fun findAll(): List<FavouriteManga>\n\n\t@Transaction\n\t@Query(\"SELECT * FROM favourites WHERE deleted_at = 0 GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit\")\n\tabstract suspend fun findLast(limit: Int): List<FavouriteManga>\n\n\t@Transaction\n\t@Query(\"SELECT manga.* FROM favourites LEFT JOIN manga ON manga.manga_id = favourites.manga_id WHERE favourites.deleted_at = 0 AND (manga.title LIKE :query OR manga.alt_title LIKE :query) LIMIT :limit\")\n\tabstract suspend fun searchByTitle(query: String, limit: Int): List<MangaWithTags>\n\n\t@Transaction\n\t@Query(\"SELECT manga.* FROM favourites LEFT JOIN manga ON manga.manga_id = favourites.manga_id WHERE favourites.deleted_at = 0 AND (manga.author LIKE :query) LIMIT :limit\")\n\tabstract suspend fun searchByAuthor(query: String, limit: Int): List<MangaWithTags>\n\n\t@Transaction\n\t@Query(\"SELECT manga.* FROM favourites LEFT JOIN manga ON manga.manga_id = favourites.manga_id WHERE favourites.deleted_at = 0 AND EXISTS(SELECT 1 FROM tags LEFT JOIN manga_tags ON manga_tags.tag_id = tags.tag_id WHERE manga_tags.manga_id = manga.manga_id AND tags.title LIKE :query) LIMIT :limit\")\n\tabstract suspend fun searchByTag(query: String, limit: Int): List<MangaWithTags>\n\n\tfun observeAll(\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t): Flow<List<FavouriteManga>> = observeAll(0L, order, filterOptions, limit)\n\n\t@Transaction\n\t@Query(\"SELECT * FROM favourites WHERE deleted_at = 0 ORDER BY created_at DESC LIMIT :limit OFFSET :offset\")\n\tabstract suspend fun findAllRaw(offset: Int, limit: Int): List<FavouriteManga>\n\n\t@Query(\"SELECT DISTINCT manga_id FROM favourites WHERE deleted_at = 0 AND category_id IN (SELECT category_id FROM favourite_categories WHERE track = 1 AND deleted_at = 0)\")\n\tabstract suspend fun findIdsWithTrack(): LongArray\n\n\t@Transaction\n\t@Query(\n\t\t\"SELECT * FROM favourites WHERE category_id = :categoryId AND deleted_at = 0 \" +\n\t\t\t\"GROUP BY manga_id ORDER BY created_at DESC\",\n\t)\n\tabstract suspend fun findAll(categoryId: Long): List<FavouriteManga>\n\n\tfun observeAll(\n\t\tcategoryId: Long,\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t): Flow<List<FavouriteManga>> = observeAllImpl(\n\t\tMangaQueryBuilder(TABLE_FAVOURITES, this)\n\t\t\t.join(\"LEFT JOIN manga ON favourites.manga_id = manga.manga_id\")\n\t\t\t.where(\"deleted_at = 0\")\n\t\t\t.where(\n\t\t\t\tif (categoryId != 0L) {\n\t\t\t\t\t\"category_id = $categoryId\"\n\t\t\t\t} else {\n\t\t\t\t\t\"(SELECT show_in_lib FROM favourite_categories WHERE favourite_categories.category_id = favourites.category_id) = 1\"\n\t\t\t\t},\n\t\t\t)\n\t\t\t.filters(filterOptions)\n\t\t\t.groupBy(\"favourites.manga_id\")\n\t\t\t.orderBy(getOrderBy(order))\n\t\t\t.limit(limit)\n\t\t\t.build(),\n\t)\n\n\tsuspend fun findCovers(categoryId: Long, order: ListSortOrder): List<Cover> {\n\t\tval orderBy = getOrderBy(order)\n\n\t\t@Language(\"RoomSql\")\n\t\tval query = SimpleSQLiteQuery(\n\t\t\t\"SELECT manga.cover_url AS url, manga.source AS source FROM favourites \" +\n\t\t\t\t\"LEFT JOIN manga ON favourites.manga_id = manga.manga_id \" +\n\t\t\t\t\"WHERE favourites.category_id = ? AND deleted_at = 0 ORDER BY $orderBy\",\n\t\t\tarrayOf<Any>(categoryId),\n\t\t)\n\t\treturn findCoversImpl(query)\n\t}\n\n\tsuspend fun findCovers(order: ListSortOrder, limit: Int): List<Cover> {\n\t\tval orderBy = getOrderBy(order)\n\n\t\t@Language(\"RoomSql\")\n\t\tval query = SimpleSQLiteQuery(\n\t\t\t\"SELECT manga.cover_url AS url, manga.source AS source FROM favourites \" +\n\t\t\t\t\"LEFT JOIN manga ON favourites.manga_id = manga.manga_id \" +\n\t\t\t\t\"WHERE deleted_at = 0 AND \" +\n\t\t\t\t\"(SELECT show_in_lib FROM favourite_categories WHERE favourite_categories.category_id = favourites.category_id) = 1 \" +\n\t\t\t\t\"GROUP BY manga.manga_id ORDER BY $orderBy LIMIT ?\",\n\t\t\tarrayOf<Any>(limit),\n\t\t)\n\t\treturn findCoversImpl(query)\n\t}\n\n\t@Query(\"SELECT COUNT(DISTINCT manga_id) FROM favourites WHERE deleted_at = 0\")\n\tabstract fun observeMangaCount(): Flow<Int>\n\n\t@Query(\"SELECT * FROM favourites WHERE manga_id = :mangaId AND deleted_at = 0\")\n\tabstract suspend fun findAllRaw(mangaId: Long): List<FavouriteEntity>\n\n\t@Query(\"SELECT DISTINCT category_id FROM favourites WHERE manga_id = :id AND deleted_at = 0\")\n\tabstract fun observeIds(id: Long): Flow<List<Long>>\n\n\t@Query(\"SELECT favourite_categories.* FROM favourites LEFT JOIN favourite_categories ON favourite_categories.category_id = favourites.category_id WHERE favourites.manga_id = :mangaId AND favourites.deleted_at = 0\")\n\tabstract fun observeCategories(mangaId: Long): Flow<List<FavouriteCategoryEntity>>\n\n\t@Query(\"SELECT DISTINCT category_id FROM favourites WHERE manga_id = :mangaId AND deleted_at = 0 ORDER BY favourites.created_at ASC\")\n\tabstract suspend fun findCategoriesIds(mangaId: Long): List<Long>\n\n\t@Query(\"SELECT COUNT(category_id) FROM favourites WHERE manga_id = :mangaId AND deleted_at = 0\")\n\tabstract suspend fun findCategoriesCount(mangaId: Long): Int\n\n\t@Query(\"SELECT manga.source AS count FROM favourites LEFT JOIN manga ON manga.manga_id = favourites.manga_id GROUP BY manga.source ORDER BY COUNT(manga.source) DESC LIMIT :limit\")\n\tabstract suspend fun findPopularSources(limit: Int): List<String>\n\n\t@Query(\"SELECT manga.source AS count FROM favourites LEFT JOIN manga ON manga.manga_id = favourites.manga_id WHERE favourites.category_id = :categoryId GROUP BY manga.source ORDER BY COUNT(manga.source) DESC LIMIT :limit\")\n\tabstract suspend fun findPopularSources(categoryId: Long, limit: Int): List<String>\n\n\tfun dump(): Flow<FavouriteManga> = flow {\n\t\tval window = 10\n\t\tvar offset = 0\n\t\twhile (currentCoroutineContext().isActive) {\n\t\t\tval list = findAllRaw(offset, window)\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset += window\n\t\t\tlist.forEach { emit(it) }\n\t\t}\n\t}\n\n\t/** INSERT **/\n\n\t@Insert(onConflict = OnConflictStrategy.REPLACE)\n\tabstract suspend fun insert(favourite: FavouriteEntity)\n\n\t/** DELETE **/\n\n\tsuspend fun delete(mangaId: Long) = setDeletedAt(\n\t\tmangaId = mangaId,\n\t\tdeletedAt = System.currentTimeMillis(),\n\t)\n\n\tsuspend fun delete(mangaId: Long, categoryId: Long) = setDeletedAt(\n\t\tcategoryId = categoryId,\n\t\tmangaId = mangaId,\n\t\tdeletedAt = System.currentTimeMillis(),\n\t)\n\n\tsuspend fun deleteAll(categoryId: Long) = setDeletedAtAll(\n\t\tcategoryId = categoryId,\n\t\tdeletedAt = System.currentTimeMillis(),\n\t)\n\n\tsuspend fun recover(mangaId: Long) = setDeletedAt(\n\t\tmangaId = mangaId,\n\t\tdeletedAt = 0L,\n\t)\n\n\tsuspend fun recover(categoryId: Long, mangaId: Long) = setDeletedAt(\n\t\tcategoryId = categoryId,\n\t\tmangaId = mangaId,\n\t\tdeletedAt = 0L,\n\t)\n\n\t@Query(\"DELETE FROM favourites WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime\")\n\tabstract suspend fun gc(maxDeletionTime: Long)\n\n\t/** TOOLS **/\n\n\t@Upsert\n\tabstract suspend fun upsert(entity: FavouriteEntity)\n\n\t@Transaction\n\t@RawQuery(observedEntities = [FavouriteEntity::class])\n\tprotected abstract fun observeAllImpl(query: SupportSQLiteQuery): Flow<List<FavouriteManga>>\n\n\t@RawQuery\n\tprotected abstract suspend fun findCoversImpl(query: SupportSQLiteQuery): List<Cover>\n\n\t@Query(\"UPDATE favourites SET deleted_at = :deletedAt WHERE manga_id = :mangaId\")\n\tprotected abstract suspend fun setDeletedAt(mangaId: Long, deletedAt: Long)\n\n\t@Query(\"UPDATE favourites SET deleted_at = :deletedAt WHERE manga_id = :mangaId AND category_id = :categoryId\")\n\tprotected abstract suspend fun setDeletedAt(categoryId: Long, mangaId: Long, deletedAt: Long)\n\n\t@Query(\"UPDATE favourites SET deleted_at = :deletedAt WHERE category_id = :categoryId AND deleted_at = 0\")\n\tprotected abstract suspend fun setDeletedAtAll(categoryId: Long, deletedAt: Long)\n\n\tprivate fun getOrderBy(sortOrder: ListSortOrder) = when (sortOrder) {\n\t\tListSortOrder.RATING -> \"manga.rating DESC\"\n\t\tListSortOrder.NEWEST -> \"favourites.created_at DESC\"\n\t\tListSortOrder.OLDEST -> \"favourites.created_at ASC\"\n\t\tListSortOrder.ALPHABETIC -> \"manga.title ASC\"\n\t\tListSortOrder.ALPHABETIC_REVERSE -> \"manga.title DESC\"\n\t\tListSortOrder.NEW_CHAPTERS -> \"IFNULL((SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC\"\n\t\tListSortOrder.PROGRESS -> \"IFNULL((SELECT percent FROM history WHERE history.manga_id = manga.manga_id), 0) DESC\"\n\t\tListSortOrder.UNREAD -> \"IFNULL((SELECT percent FROM history WHERE history.manga_id = manga.manga_id), 0) ASC\"\n\t\tListSortOrder.LAST_READ -> \"IFNULL((SELECT updated_at FROM history WHERE history.manga_id = manga.manga_id), 0) DESC\"\n\t\tListSortOrder.LONG_AGO_READ -> \"IFNULL((SELECT updated_at FROM history WHERE history.manga_id = manga.manga_id), 0) ASC\"\n\t\tListSortOrder.UPDATED -> \"IFNULL((SELECT last_chapter_date FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC\"\n\n\t\telse -> throw IllegalArgumentException(\"Sort order $sortOrder is not supported\")\n\t}\n\n\toverride fun getCondition(option: ListFilterOption): String? = when (option) {\n\t\tListFilterOption.Macro.COMPLETED -> \"EXISTS(SELECT * FROM history WHERE history.manga_id = favourites.manga_id AND history.percent >= $PROGRESS_COMPLETED)\"\n\t\tListFilterOption.Macro.NEW_CHAPTERS -> \"(SELECT chapters_new FROM tracks WHERE tracks.manga_id = favourites.manga_id) > 0\"\n\t\tListFilterOption.Macro.NSFW -> \"manga.nsfw = 1\"\n\t\tis ListFilterOption.Tag -> \"EXISTS(SELECT * FROM manga_tags WHERE favourites.manga_id = manga_tags.manga_id AND tag_id = ${option.tagId})\"\n\t\tListFilterOption.Downloaded -> \"EXISTS(SELECT * FROM local_index WHERE local_index.manga_id = favourites.manga_id)\"\n\t\tis ListFilterOption.Source -> \"manga.source = ${sqlEscapeString(option.mangaSource.name)}\"\n\t\telse -> null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavoritesListQuickFilter.kt",
    "content": "package org.koitharu.kotatsu.favourites.domain\n\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.MangaListQuickFilter\n\nclass FavoritesListQuickFilter @AssistedInject constructor(\n\t@Assisted private val categoryId: Long,\n\tprivate val settings: AppSettings,\n\tprivate val repository: FavouritesRepository,\n\tnetworkState: NetworkState,\n) : MangaListQuickFilter(settings) {\n\n\tinit {\n\t\tsetFilterOption(ListFilterOption.Downloaded, !networkState.value)\n\t}\n\n\toverride suspend fun getAvailableFilterOptions(): List<ListFilterOption> = buildList {\n\t\tadd(ListFilterOption.Downloaded)\n\t\tif (settings.isTrackerEnabled) {\n\t\t\tadd(ListFilterOption.Macro.NEW_CHAPTERS)\n\t\t}\n\t\tadd(ListFilterOption.Macro.COMPLETED)\n\t\trepository.findPopularSources(categoryId, 3).mapTo(this) {\n\t\t\tListFilterOption.Source(it)\n\t\t}\n\t}\n\n\t@AssistedFactory\n\tinterface Factory {\n\n\t\tfun create(categoryId: Long): FavoritesListQuickFilter\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt",
    "content": "package org.koitharu.kotatsu.favourites.domain\n\nimport androidx.room.withTransaction\nimport dagger.Reusable\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.mapLatest\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES\nimport org.koitharu.kotatsu.core.db.entity.toEntities\nimport org.koitharu.kotatsu.core.db.entity.toEntity\nimport org.koitharu.kotatsu.core.db.entity.toMangaList\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.model.toMangaSources\nimport org.koitharu.kotatsu.core.ui.util.ReversibleHandle\nimport org.koitharu.kotatsu.core.util.ext.mapItems\nimport org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity\nimport org.koitharu.kotatsu.favourites.data.FavouriteEntity\nimport org.koitharu.kotatsu.favourites.data.toFavouriteCategory\nimport org.koitharu.kotatsu.favourites.data.toMangaList\nimport org.koitharu.kotatsu.favourites.domain.model.Cover\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.levenshteinDistance\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport javax.inject.Inject\n\n@Reusable\nclass FavouritesRepository @Inject constructor(\n\tprivate val db: MangaDatabase,\n\tprivate val localObserver: LocalFavoritesObserver,\n) {\n\n\tsuspend fun getAllManga(): List<Manga> {\n\t\tval entities = db.getFavouritesDao().findAll()\n\t\treturn entities.toMangaList()\n\t}\n\n\tsuspend fun getLastManga(limit: Int): List<Manga> {\n\t\tval entities = db.getFavouritesDao().findLast(limit)\n\t\treturn entities.toMangaList()\n\t}\n\n\tsuspend fun search(query: String, kind: SearchKind, limit: Int): List<Manga> {\n\t\tval dao = db.getFavouritesDao()\n\t\tval q = \"%$query%\"\n\t\tval entities = when (kind) {\n\t\t\tSearchKind.SIMPLE,\n\t\t\tSearchKind.TITLE -> dao.searchByTitle(q, limit).sortedBy { it.manga.title.levenshteinDistance(query) }\n\n\t\t\tSearchKind.AUTHOR -> dao.searchByAuthor(q, limit)\n\t\t\tSearchKind.TAG -> dao.searchByTag(q, limit)\n\t\t}\n\t\treturn entities.toMangaList()\n\t}\n\n\tfun observeAll(order: ListSortOrder, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> {\n\t\tif (ListFilterOption.Downloaded in filterOptions) {\n\t\t\treturn localObserver.observeAll(order, filterOptions, limit)\n\t\t}\n\t\treturn db.getFavouritesDao().observeAll(order, filterOptions, limit)\n\t\t\t.map { it.toMangaList() }\n\t}\n\n\tsuspend fun getManga(categoryId: Long): List<Manga> {\n\t\tval entities = db.getFavouritesDao().findAll(categoryId)\n\t\treturn entities.toMangaList()\n\t}\n\n\tfun observeAll(\n\t\tcategoryId: Long,\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t): Flow<List<Manga>> {\n\t\tif (ListFilterOption.Downloaded in filterOptions) {\n\t\t\treturn localObserver.observeAll(categoryId, order, filterOptions, limit)\n\t\t}\n\t\treturn db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit)\n\t\t\t.map { it.toMangaList() }\n\t}\n\n\tfun observeAll(categoryId: Long, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> {\n\t\treturn observeOrder(categoryId)\n\t\t\t.flatMapLatest { order -> observeAll(categoryId, order, filterOptions, limit) }\n\t}\n\n\tfun observeMangaCount(): Flow<Int> {\n\t\treturn db.getFavouritesDao().observeMangaCount()\n\t\t\t.distinctUntilChanged()\n\t}\n\n\tfun observeCategories(): Flow<List<FavouriteCategory>> {\n\t\treturn db.getFavouriteCategoriesDao().observeAll().mapItems {\n\t\t\tit.toFavouriteCategory()\n\t\t}.distinctUntilChanged()\n\t}\n\n\tfun observeCategoriesForLibrary(): Flow<List<FavouriteCategory>> {\n\t\treturn db.getFavouriteCategoriesDao().observeAllVisible().mapItems {\n\t\t\tit.toFavouriteCategory()\n\t\t}.distinctUntilChanged()\n\t}\n\n\tfun observeCategoriesWithCovers(): Flow<Map<FavouriteCategory, List<Cover>>> {\n\t\treturn db.invalidationTracker.createFlow(\n\t\t\tTABLE_FAVOURITES,\n\t\t\tTABLE_FAVOURITE_CATEGORIES,\n\t\t\temitInitialState = true,\n\t\t).mapLatest {\n\t\t\tdb.withTransaction {\n\t\t\t\tval categories = db.getFavouriteCategoriesDao().findAll()\n\t\t\t\tval res = LinkedHashMap<FavouriteCategory, List<Cover>>(categories.size)\n\t\t\t\tfor (entity in categories) {\n\t\t\t\t\tval cat = entity.toFavouriteCategory()\n\t\t\t\t\tres[cat] = db.getFavouritesDao().findCovers(\n\t\t\t\t\t\tcategoryId = cat.id,\n\t\t\t\t\t\torder = cat.order,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tres\n\t\t\t}\n\t\t}.distinctUntilChanged()\n\t}\n\n\tsuspend fun getAllFavoritesCovers(order: ListSortOrder, limit: Int): List<Cover> {\n\t\treturn db.getFavouritesDao().findCovers(order, limit)\n\t}\n\n\tfun observeCategory(id: Long): Flow<FavouriteCategory?> {\n\t\treturn db.getFavouriteCategoriesDao().observe(id)\n\t\t\t.map { it?.toFavouriteCategory() }\n\t}\n\n\tfun observeCategoriesIds(mangaId: Long): Flow<Set<Long>> {\n\t\treturn db.getFavouritesDao().observeIds(mangaId).map { it.toSet() }\n\t}\n\n\tfun observeCategories(mangaId: Long): Flow<Set<FavouriteCategory>> {\n\t\treturn db.getFavouritesDao().observeCategories(mangaId).map {\n\t\t\tit.mapTo(LinkedHashSet(it.size)) { x -> x.toFavouriteCategory() }\n\t\t}\n\t}\n\n\tsuspend fun getCategory(id: Long): FavouriteCategory {\n\t\treturn db.getFavouriteCategoriesDao().find(id.toInt()).toFavouriteCategory()\n\t}\n\n\tsuspend fun isFavorite(mangaId: Long): Boolean {\n\t\treturn db.getFavouritesDao().findCategoriesCount(mangaId) != 0\n\t}\n\n\tsuspend fun getCategoriesIds(mangaId: Long): Set<Long> {\n\t\treturn db.getFavouritesDao().findCategoriesIds(mangaId).toSet()\n\t}\n\n\tsuspend fun findPopularSources(categoryId: Long, limit: Int): List<MangaSource> {\n\t\treturn db.getFavouritesDao().run {\n\t\t\tif (categoryId == 0L) {\n\t\t\t\tfindPopularSources(limit)\n\t\t\t} else {\n\t\t\t\tfindPopularSources(categoryId, limit)\n\t\t\t}\n\t\t}.toMangaSources()\n\t}\n\n\tsuspend fun createCategory(\n\t\ttitle: String,\n\t\tsortOrder: ListSortOrder,\n\t\tisTrackerEnabled: Boolean,\n\t\tisVisibleOnShelf: Boolean,\n\t): FavouriteCategory {\n\t\tval entity = FavouriteCategoryEntity(\n\t\t\ttitle = title,\n\t\t\tcreatedAt = System.currentTimeMillis(),\n\t\t\tsortKey = db.getFavouriteCategoriesDao().getNextSortKey(),\n\t\t\tcategoryId = 0,\n\t\t\torder = sortOrder.name,\n\t\t\ttrack = isTrackerEnabled,\n\t\t\tdeletedAt = 0L,\n\t\t\tisVisibleInLibrary = isVisibleOnShelf,\n\t\t)\n\t\tval id = db.getFavouriteCategoriesDao().insert(entity)\n\t\tval category = entity.toFavouriteCategory(id)\n\t\treturn category\n\t}\n\n\tsuspend fun updateCategory(\n\t\tid: Long,\n\t\ttitle: String,\n\t\tsortOrder: ListSortOrder,\n\t\tisTrackerEnabled: Boolean,\n\t\tisVisibleOnShelf: Boolean,\n\t) {\n\t\tdb.getFavouriteCategoriesDao().update(id, title, sortOrder.name, isTrackerEnabled, isVisibleOnShelf)\n\t}\n\n\tsuspend fun updateCategory(id: Long, isVisibleInLibrary: Boolean) {\n\t\tdb.getFavouriteCategoriesDao().updateVisibility(id, isVisibleInLibrary)\n\t}\n\n\tsuspend fun updateCategoryTracking(id: Long, isTrackingEnabled: Boolean) {\n\t\tdb.getFavouriteCategoriesDao().updateTracking(id, isTrackingEnabled)\n\t}\n\n\tsuspend fun removeCategories(ids: Collection<Long>) {\n\t\tdb.withTransaction {\n\t\t\tfor (id in ids) {\n\t\t\t\tdb.getFavouritesDao().deleteAll(id)\n\t\t\t\tdb.getFavouriteCategoriesDao().delete(id)\n\t\t\t}\n\t\t\tdb.getChaptersDao().gc()\n\t\t}\n\t}\n\n\tsuspend fun setCategoryOrder(id: Long, order: ListSortOrder) {\n\t\tdb.getFavouriteCategoriesDao().updateOrder(id, order.name)\n\t}\n\n\tsuspend fun reorderCategories(orderedIds: List<Long>) {\n\t\tval dao = db.getFavouriteCategoriesDao()\n\t\tdb.withTransaction {\n\t\t\tfor ((i, id) in orderedIds.withIndex()) {\n\t\t\t\tdao.updateSortKey(id, i)\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun addToCategory(categoryId: Long, mangas: Collection<Manga>) {\n\t\tdb.withTransaction {\n\t\t\tfor (manga in mangas) {\n\t\t\t\tval tags = manga.tags.toEntities()\n\t\t\t\tdb.getTagsDao().upsert(tags)\n\t\t\t\tdb.getMangaDao().upsert(manga.toEntity(), tags)\n\t\t\t\tval entity = FavouriteEntity(\n\t\t\t\t\tmangaId = manga.id,\n\t\t\t\t\tcategoryId = categoryId,\n\t\t\t\t\tcreatedAt = System.currentTimeMillis(),\n\t\t\t\t\tsortKey = 0,\n\t\t\t\t\tdeletedAt = 0L,\n\t\t\t\t\tisPinned = false,\n\t\t\t\t)\n\t\t\t\tdb.getFavouritesDao().insert(entity)\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun removeFromFavourites(ids: Collection<Long>): ReversibleHandle {\n\t\tdb.withTransaction {\n\t\t\tfor (id in ids) {\n\t\t\t\tdb.getFavouritesDao().delete(mangaId = id)\n\t\t\t}\n\t\t\tdb.getChaptersDao().gc()\n\t\t}\n\t\treturn ReversibleHandle { recoverToFavourites(ids) }\n\t}\n\n\tsuspend fun removeFromCategory(categoryId: Long, ids: Collection<Long>): ReversibleHandle {\n\t\tdb.withTransaction {\n\t\t\tfor (id in ids) {\n\t\t\t\tdb.getFavouritesDao().delete(categoryId = categoryId, mangaId = id)\n\t\t\t}\n\t\t\tdb.getChaptersDao().gc()\n\t\t}\n\t\treturn ReversibleHandle { recoverToCategory(categoryId, ids) }\n\t}\n\n\tprivate fun observeOrder(categoryId: Long): Flow<ListSortOrder> {\n\t\treturn db.getFavouriteCategoriesDao().observe(categoryId)\n\t\t\t.filterNotNull()\n\t\t\t.map { x -> ListSortOrder(x.order, ListSortOrder.NEWEST) }\n\t\t\t.distinctUntilChanged()\n\t}\n\n\tsuspend fun getMostUpdatedCategories(limit: Int): List<FavouriteCategory> {\n\t\treturn db.getFavouriteCategoriesDao().getMostUpdatedCategories(limit).map {\n\t\t\tit.toFavouriteCategory()\n\t\t}\n\t}\n\n\tprivate suspend fun recoverToFavourites(ids: Collection<Long>) {\n\t\tdb.withTransaction {\n\t\t\tfor (id in ids) {\n\t\t\t\tdb.getFavouritesDao().recover(mangaId = id)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun recoverToCategory(categoryId: Long, ids: Collection<Long>) {\n\t\tdb.withTransaction {\n\t\t\tfor (id in ids) {\n\t\t\t\tdb.getFavouritesDao().recover(mangaId = id, categoryId = categoryId)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/LocalFavoritesObserver.kt",
    "content": "package org.koitharu.kotatsu.favourites.domain\n\nimport dagger.Reusable\nimport kotlinx.coroutines.flow.Flow\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.favourites.data.FavouriteManga\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndex\nimport org.koitharu.kotatsu.local.domain.LocalObserveMapper\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\n@Reusable\nclass LocalFavoritesObserver @Inject constructor(\n\tlocalMangaIndex: LocalMangaIndex,\n\tprivate val db: MangaDatabase,\n) : LocalObserveMapper<FavouriteManga, Manga>(localMangaIndex) {\n\n\tfun observeAll(\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t): Flow<List<Manga>> = db.getFavouritesDao().observeAll(order, filterOptions, limit).mapToLocal()\n\n\tfun observeAll(\n\t\tcategoryId: Long,\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t): Flow<List<Manga>> = db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit).mapToLocal()\n\n\toverride fun toManga(e: FavouriteManga) = e.manga.toManga(e.tags.toMangaTags(), null)\n\n\toverride fun toResult(e: FavouriteManga, manga: Manga) = manga\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/model/Cover.kt",
    "content": "package org.koitharu.kotatsu.favourites.domain.model\n\nimport org.koitharu.kotatsu.core.model.MangaSource\n\ndata class Cover(\n\tval url: String?,\n\tval source: String,\n) {\n\tval mangaSource by lazy { MangaSource(source) }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/FavouritesActivity.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui\n\nimport android.os.Bundle\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.FragmentContainerActivity\nimport org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment\n\nclass FavouritesActivity : FragmentContainerActivity(FavouritesListFragment::class.java) {\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tval categoryTitle = intent.getStringExtra(AppRouter.KEY_TITLE)\n\t\tif (categoryTitle != null) {\n\t\t\ttitle = categoryTitle\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionCallback.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.appcompat.view.ActionMode\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\n\nclass CategoriesSelectionCallback(\n\tprivate val recyclerView: RecyclerView,\n\tprivate val viewModel: FavouritesCategoriesViewModel,\n) : ListSelectionController.Callback {\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\trecyclerView.invalidateItemDecorations()\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_category, menu)\n\t\treturn true\n\t}\n\n\toverride fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {\n\t\tval categories = viewModel.getCategories(controller.peekCheckedIds())\n\t\tvar canShow = categories.isNotEmpty()\n\t\tvar canHide = canShow\n\t\tfor (cat in categories) {\n\t\t\tif (cat.isVisibleInLibrary) {\n\t\t\t\tcanShow = false\n\t\t\t} else {\n\t\t\t\tcanHide = false\n\t\t\t}\n\t\t}\n\t\tmenu.findItem(R.id.action_show)?.isVisible = canShow\n\t\tmenu.findItem(R.id.action_hide)?.isVisible = canHide\n\t\tmode?.title = controller.count.toString()\n\t\treturn true\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_show -> {\n\t\t\t\tviewModel.setIsVisible(controller.snapshot(), true)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_hide -> {\n\t\t\t\tviewModel.setIsVisible(controller.snapshot(), false)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_remove -> {\n\t\t\t\tconfirmDeleteCategories(controller.snapshot(), mode)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\tprivate fun confirmDeleteCategories(ids: Set<Long>, mode: ActionMode?) {\n\t\tbuildAlertDialog(recyclerView.context, isCentered = true) {\n\t\t\tsetMessage(R.string.categories_delete_confirm)\n\t\t\tsetTitle(R.string.remove_category)\n\t\t\tsetIcon(R.drawable.ic_delete)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.remove) { _, _ ->\n\t\t\t\tviewModel.deleteCategories(ids)\n\t\t\t\tmode?.finish()\n\t\t\t}\n\t\t}.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/CategoriesSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.view.View\nimport androidx.core.graphics.ColorUtils\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nclass CategoriesSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate val radius = context.resources.getDimension(R.dimen.list_selector_corner)\n\tprivate val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)\n\tprivate val fillColor = ColorUtils.setAlphaComponent(\n\t\tColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),\n\t\t0x74,\n\t)\n\tprivate val padding = context.resources.getDimension(R.dimen.grid_spacing_outer)\n\n\tinit {\n\t\tpaint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)\n\t\thasForeground = true\n\t\thasBackground = false\n\t\tisIncludeDecorAndMargins = false\n\t}\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return RecyclerView.NO_ID\n\t\tval item = holder.getItem(CategoryListModel::class.java) ?: return RecyclerView.NO_ID\n\t\treturn item.category.id\n\t}\n\n\toverride fun onDrawForeground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\tbounds.inset(padding, padding)\n\t\tpaint.color = fillColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t\tpaint.color = strokeColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport androidx.activity.viewModels\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport coil3.ImageLoader\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityCategoriesBinding\nimport org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoriesAdapter\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass FavouriteCategoriesActivity :\n\tBaseActivity<ActivityCategoriesBinding>(),\n\tFavouriteCategoriesListListener,\n\tView.OnClickListener,\n\tListStateHolderListener {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate val viewModel by viewModels<FavouritesCategoriesViewModel>()\n\n\tprivate lateinit var adapter: CategoriesAdapter\n\tprivate lateinit var selectionController: ListSelectionController\n\tprivate lateinit var reorderHelper: ItemTouchHelper\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityCategoriesBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tadapter = CategoriesAdapter(this, this)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = delegate,\n\t\t\tdecoration = CategoriesSelectionDecoration(this),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = CategoriesSelectionCallback(viewBinding.recyclerView, viewModel),\n\t\t)\n\t\tselectionController.attachToRecyclerView(viewBinding.recyclerView)\n\t\tviewBinding.recyclerView.setHasFixedSize(true)\n\t\tviewBinding.recyclerView.adapter = adapter\n\t\tviewBinding.recyclerView.addItemDecoration(TypedListSpacingDecoration(this, false))\n\t\tviewBinding.fabAdd.setOnClickListener(this)\n\n\t\treorderHelper = ItemTouchHelper(ReorderHelperCallback()).apply {\n\t\t\tattachToRecyclerView(viewBinding.recyclerView)\n\t\t}\n\n\t\tviewModel.content.observe(this, ::onCategoriesChanged)\n\t\tviewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.recyclerView.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\ttop = barsInsets.top,\n\t\t)\n\t\tviewBinding.fabAdd.updateLayoutParams<MarginLayoutParams> {\n\t\t\tmarginEnd = topMargin + barsInsets.end(v)\n\t\t\tbottomMargin = topMargin + barsInsets.bottom\n\t\t}\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.fab_add -> router.openFavoriteCategoryCreate()\n\t\t}\n\t}\n\n\toverride fun onItemClick(item: FavouriteCategory?, view: View) {\n\t\tif (item == null) {\n\t\t\tif (selectionController.count == 0) {\n\t\t\t\trouter.openFavorites()\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (selectionController.onItemClick(item.id)) {\n\t\t\treturn\n\t\t}\n\t\trouter.openFavorites(item)\n\t}\n\n\toverride fun onEditClick(item: FavouriteCategory, view: View) {\n\t\tif (selectionController.onItemClick(item.id)) {\n\t\t\treturn\n\t\t}\n\t\trouter.openFavoriteCategoryEdit(item.id)\n\t}\n\n\toverride fun onItemLongClick(item: FavouriteCategory?, view: View): Boolean {\n\t\treturn item != null && selectionController.onItemLongClick(view, item.id)\n\t}\n\n\toverride fun onItemContextClick(item: FavouriteCategory?, view: View): Boolean {\n\t\treturn item != null && selectionController.onItemContextClick(view, item.id)\n\t}\n\n\toverride fun onSupportActionModeStarted(mode: ActionMode) {\n\t\tsuper.onSupportActionModeStarted(mode)\n\t\tviewBinding.fabAdd.hide()\n\t\tviewModel.setActionsEnabled(false)\n\t}\n\n\toverride fun onSupportActionModeFinished(mode: ActionMode) {\n\t\tsuper.onSupportActionModeFinished(mode)\n\t\tviewBinding.fabAdd.show()\n\t\tviewModel.setActionsEnabled(true)\n\t}\n\n\toverride fun onShowAllClick(isChecked: Boolean) {\n\t\tviewModel.setAllCategoriesVisible(isChecked)\n\t}\n\n\toverride fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean {\n\t\treorderHelper.startDrag(holder)\n\t\treturn true\n\t}\n\n\toverride fun onRetryClick(error: Throwable) = Unit\n\n\toverride fun onEmptyActionClick() = Unit\n\n\tprivate suspend fun onCategoriesChanged(categories: List<ListModel>) {\n\t\tadapter.emit(categories)\n\t\tinvalidateOptionsMenu()\n\t}\n\n\tprivate inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(\n\t\tItemTouchHelper.DOWN or ItemTouchHelper.UP,\n\t\t0,\n\t) {\n\n\t\toverride fun getDragDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {\n\t\t\treturn if (actionModeDelegate.isActionModeStarted) {\n\t\t\t\t0\n\t\t\t} else {\n\t\t\t\tsuper.getDragDirs(recyclerView, viewHolder)\n\t\t\t}\n\t\t}\n\n\t\toverride fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit\n\n\t\toverride fun onMove(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tviewHolder: RecyclerView.ViewHolder,\n\t\t\ttarget: RecyclerView.ViewHolder,\n\t\t): Boolean {\n\t\t\tif (viewHolder.itemViewType != target.itemViewType) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tval fromPos = viewHolder.bindingAdapterPosition\n\t\t\tval toPos = target.bindingAdapterPosition\n\t\t\tif (fromPos == toPos || fromPos == RecyclerView.NO_POSITION || toPos == RecyclerView.NO_POSITION) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tadapter.reorderItems(fromPos, toPos)\n\t\t\treturn true\n\t\t}\n\n\t\toverride fun canDropOver(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tcurrent: RecyclerView.ViewHolder,\n\t\t\ttarget: RecyclerView.ViewHolder,\n\t\t): Boolean = current.itemViewType == target.itemViewType\n\n\t\toverride fun isLongPressDragEnabled(): Boolean = false\n\n\t\toverride fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {\n\t\t\tsuper.onSelectedChanged(viewHolder, actionState)\n\t\t\tviewBinding.recyclerView.isNestedScrollingEnabled = actionState == ItemTouchHelper.ACTION_STATE_IDLE\n\t\t}\n\n\t\toverride fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n\t\t\tsuper.clearView(recyclerView, viewHolder)\n\t\t\tviewModel.saveOrder(adapter.items ?: return)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesListListener.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories\n\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\n\ninterface FavouriteCategoriesListListener : OnListItemClickListener<FavouriteCategory?> {\n\n\tfun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean\n\n\tfun onEditClick(item: FavouriteCategory, view: View)\n\n\tfun onShowAllClick(isChecked: Boolean)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouritesCategoriesViewModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories\n\nimport androidx.collection.LongSet\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.mapLatest\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.requireValue\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.favourites.domain.model.Cover\nimport org.koitharu.kotatsu.favourites.ui.categories.adapter.AllCategoriesListModel\nimport org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport javax.inject.Inject\n\n@HiltViewModel\nclass FavouritesCategoriesViewModel @Inject constructor(\n\tprivate val repository: FavouritesRepository,\n\tprivate val settings: AppSettings,\n) : BaseViewModel() {\n\n\tprivate var commitJob: Job? = null\n\tprivate val isActionsEnabled = MutableStateFlow(true)\n\n\tval content = combine(\n\t\trepository.observeCategoriesWithCovers(),\n\t\tobserveAllCategories(),\n\t\tsettings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) { isAllFavouritesVisible },\n\t\tisActionsEnabled,\n\t) { cats, all, showAll, hasActions ->\n\t\tcats.toUiList(all, showAll, hasActions)\n\t}.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tfun deleteCategories(ids: Set<Long>) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.removeCategories(ids)\n\t\t}\n\t}\n\n\tfun setAllCategoriesVisible(isVisible: Boolean) {\n\t\tsettings.isAllFavouritesVisible = isVisible\n\t}\n\n\tfun isEmpty(): Boolean = content.value.none { it is CategoryListModel }\n\n\tfun saveOrder(snapshot: List<ListModel>) {\n\t\tval prevJob = commitJob\n\t\tcommitJob = launchJob {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tval ids = snapshot.mapNotNullTo(ArrayList(snapshot.size)) {\n\t\t\t\t(it as? CategoryListModel)?.category?.id\n\t\t\t}\n\t\t\tif (ids.isNotEmpty()) {\n\t\t\t\trepository.reorderCategories(ids)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun setIsVisible(ids: Set<Long>, isVisible: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tfor (id in ids) {\n\t\t\t\trepository.updateCategory(id, isVisible)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun setActionsEnabled(value: Boolean) {\n\t\tisActionsEnabled.value = value\n\t}\n\n\tfun getCategories(ids: LongSet): ArrayList<FavouriteCategory> {\n\t\tval items = content.requireValue()\n\t\treturn items.mapNotNullTo(ArrayList(ids.size)) { item ->\n\t\t\t(item as? CategoryListModel)?.category?.takeIf { it.id in ids }\n\t\t}\n\t}\n\n\tprivate fun Map<FavouriteCategory, List<Cover>>.toUiList(\n\t\tallFavorites: Pair<Int, List<Cover>>,\n\t\tshowAll: Boolean,\n\t\thasActions: Boolean,\n\t): List<ListModel> {\n\t\tif (isEmpty()) {\n\t\t\treturn listOf(\n\t\t\t\tEmptyState(\n\t\t\t\t\ticon = R.drawable.ic_empty_favourites,\n\t\t\t\t\ttextPrimary = R.string.text_empty_holder_primary,\n\t\t\t\t\ttextSecondary = R.string.empty_favourite_categories,\n\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tval result = ArrayList<ListModel>(size + 1)\n\t\tresult.add(\n\t\t\tAllCategoriesListModel(\n\t\t\t\tmangaCount = allFavorites.first,\n\t\t\t\tcovers = allFavorites.second,\n\t\t\t\tisVisible = showAll,\n\t\t\t\tisActionsEnabled = hasActions,\n\t\t\t),\n\t\t)\n\t\tmapTo(result) { (category, covers) ->\n\t\t\tCategoryListModel(\n\t\t\t\tmangaCount = covers.size,\n\t\t\t\tcovers = covers.take(3),\n\t\t\t\tcategory = category,\n\t\t\t\tisActionsEnabled = hasActions,\n\t\t\t\tisTrackerEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources,\n\t\t\t)\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate fun observeAllCategories(): Flow<Pair<Int, List<Cover>>> {\n\t\treturn settings.observeAsFlow(AppSettings.KEY_FAVORITES_ORDER) {\n\t\t\tallFavoritesSortOrder\n\t\t}.mapLatest { order ->\n\t\t\trepository.getAllFavoritesCovers(order, limit = 3)\n\t\t}.combine(repository.observeMangaCount()) { covers, count ->\n\t\t\tcount to covers\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/AllCategoriesListModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.adapter\n\nimport org.koitharu.kotatsu.favourites.domain.model.Cover\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class AllCategoriesListModel(\n\tval mangaCount: Int,\n\tval covers: List<Cover>,\n\tval isVisible: Boolean,\n\tval isActionsEnabled: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is AllCategoriesListModel\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is AllCategoriesListModel -> super.getChangePayload(previousState)\n\t\tpreviousState.isVisible != isVisible -> ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\tpreviousState.isActionsEnabled != isActionsEnabled -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED\n\t\telse -> super.getChangePayload(previousState)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.adapter\n\nimport org.koitharu.kotatsu.core.ui.ReorderableListAdapter\nimport org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass CategoriesAdapter(\n\tonItemClickListener: FavouriteCategoriesListListener,\n\tlistListener: ListStateHolderListener,\n) : ReorderableListAdapter<ListModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.CATEGORY_LARGE, categoryAD(onItemClickListener))\n\t\taddDelegate(ListItemType.NAV_ITEM, allCategoriesAD(onItemClickListener))\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listListener))\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.adapter\n\nimport android.annotation.SuppressLint\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.View.OnClickListener\nimport android.view.View.OnLongClickListener\nimport android.view.View.OnTouchListener\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding\nimport org.koitharu.kotatsu.databinding.ItemCategoryBinding\nimport org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\n@SuppressLint(\"ClickableViewAccessibility\")\nfun categoryAD(\n\tclickListener: FavouriteCategoriesListListener,\n) = adapterDelegateViewBinding<CategoryListModel, ListModel, ItemCategoryBinding>(\n\t{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) },\n) {\n\tval eventListener = object : OnClickListener, OnLongClickListener, OnTouchListener {\n\t\toverride fun onClick(v: View) = if (v.id == R.id.imageView_edit) {\n\t\t\tclickListener.onEditClick(item.category, v)\n\t\t} else {\n\t\t\tclickListener.onItemClick(item.category, v)\n\t\t}\n\n\t\toverride fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, v)\n\t\toverride fun onTouch(v: View?, event: MotionEvent): Boolean = event.actionMasked == MotionEvent.ACTION_DOWN &&\n\t\t\tclickListener.onDragHandleTouch(this@adapterDelegateViewBinding)\n\t}\n\titemView.setOnClickListener(eventListener)\n\titemView.setOnLongClickListener(eventListener)\n\tbinding.imageViewEdit.setOnClickListener(eventListener)\n\tbinding.imageViewHandle.setOnTouchListener(eventListener)\n\n\tbind {\n\t\tbinding.imageViewHandle.isVisible = item.isActionsEnabled\n\t\tbinding.imageViewEdit.isVisible = item.isActionsEnabled\n\t\tbinding.textViewTitle.text = item.category.title\n\t\tbinding.textViewSubtitle.text = if (item.mangaCount == 0) {\n\t\t\tgetString(R.string.empty)\n\t\t} else {\n\t\t\tcontext.resources.getQuantityStringSafe(\n\t\t\t\tR.plurals.items,\n\t\t\t\titem.mangaCount,\n\t\t\t\titem.mangaCount,\n\t\t\t)\n\t\t}\n\t\tbinding.imageViewTracker.isVisible = item.category.isTrackingEnabled\n\t\tbinding.imageViewHidden.isGone = item.category.isVisibleInLibrary\n\t\tbinding.coversView.setCoversAsync(item.covers)\n\t}\n}\n\nfun allCategoriesAD(\n\tclickListener: FavouriteCategoriesListListener,\n) = adapterDelegateViewBinding<AllCategoriesListModel, ListModel, ItemCategoriesAllBinding>(\n\t{ inflater, parent -> ItemCategoriesAllBinding.inflate(inflater, parent, false) },\n) {\n\tval eventListener = OnClickListener { v ->\n\t\tif (v.id == R.id.imageView_visible) {\n\t\t\tclickListener.onShowAllClick(!item.isVisible)\n\t\t} else {\n\t\t\tclickListener.onItemClick(null, v)\n\t\t}\n\t}\n\n\titemView.setOnClickListener(eventListener)\n\tbinding.imageViewVisible.setOnClickListener(eventListener)\n\n\tbind {\n\t\tbinding.textViewSubtitle.text = if (item.mangaCount == 0) {\n\t\t\tgetString(R.string.empty)\n\t\t} else {\n\t\t\tcontext.resources.getQuantityStringSafe(\n\t\t\t\tR.plurals.items,\n\t\t\t\titem.mangaCount,\n\t\t\t\titem.mangaCount,\n\t\t\t)\n\t\t}\n\t\tbinding.imageViewVisible.isVisible = item.isActionsEnabled\n\t\tbinding.imageViewVisible.setImageResource(\n\t\t\tif (item.isVisible) {\n\t\t\t\tR.drawable.ic_eye\n\t\t\t} else {\n\t\t\t\tR.drawable.ic_eye_off\n\t\t\t},\n\t\t)\n\t\tbinding.imageViewVisible.setTooltipCompat(\n\t\t\tif (item.isVisible) {\n\t\t\t\tR.string.hide\n\t\t\t} else {\n\t\t\t\tR.string.show\n\t\t\t},\n\t\t)\n\t\tbinding.coversView.setCoversAsync(item.covers)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryListModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.adapter\n\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.favourites.domain.model.Cover\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass CategoryListModel(\n\tval mangaCount: Int,\n\tval covers: List<Cover>,\n\tval category: FavouriteCategory,\n\tval isTrackerEnabled: Boolean,\n\tval isActionsEnabled: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is CategoryListModel && other.category.id == category.id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is CategoryListModel -> super.getChangePayload(previousState)\n\t\tpreviousState.isActionsEnabled != isActionsEnabled -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED\n\t\telse -> super.getChangePayload(previousState)\n\t}\n\n\toverride fun equals(other: Any?): Boolean {\n\t\tif (this === other) return true\n\t\tif (javaClass != other?.javaClass) return false\n\n\t\tother as CategoryListModel\n\n\t\tif (mangaCount != other.mangaCount) return false\n\t\tif (isTrackerEnabled != other.isTrackerEnabled) return false\n\t\tif (isActionsEnabled != other.isActionsEnabled) return false\n\t\tif (covers != other.covers) return false\n\t\tif (category.id != other.category.id) return false\n\t\tif (category.title != other.category.title) return false\n\t\t// ignore the category.sortKey field\n\t\tif (category.order != other.category.order) return false\n\t\tif (category.createdAt != other.category.createdAt) return false\n\t\tif (category.isTrackingEnabled != other.category.isTrackingEnabled) return false\n\t\treturn category.isVisibleInLibrary == other.category.isVisibleInLibrary\n\t}\n\n\toverride fun hashCode(): Int {\n\t\tvar result = mangaCount\n\t\tresult = 31 * result + isTrackerEnabled.hashCode()\n\t\tresult = 31 * result + isActionsEnabled.hashCode()\n\t\tresult = 31 * result + covers.hashCode()\n\t\tresult = 31 * result + category.id.hashCode()\n\t\tresult = 31 * result + category.title.hashCode()\n\t\t// ignore the category.sortKey field\n\t\tresult = 31 * result + category.order.hashCode()\n\t\tresult = 31 * result + category.createdAt.hashCode()\n\t\tresult = 31 * result + category.isTrackingEnabled.hashCode()\n\t\tresult = 31 * result + category.isVisibleInLibrary.hashCode()\n\t\treturn result\n\t}\n\n\toverride fun toString(): String {\n\t\treturn \"CategoryListModel(categoryId=${category.id})\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/edit/FavouritesCategoryEditActivity.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.edit\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.text.Editable\nimport android.view.View\nimport android.widget.AdapterView\nimport android.widget.ArrayAdapter\nimport android.widget.Filter\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getSerializableCompat\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.setChecked\nimport org.koitharu.kotatsu.core.util.ext.sortedByOrdinal\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\n\n@AndroidEntryPoint\nclass FavouritesCategoryEditActivity :\n\tBaseActivity<ActivityCategoryEditBinding>(),\n\tAdapterView.OnItemClickListener,\n\tView.OnClickListener,\n\tDefaultTextWatcher {\n\n\tprivate val viewModel by viewModels<FavouritesCategoryEditViewModel>()\n\tprivate var selectedSortOrder: ListSortOrder? = null\n\tprivate val sortOrders = ListSortOrder.FAVORITES.sortedByOrdinal()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityCategoryEditBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tinitSortSpinner()\n\t\tviewBinding.buttonDone.setOnClickListener(this)\n\t\tviewBinding.editName.addTextChangedListener(this)\n\t\tafterTextChanged(viewBinding.editName.text)\n\n\t\tviewModel.onSaved.observeEvent(this) { finishAfterTransition() }\n\t\tviewModel.category.observe(this, ::onCategoryChanged)\n\t\tviewModel.isLoading.observe(this, ::onLoadingStateChanged)\n\t\tviewModel.onError.observeEvent(this, ::onError)\n\t\tviewModel.isTrackerEnabled.observe(this) {\n\t\t\tviewBinding.switchTracker.isVisible = it\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.root.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onSaveInstanceState(outState: Bundle) {\n\t\tsuper.onSaveInstanceState(outState)\n\t\toutState.putSerializable(KEY_SORT_ORDER, selectedSortOrder)\n\t}\n\n\toverride fun onRestoreInstanceState(savedInstanceState: Bundle) {\n\t\tsuper.onRestoreInstanceState(savedInstanceState)\n\t\tsavedInstanceState.getSerializableCompat<ListSortOrder>(KEY_SORT_ORDER)?.let {\n\t\t\tselectedSortOrder = it\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_done -> viewModel.save(\n\t\t\t\ttitle = viewBinding.editName.text?.toString()?.trim().orEmpty(),\n\t\t\t\tsortOrder = getSelectedSortOrder(),\n\t\t\t\tisTrackerEnabled = viewBinding.switchTracker.isChecked,\n\t\t\t\tisVisibleOnShelf = viewBinding.switchShelf.isChecked,\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tviewBinding.buttonDone.isEnabled = !s.isNullOrBlank() && !viewModel.isLoading.value\n\t}\n\n\toverride fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {\n\t\tselectedSortOrder = sortOrders.getOrNull(position)\n\t}\n\n\tprivate fun onCategoryChanged(category: FavouriteCategory?) {\n\t\tsetTitle(if (category == null) R.string.create_category else R.string.edit_category)\n\t\tif (selectedSortOrder != null) {\n\t\t\treturn\n\t\t}\n\t\tviewBinding.editName.setText(category?.title)\n\t\tselectedSortOrder = category?.order\n\t\tval sortText = getString((category?.order ?: ListSortOrder.NEWEST).titleResId)\n\t\tviewBinding.editSort.setText(sortText, false)\n\t\tviewBinding.switchTracker.setChecked(category?.isTrackingEnabled != false, false)\n\t\tviewBinding.switchShelf.setChecked(category?.isVisibleInLibrary != false, false)\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tviewBinding.textViewError.text = e.getDisplayMessage(resources)\n\t\tviewBinding.textViewError.isVisible = true\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tviewBinding.buttonDone.isEnabled = !isLoading && !viewBinding.editName.text.isNullOrBlank()\n\t\tviewBinding.editSort.isEnabled = !isLoading\n\t\tviewBinding.editName.isEnabled = !isLoading\n\t\tviewBinding.switchTracker.isEnabled = !isLoading\n\t\tviewBinding.switchShelf.isEnabled = !isLoading\n\t\tif (isLoading) {\n\t\t\tviewBinding.textViewError.isVisible = false\n\t\t}\n\t}\n\n\tprivate fun initSortSpinner() {\n\t\tval entries = sortOrders.map { getString(it.titleResId) }\n\t\tval adapter = SortAdapter(this, entries)\n\t\tviewBinding.editSort.setAdapter(adapter)\n\t\tviewBinding.editSort.onItemClickListener = this\n\t}\n\n\tprivate fun getSelectedSortOrder(): ListSortOrder {\n\t\tselectedSortOrder?.let { return it }\n\t\tval entries = sortOrders.map { getString(it.titleResId) }\n\t\tval index = entries.indexOf(viewBinding.editSort.text.toString())\n\t\treturn sortOrders.getOrNull(index) ?: ListSortOrder.NEWEST\n\t}\n\n\tprivate class SortAdapter(\n\t\tcontext: Context,\n\t\tentries: List<String>,\n\t) : ArrayAdapter<String>(context, android.R.layout.simple_spinner_dropdown_item, entries) {\n\n\t\toverride fun getFilter(): Filter = EmptyFilter\n\n\t\tprivate object EmptyFilter : Filter() {\n\t\t\toverride fun performFiltering(constraint: CharSequence?) = FilterResults()\n\t\t\toverride fun publishResults(constraint: CharSequence?, results: FilterResults?) = Unit\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tconst val NO_ID = -1L\n\t\tprivate const val KEY_SORT_ORDER = \"sort\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/edit/FavouritesCategoryEditViewModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.edit\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.NO_ID\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport javax.inject.Inject\n\n@HiltViewModel\nclass FavouritesCategoryEditViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val repository: FavouritesRepository,\n\tprivate val settings: AppSettings,\n) : BaseViewModel() {\n\n\tprivate val categoryId = savedStateHandle[AppRouter.KEY_ID] ?: NO_ID\n\n\tval onSaved = MutableEventFlow<Unit>()\n\tval category = MutableStateFlow<FavouriteCategory?>(null)\n\n\tval isTrackerEnabled = flow {\n\t\temit(settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)\n\n\tinit {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tcategory.value = if (categoryId != NO_ID) {\n\t\t\t\trepository.getCategory(categoryId)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}\n\t}\n\n\tfun save(\n\t\ttitle: String,\n\t\tsortOrder: ListSortOrder,\n\t\tisTrackerEnabled: Boolean,\n\t\tisVisibleOnShelf: Boolean,\n\t) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tcheck(title.isNotEmpty())\n\t\t\tif (categoryId == NO_ID) {\n\t\t\t\trepository.createCategory(title, sortOrder, isTrackerEnabled, isVisibleOnShelf)\n\t\t\t} else {\n\t\t\t\trepository.updateCategory(categoryId, title, sortOrder, isTrackerEnabled, isVisibleOnShelf)\n\t\t\t}\n\t\t\tonSaved.call(Unit)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteDialog.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.select\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.checkbox.MaterialCheckBox\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.databinding.DialogFavoriteBinding\nimport org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter\nimport org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem\n\n@AndroidEntryPoint\nclass FavoriteDialog : AlertDialogFragment<DialogFavoriteBinding>(),\n\tOnListItemClickListener<MangaCategoryItem>, DialogInterface.OnClickListener {\n\n\tprivate val viewModel by viewModels<FavoriteDialogViewModel>()\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = DialogFavoriteBinding.inflate(inflater, container, false)\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder)\n\t\t\t.setPositiveButton(R.string.done, null)\n\t\t\t.setNeutralButton(R.string.manage, this)\n\t}\n\n\toverride fun onViewBindingCreated(\n\t\tbinding: DialogFavoriteBinding,\n\t\tsavedInstanceState: Bundle?,\n\t) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval adapter = MangaCategoriesAdapter(this)\n\t\tbinding.recyclerViewCategories.adapter = adapter\n\t\tviewModel.content.observe(viewLifecycleOwner, adapter)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, ::onError)\n\t\tbindHeader()\n\t}\n\n\toverride fun onItemClick(item: MangaCategoryItem, view: View) {\n\t\tviewModel.setChecked(item.category.id, item.checkedState != MaterialCheckBox.STATE_CHECKED)\n\t}\n\n\toverride fun onClick(dialog: DialogInterface?, which: Int) {\n\t\trouter.openFavoriteCategories()\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tToast.makeText(context ?: return, e.getDisplayMessage(resources), Toast.LENGTH_SHORT).show()\n\t}\n\n\tprivate fun bindHeader() {\n\t\tval manga = viewModel.manga\n\t\tval binding = viewBinding ?: return\n\t\tbinding.textViewTitle.text = manga.joinToStringWithLimit(binding.root.context, 92) { it.title }\n\t\tbinding.coversStack.setCoversAsync(manga)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteDialogViewModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.select\n\nimport androidx.collection.MutableLongObjectMap\nimport androidx.collection.MutableLongSet\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport com.google.android.material.checkbox.MaterialCheckBox\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.model.ids\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport javax.inject.Inject\n\n@HiltViewModel\nclass FavoriteDialogViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val favouritesRepository: FavouritesRepository,\n\tsettings: AppSettings,\n) : BaseViewModel() {\n\n\tval manga = savedStateHandle.require<List<ParcelableManga>>(AppRouter.KEY_MANGA_LIST).map {\n\t\tit.manga\n\t}\n\n\tprivate val refreshTrigger = MutableStateFlow(Any())\n\tval content = combine(\n\t\tfavouritesRepository.observeCategories(),\n\t\trefreshTrigger,\n\t\tsettings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled },\n\t) { categories, _, tracker ->\n\t\tmapList(categories, tracker)\n\t}.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tfun setChecked(categoryId: Long, isChecked: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tif (isChecked) {\n\t\t\t\tfavouritesRepository.addToCategory(categoryId, manga)\n\t\t\t} else {\n\t\t\t\tfavouritesRepository.removeFromCategory(categoryId, manga.ids())\n\t\t\t}\n\t\t\trefreshTrigger.value = Any()\n\t\t}\n\t}\n\n\tprivate suspend fun mapList(categories: List<FavouriteCategory>, tracker: Boolean): List<ListModel> {\n\t\tif (categories.isEmpty()) {\n\t\t\treturn listOf(\n\t\t\t\tEmptyState(\n\t\t\t\t\ticon = 0,\n\t\t\t\t\ttextPrimary = R.string.empty_favourite_categories,\n\t\t\t\t\ttextSecondary = 0,\n\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tval cats = MutableLongObjectMap<MutableLongSet>(categories.size)\n\t\tcategories.forEach { cats[it.id] = MutableLongSet(manga.size) }\n\t\tfor (m in manga) {\n\t\t\tval ids = favouritesRepository.getCategoriesIds(m.id)\n\t\t\tids.forEach { id -> cats[id]?.add(m.id) }\n\t\t}\n\t\treturn categories.map { cat ->\n\t\t\tMangaCategoryItem(\n\t\t\t\tcategory = cat,\n\t\t\t\tcheckedState = when (cats[cat.id]?.size ?: 0) {\n\t\t\t\t\t0 -> MaterialCheckBox.STATE_UNCHECKED\n\t\t\t\t\tmanga.size -> MaterialCheckBox.STATE_CHECKED\n\t\t\t\t\telse -> MaterialCheckBox.STATE_INDETERMINATE\n\t\t\t\t},\n\t\t\t\tisTrackerEnabled = tracker,\n\t\t\t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.select.adapter\n\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass MangaCategoriesAdapter(\n\tclickListener: OnListItemClickListener<MangaCategoryItem>,\n) : BaseListAdapter<ListModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.NAV_ITEM, mangaCategoryAD(clickListener))\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoryAD.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.select.adapter\n\nimport androidx.core.text.buildSpannedString\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.appendIcon\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.databinding.ItemCategoryCheckableBinding\nimport org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun mangaCategoryAD(\n\tclickListener: OnListItemClickListener<MangaCategoryItem>,\n) = adapterDelegateViewBinding<MangaCategoryItem, ListModel, ItemCategoryCheckableBinding>(\n\t{ inflater, parent -> ItemCategoryCheckableBinding.inflate(inflater, parent, false) },\n) {\n\n\titemView.setOnClickListener {\n\t\tclickListener.onItemClick(item, itemView)\n\t}\n\n\tbind { payloads ->\n\t\tbinding.checkBox.checkedState = item.checkedState\n\t\tif (ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED !in payloads) {\n\t\t\tbinding.checkBox.text = buildSpannedString {\n\t\t\t\tappend(item.category.title)\n\t\t\t\tif (item.isTrackerEnabled && item.category.isTrackingEnabled) {\n\t\t\t\t\tappend(' ')\n\t\t\t\t\tappendIcon(binding.checkBox, R.drawable.ic_notification)\n\t\t\t\t}\n\t\t\t\tif (!item.category.isVisibleInLibrary) {\n\t\t\t\t\tappend(' ')\n\t\t\t\t\tappendIcon(binding.checkBox, R.drawable.ic_eye_off)\n\t\t\t\t}\n\t\t\t}\n\t\t\tbinding.checkBox.jumpDrawablesToCurrentState()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/model/MangaCategoryItem.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.categories.select.model\n\nimport com.google.android.material.checkbox.MaterialCheckBox.CheckedState\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class MangaCategoryItem(\n\tval category: FavouriteCategory,\n\t@CheckedState val checkedState: Int,\n\tval isTrackerEnabled: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is MangaCategoryItem && other.category.id == category.id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\treturn if (previousState is MangaCategoryItem && previousState.checkedState != checkedState) {\n\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.container\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class FavouriteTabModel(\n\tval id: Long,\n\tval title: String?,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is FavouriteTabModel && other.id == id\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouriteTabPopupMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.container\n\nimport android.content.Context\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID\n\nclass FavouriteTabPopupMenuProvider(\n\tprivate val context: Context,\n\tprivate val router: AppRouter,\n\tprivate val viewModel: FavouritesContainerViewModel,\n\tprivate val categoryId: Long\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tval menuResId = if (categoryId == NO_ID) {\n\t\t\tR.menu.popup_fav_tab_all\n\t\t} else {\n\t\t\tR.menu.popup_fav_tab\n\t\t}\n\t\tmenuInflater.inflate(menuResId, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\twhen (menuItem.itemId) {\n\t\t\tR.id.action_hide -> viewModel.hide(categoryId)\n\t\t\tR.id.action_edit -> router.openFavoriteCategoryEdit(categoryId)\n\t\t\tR.id.action_delete -> confirmDelete()\n\t\t\tR.id.action_manage -> router.openFavoriteCategories()\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n\n\tprivate fun confirmDelete() {\n\t\tbuildAlertDialog(context, isCentered = true) {\n\t\t\tsetMessage(R.string.categories_delete_confirm)\n\t\t\tsetTitle(R.string.remove_category)\n\t\t\tsetIcon(R.drawable.ic_delete)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.remove) { _, _ -> viewModel.deleteCategory(categoryId) }\n\t\t}.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerAdapter.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.container\n\nimport androidx.fragment.app.Fragment\nimport androidx.recyclerview.widget.AdapterListUpdateCallback\nimport androidx.recyclerview.widget.AsyncDifferConfig\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.asExecutor\nimport kotlinx.coroutines.flow.FlowCollector\nimport org.koitharu.kotatsu.core.util.ContinuationResumeRunnable\nimport org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport kotlin.coroutines.suspendCoroutine\n\nclass FavouritesContainerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment),\n\tFlowCollector<List<FavouriteTabModel>> {\n\n\tprivate val differ = AsyncListDiffer(\n\t\tAdapterListUpdateCallback(this),\n\t\tAsyncDifferConfig.Builder(ListModelDiffCallback<FavouriteTabModel>())\n\t\t\t.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())\n\t\t\t.build(),\n\t)\n\n\toverride fun getItemCount(): Int = differ.currentList.size\n\n\toverride fun getItemId(position: Int): Long {\n\t\treturn differ.currentList.getOrNull(position)?.id ?: RecyclerView.NO_ID\n\t}\n\n\toverride fun containsItem(itemId: Long): Boolean {\n\t\treturn differ.currentList.any { x -> x.id == itemId }\n\t}\n\n\toverride fun createFragment(position: Int): Fragment {\n\t\tval item = differ.currentList[position]\n\t\treturn FavouritesListFragment.newInstance(item.id)\n\t}\n\n\toverride suspend fun emit(value: List<FavouriteTabModel>) = suspendCoroutine { cont ->\n\t\tdiffer.submitList(value, ContinuationResumeRunnable(cont))\n\t}\n\n\tfun getItem(position: Int): FavouriteTabModel = differ.currentList[position]\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.container\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewStub\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.tabs.TabLayoutMediator\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.util.ActionModeListener\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.findCurrentPagerFragment\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.recyclerView\nimport org.koitharu.kotatsu.core.util.ext.setTabsEnabled\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding\nimport org.koitharu.kotatsu.databinding.ItemEmptyStateBinding\n\n@AndroidEntryPoint\nclass FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBinding>(),\n\tActionModeListener,\n\tRecyclerViewOwner,\n\tViewStub.OnInflateListener,\n\tView.OnClickListener {\n\n\tprivate val viewModel: FavouritesContainerViewModel by viewModels()\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = (findCurrentFragment() as? RecyclerViewOwner)?.recyclerView\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentFavouritesContainerBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: FragmentFavouritesContainerBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval pagerAdapter = FavouritesContainerAdapter(this)\n\t\tbinding.pager.adapter = pagerAdapter\n\t\tbinding.pager.offscreenPageLimit = 1\n\t\tbinding.pager.recyclerView?.isNestedScrollingEnabled = false\n\t\tTabLayoutMediator(\n\t\t\tbinding.tabs,\n\t\t\tbinding.pager,\n\t\t\tFavouritesTabConfigurationStrategy(pagerAdapter, viewModel, router),\n\t\t).attach()\n\t\tbinding.stubEmpty.setOnInflateListener(this)\n\t\tactionModeDelegate.addListener(this)\n\t\tviewModel.categories.observe(viewLifecycleOwner, pagerAdapter)\n\t\tviewModel.isEmpty.observe(viewLifecycleOwner, ::onEmptyStateChanged)\n\t\taddMenuProvider(FavouritesContainerMenuProvider(router))\n\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager))\n\t}\n\n\toverride fun onDestroyView() {\n\t\tactionModeDelegate.removeListener(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat = insets\n\n\toverride fun onActionModeStarted(mode: ActionMode) {\n\t\tviewBinding?.run {\n\t\t\tpager.isUserInputEnabled = false\n\t\t\ttabs.setTabsEnabled(false)\n\t\t}\n\t}\n\n\toverride fun onActionModeFinished(mode: ActionMode) {\n\t\tviewBinding?.run {\n\t\t\tpager.isUserInputEnabled = true\n\t\t\ttabs.setTabsEnabled(true)\n\t\t}\n\t}\n\n\toverride fun onInflate(stub: ViewStub?, inflated: View) {\n\t\tval stubBinding = ItemEmptyStateBinding.bind(inflated)\n\t\tstubBinding.icon.setImageAsync(R.drawable.ic_empty_favourites)\n\t\tstubBinding.textPrimary.setText(R.string.text_empty_holder_primary)\n\t\tstubBinding.textSecondary.setTextAndVisible(R.string.empty_favourite_categories)\n\t\tstubBinding.buttonRetry.setTextAndVisible(R.string.manage)\n\t\tstubBinding.buttonRetry.setOnClickListener(this)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_retry -> router.openFavoriteCategories()\n\t\t}\n\t}\n\n\tprivate fun onEmptyStateChanged(isEmpty: Boolean) {\n\t\tviewBinding?.run {\n\t\t\tpager.isGone = isEmpty\n\t\t\ttabs.isGone = isEmpty\n\t\t\tstubEmpty.isVisible = isEmpty\n\t\t}\n\t}\n\n\tprivate fun findCurrentFragment(): Fragment? {\n\t\treturn childFragmentManager.findCurrentPagerFragment(\n\t\t\tviewBinding?.pager ?: return null,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.container\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\n\nclass FavouritesContainerMenuProvider(\n\tprivate val router: AppRouter,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_favourites_container, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\twhen (menuItem.itemId) {\n\t\t\tR.id.action_manage -> {\n\t\t\t\trouter.openFavoriteCategories()\n\t\t\t}\n\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.container\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.ui.util.ReversibleHandle\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID\nimport javax.inject.Inject\n\n@HiltViewModel\nclass FavouritesContainerViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val favouritesRepository: FavouritesRepository,\n) : BaseViewModel() {\n\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\n\tprivate val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary()\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)\n\n\tval categories = combine(\n\t\tcategoriesStateFlow.filterNotNull(),\n\t\tobserveAllFavouritesVisibility(),\n\t) { list, showAll ->\n\t\tlist.toUi(showAll)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tval isEmpty = categoriesStateFlow.map {\n\t\tit?.isEmpty() == true\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)\n\n\tprivate fun List<FavouriteCategory>.toUi(showAll: Boolean): List<FavouriteTabModel> {\n\t\tif (isEmpty()) {\n\t\t\treturn emptyList()\n\t\t}\n\t\tval result = ArrayList<FavouriteTabModel>(if (showAll) size + 1 else size)\n\t\tif (showAll) {\n\t\t\tresult.add(FavouriteTabModel(NO_ID, null))\n\t\t}\n\t\tmapTo(result) { FavouriteTabModel(it.id, it.title) }\n\t\treturn result\n\t}\n\n\tfun hide(categoryId: Long) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tif (categoryId == NO_ID) {\n\t\t\t\tsettings.isAllFavouritesVisible = false\n\t\t\t} else {\n\t\t\t\tfavouritesRepository.updateCategory(categoryId, isVisibleInLibrary = false)\n\t\t\t\tval reverse = ReversibleHandle {\n\t\t\t\t\tfavouritesRepository.updateCategory(categoryId, isVisibleInLibrary = true)\n\t\t\t\t}\n\t\t\t\tonActionDone.call(ReversibleAction(R.string.category_hidden_done, reverse))\n\t\t\t}\n\t\t}\n\t}\n\n\tfun deleteCategory(categoryId: Long) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tfavouritesRepository.removeCategories(setOf(categoryId))\n\t\t}\n\t}\n\n\tprivate fun observeAllFavouritesVisibility() = settings.observeAsFlow(\n\t\tkey = AppSettings.KEY_ALL_FAVOURITES_VISIBLE,\n\t\tvalueProducer = { isAllFavouritesVisible },\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesTabConfigurationStrategy.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.container\n\nimport com.google.android.material.tabs.TabLayout\nimport com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.util.PopupMenuMediator\n\nclass FavouritesTabConfigurationStrategy(\n\tprivate val adapter: FavouritesContainerAdapter,\n\tprivate val viewModel: FavouritesContainerViewModel,\n\tprivate val router: AppRouter,\n) : TabConfigurationStrategy {\n\n\toverride fun onConfigureTab(tab: TabLayout.Tab, position: Int) {\n\t\tval item = adapter.getItem(position)\n\t\ttab.text = item.title ?: tab.view.context.getString(R.string.all_favourites)\n\t\ttab.tag = item\n\t\tPopupMenuMediator(\n\t\t\tFavouriteTabPopupMenuProvider(tab.view.context, router, viewModel, item.id)\n\t\t).attach(tab.view)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.list\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.appcompat.view.ActionMode\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.util.ext.sortedByOrdinal\nimport org.koitharu.kotatsu.core.util.ext.withArgs\nimport org.koitharu.kotatsu.databinding.FragmentListBinding\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\n\n@AndroidEntryPoint\nclass FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener {\n\n\toverride val viewModel by viewModels<FavouritesListViewModel>()\n\n\toverride val isSwipeRefreshEnabled = false\n\n\tval categoryId\n\t\tget() = viewModel.categoryId\n\n\toverride fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.recyclerView.isVP2BugWorkaroundEnabled = true\n\t}\n\n\toverride fun onScrolledToEnd() = viewModel.requestMoreItems()\n\n\toverride fun onEmptyActionClick() = viewModel.clearFilter()\n\n\toverride fun onFilterClick(view: View?) {\n\t\tval menu = PopupMenu(view?.context ?: return, view)\n\t\tmenu.setOnMenuItemClickListener(this)\n\t\tval orders = ListSortOrder.FAVORITES.sortedByOrdinal()\n\t\tfor ((i, item) in orders.withIndex()) {\n\t\t\tmenu.menu.add(Menu.NONE, Menu.NONE, i, item.titleResId)\n\t\t}\n\t\tmenu.show()\n\t}\n\n\toverride fun onMenuItemClick(item: MenuItem): Boolean {\n\t\tval order = ListSortOrder.FAVORITES.sortedByOrdinal().getOrNull(item.order) ?: return false\n\t\tviewModel.setSortOrder(order)\n\t\treturn true\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_favourites, menu)\n\t\treturn super.onCreateActionMode(controller, menuInflater, menu)\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_remove -> {\n\t\t\t\tviewModel.removeFromFavourites(selectedItemsIds)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_mark_current -> {\n\t\t\t\tval itemsSnapshot = selectedItems\n\t\t\t\tMaterialAlertDialogBuilder(context ?: return false)\n\t\t\t\t\t.setTitle(item.title)\n\t\t\t\t\t.setMessage(R.string.mark_as_completed_prompt)\n\t\t\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t\t\t\t\t.setPositiveButton(android.R.string.ok) { _, _ ->\n\t\t\t\t\t\tviewModel.markAsRead(itemsSnapshot)\n\t\t\t\t\t\tmode?.finish()\n\t\t\t\t\t}.show()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onActionItemClicked(controller, mode, item)\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tconst val NO_ID = 0L\n\n\t\tfun newInstance(categoryId: Long) = FavouritesListFragment().withArgs(1) {\n\t\t\tputLong(AppRouter.KEY_ID, categoryId)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt",
    "content": "package org.koitharu.kotatsu.favourites.ui.list\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.flattenLatest\nimport org.koitharu.kotatsu.favourites.domain.FavoritesListQuickFilter\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID\nimport org.koitharu.kotatsu.history.domain.MarkAsReadUseCase\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.domain.QuickFilterListener\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.util.concurrent.atomic.AtomicBoolean\nimport javax.inject.Inject\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport kotlinx.coroutines.flow.SharedFlow\n\nprivate const val PAGE_SIZE = 16\n\n@HiltViewModel\nclass FavouritesListViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val repository: FavouritesRepository,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val markAsReadUseCase: MarkAsReadUseCase,\n\tquickFilterFactory: FavoritesListQuickFilter.Factory,\n\tsettings: AppSettings,\n\tmangaDataRepository: MangaDataRepository,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener {\n\n\tval categoryId: Long = savedStateHandle[AppRouter.KEY_ID] ?: NO_ID\n\tprivate val quickFilter = quickFilterFactory.create(categoryId)\n\tprivate val refreshTrigger = MutableStateFlow(Any())\n\tprivate val limit = MutableStateFlow(PAGE_SIZE)\n\tprivate val isPaginationReady = AtomicBoolean(false)\n\n\toverride val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode }\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode)\n\n\tval sortOrder: StateFlow<ListSortOrder?> = if (categoryId == NO_ID) {\n\t\tsettings.observeAsFlow(AppSettings.KEY_FAVORITES_ORDER) {\n\t\t\tallFavoritesSortOrder\n\t\t}\n\t} else {\n\t\trepository.observeCategory(categoryId)\n\t\t\t.withErrorHandling()\n\t\t\t.map { it?.order }\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)\n\n\toverride val content = combine(\n\t\tobserveFavorites(),\n\t\tquickFilter.appliedOptions,\n\t\tobserveListModeWithTriggers(),\n\t\trefreshTrigger,\n\t) { list, filters, mode, _ ->\n\t\tlist.mapList(mode, filters)\n\t}.distinctUntilChanged().onEach {\n\t\tisPaginationReady.set(true)\n\t}.catch {\n\t\temit(listOf(it.toErrorState(canRetry = false)))\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\toverride fun onRefresh() {\n\t\trefreshTrigger.value = Any()\n\t}\n\n\toverride fun onRetry() = Unit\n\n\toverride fun setFilterOption(option: ListFilterOption, isApplied: Boolean) =\n\t\tquickFilter.setFilterOption(option, isApplied)\n\n\toverride fun toggleFilterOption(option: ListFilterOption) = quickFilter.toggleFilterOption(option)\n\n\toverride fun clearFilter() = quickFilter.clearFilter()\n\n\tfun markAsRead(items: Set<Manga>) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tmarkAsReadUseCase(items)\n\t\t\tonRefresh()\n\t\t}\n\t}\n\n\tfun removeFromFavourites(ids: Set<Long>) {\n\t\tif (ids.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval handle = if (categoryId == NO_ID) {\n\t\t\t\trepository.removeFromFavourites(ids)\n\t\t\t} else {\n\t\t\t\trepository.removeFromCategory(categoryId, ids)\n\t\t\t}\n\t\t\tonActionDone.call(ReversibleAction(R.string.removed_from_favourites, handle))\n\t\t}\n\t}\n\n\tfun setSortOrder(order: ListSortOrder) {\n\t\tif (categoryId == NO_ID) {\n\t\t\treturn\n\t\t}\n\t\tlaunchJob {\n\t\t\trepository.setCategoryOrder(categoryId, order)\n\t\t}\n\t}\n\n\tfun requestMoreItems() {\n\t\tif (isPaginationReady.compareAndSet(true, false)) {\n\t\t\tlimit.value += PAGE_SIZE\n\t\t}\n\t}\n\n\tprivate suspend fun List<Manga>.mapList(mode: ListMode, filters: Set<ListFilterOption>): List<ListModel> {\n\t\tif (isEmpty()) {\n\t\t\treturn if (filters.isEmpty()) {\n\t\t\t\tlistOf(getEmptyState(hasFilters = false))\n\t\t\t} else {\n\t\t\t\tlistOfNotNull(quickFilter.filterItem(filters), getEmptyState(hasFilters = true))\n\t\t\t}\n\t\t}\n\t\tval result = ArrayList<ListModel>(size + 1)\n\t\tquickFilter.filterItem(filters)?.let(result::add)\n\t\tmangaListMapper.toListModelList(result, this, mode, MangaListMapper.NO_FAVORITE)\n\t\treturn result\n\t}\n\n\tprivate fun observeFavorites() = if (categoryId == NO_ID) {\n\t\tcombine(\n\t\t\tsortOrder.filterNotNull(),\n\t\t\tquickFilter.appliedOptions.combineWithSettings(),\n\t\t\tlimit,\n\t\t) { order, filters, limit ->\n\t\t\tisPaginationReady.set(false)\n\t\t\trepository.observeAll(order, filters, limit)\n\t\t}.flattenLatest()\n\t} else {\n\t\tcombine(quickFilter.appliedOptions.combineWithSettings(), limit) { filters, limit ->\n\t\t\trepository.observeAll(categoryId, filters, limit)\n\t\t}.flattenLatest()\n\t}\n\n\tprivate fun getEmptyState(hasFilters: Boolean) = if (hasFilters) {\n\t\tEmptyState(\n\t\t\ticon = R.drawable.ic_empty_favourites,\n\t\t\ttextPrimary = R.string.nothing_found,\n\t\t\ttextSecondary = R.string.text_empty_holder_secondary_filtered,\n\t\t\tactionStringRes = R.string.reset_filter,\n\t\t)\n\t} else {\n\t\tEmptyState(\n\t\t\ticon = R.drawable.ic_empty_favourites,\n\t\t\ttextPrimary = R.string.text_empty_holder_primary,\n\t\t\ttextSecondary = if (categoryId == NO_ID) {\n\t\t\t\tR.string.you_have_not_favourites_yet\n\t\t\t} else {\n\t\t\t\tR.string.favourites_category_empty\n\t\t\t},\n\t\t\tactionStringRes = 0,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/data/MangaListFilterSerializer.kt",
    "content": "package org.koitharu.kotatsu.filter.data\n\nimport kotlinx.serialization.KSerializer\nimport kotlinx.serialization.builtins.SetSerializer\nimport kotlinx.serialization.builtins.serializer\nimport kotlinx.serialization.descriptors.SerialDescriptor\nimport kotlinx.serialization.descriptors.buildClassSerialDescriptor\nimport kotlinx.serialization.descriptors.element\nimport kotlinx.serialization.encoding.CompositeDecoder\nimport kotlinx.serialization.encoding.Decoder\nimport kotlinx.serialization.encoding.Encoder\nimport kotlinx.serialization.encoding.decodeStructure\nimport kotlinx.serialization.encoding.encodeStructure\nimport kotlinx.serialization.serializer\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.util.ext.toLocaleOrNull\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.Demographic\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport java.util.Locale\n\nobject MangaListFilterSerializer : KSerializer<MangaListFilter> {\n\n    override val descriptor: SerialDescriptor =\n        buildClassSerialDescriptor(MangaListFilter::class.java.name) {\n            element<String?>(\"query\", isOptional = true)\n            element(\n                elementName = \"tags\",\n                descriptor = SetSerializer(MangaTagSerializer).descriptor,\n                isOptional = true,\n            )\n            element(\n                elementName = \"tagsExclude\",\n                descriptor = SetSerializer(MangaTagSerializer).descriptor,\n                isOptional = true,\n            )\n            element<String?>(\"locale\", isOptional = true)\n            element<String?>(\"originalLocale\", isOptional = true)\n            element<Set<MangaState>>(\"states\", isOptional = true)\n            element<Set<ContentRating>>(\"contentRating\", isOptional = true)\n            element<Set<ContentType>>(\"types\", isOptional = true)\n            element<Set<Demographic>>(\"demographics\", isOptional = true)\n            element<Int>(\"year\", isOptional = true)\n            element<Int>(\"yearFrom\", isOptional = true)\n            element<Int>(\"yearTo\", isOptional = true)\n            element<String?>(\"author\", isOptional = true)\n        }\n\n    override fun serialize(\n        encoder: Encoder,\n        value: MangaListFilter\n    ) = encoder.encodeStructure(descriptor) {\n        encodeNullableSerializableElement(descriptor, 0, String.serializer(), value.query)\n        encodeSerializableElement(descriptor, 1, SetSerializer(MangaTagSerializer), value.tags)\n        encodeSerializableElement(descriptor, 2, SetSerializer(MangaTagSerializer), value.tagsExclude)\n        encodeNullableSerializableElement(descriptor, 3, String.serializer(), value.locale?.toLanguageTag())\n        encodeNullableSerializableElement(descriptor, 4, String.serializer(), value.originalLocale?.toLanguageTag())\n        encodeSerializableElement(descriptor, 5, SetSerializer(serializer()), value.states)\n        encodeSerializableElement(descriptor, 6, SetSerializer(serializer()), value.contentRating)\n        encodeSerializableElement(descriptor, 7, SetSerializer(serializer()), value.types)\n        encodeSerializableElement(descriptor, 8, SetSerializer(serializer()), value.demographics)\n        encodeIntElement(descriptor, 9, value.year)\n        encodeIntElement(descriptor, 10, value.yearFrom)\n        encodeIntElement(descriptor, 11, value.yearTo)\n        encodeNullableSerializableElement(descriptor, 12, String.serializer(), value.author)\n    }\n\n    override fun deserialize(\n        decoder: Decoder\n    ): MangaListFilter = decoder.decodeStructure(descriptor) {\n        var query: String? = MangaListFilter.EMPTY.query\n        var tags: Set<MangaTag> = MangaListFilter.EMPTY.tags\n        var tagsExclude: Set<MangaTag> = MangaListFilter.EMPTY.tagsExclude\n        var locale: Locale? = MangaListFilter.EMPTY.locale\n        var originalLocale: Locale? = MangaListFilter.EMPTY.originalLocale\n        var states: Set<MangaState> = MangaListFilter.EMPTY.states\n        var contentRating: Set<ContentRating> = MangaListFilter.EMPTY.contentRating\n        var types: Set<ContentType> = MangaListFilter.EMPTY.types\n        var demographics: Set<Demographic> = MangaListFilter.EMPTY.demographics\n        var year: Int = MangaListFilter.EMPTY.year\n        var yearFrom: Int = MangaListFilter.EMPTY.yearFrom\n        var yearTo: Int = MangaListFilter.EMPTY.yearTo\n        var author: String? = MangaListFilter.EMPTY.author\n\n        while (true) {\n            when (decodeElementIndex(descriptor)) {\n                0 -> query = decodeNullableSerializableElement(descriptor, 0, serializer<String>())\n                1 -> tags = decodeSerializableElement(descriptor, 1, SetSerializer(MangaTagSerializer))\n                2 -> tagsExclude = decodeSerializableElement(descriptor, 2, SetSerializer(MangaTagSerializer))\n                3 -> locale = decodeNullableSerializableElement(descriptor, 3, serializer<String>())?.toLocaleOrNull()\n                4 -> originalLocale =\n                    decodeNullableSerializableElement(descriptor, 4, serializer<String>())?.toLocaleOrNull()\n\n                5 -> states = decodeSerializableElement(descriptor, 5, SetSerializer(serializer()))\n                6 -> contentRating = decodeSerializableElement(descriptor, 6, SetSerializer(serializer()))\n                7 -> types = decodeSerializableElement(descriptor, 7, SetSerializer(serializer()))\n                8 -> demographics = decodeSerializableElement(descriptor, 8, SetSerializer(serializer()))\n                9 -> year = decodeIntElement(descriptor, 9)\n                10 -> yearFrom = decodeIntElement(descriptor, 10)\n                11 -> yearTo = decodeIntElement(descriptor, 11)\n                12 -> author = decodeNullableSerializableElement(descriptor, 12, serializer<String>())\n                CompositeDecoder.DECODE_DONE -> break\n            }\n        }\n\n        MangaListFilter(\n            query = query,\n            tags = tags,\n            tagsExclude = tagsExclude,\n            locale = locale,\n            originalLocale = originalLocale,\n            states = states,\n            contentRating = contentRating,\n            types = types,\n            demographics = demographics,\n            year = year,\n            yearFrom = yearFrom,\n            yearTo = yearTo,\n            author = author,\n        )\n    }\n\n    private object MangaTagSerializer : KSerializer<MangaTag> {\n\n        override val descriptor: SerialDescriptor = buildClassSerialDescriptor(MangaTag::class.java.name) {\n            element<String>(\"title\")\n            element<String>(\"key\")\n            element<String>(\"source\")\n        }\n\n        override fun serialize(encoder: Encoder, value: MangaTag) = encoder.encodeStructure(descriptor) {\n            encodeStringElement(descriptor, 0, value.title)\n            encodeStringElement(descriptor, 1, value.key)\n            encodeStringElement(descriptor, 2, value.source.name)\n        }\n\n        override fun deserialize(decoder: Decoder): MangaTag = decoder.decodeStructure(descriptor) {\n            var title: String? = null\n            var key: String? = null\n            var source: String? = null\n\n            while (true) {\n                when (decodeElementIndex(descriptor)) {\n                    0 -> title = decodeStringElement(descriptor, 0)\n                    1 -> key = decodeStringElement(descriptor, 1)\n                    2 -> source = decodeStringElement(descriptor, 2)\n                    CompositeDecoder.DECODE_DONE -> break\n                }\n            }\n\n            MangaTag(\n                title = title ?: error(\"Missing 'title' field\"),\n                key = key ?: error(\"Missing 'key' field\"),\n                source = MangaSource(source),\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/data/PersistableFilter.kt",
    "content": "package org.koitharu.kotatsu.filter.data\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonIgnoreUnknownKeys\nimport org.koitharu.kotatsu.core.model.MangaSourceSerializer\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\n@Serializable\n@JsonIgnoreUnknownKeys\ndata class PersistableFilter(\n    @SerialName(\"name\")\n    val name: String,\n    @Serializable(with = MangaSourceSerializer::class)\n    @SerialName(\"source\")\n    val source: MangaSource,\n    @Serializable(with = MangaListFilterSerializer::class)\n    @SerialName(\"filter\")\n    val filter: MangaListFilter,\n) {\n\n    val id: Int\n        get() = name.hashCode()\n\n    companion object {\n\n        const val MAX_TITLE_LENGTH = 18\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/data/SavedFiltersRepository.kt",
    "content": "package org.koitharu.kotatsu.filter.data\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport dagger.Reusable\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.withContext\nimport kotlinx.serialization.SerializationException\nimport kotlinx.serialization.json.Json\nimport org.koitharu.kotatsu.core.util.ext.observeChanges\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport java.io.File\nimport javax.inject.Inject\n\n@Reusable\nclass SavedFiltersRepository @Inject constructor(\n    @ApplicationContext private val context: Context,\n) {\n\n    fun observeAll(source: MangaSource): Flow<List<PersistableFilter>> = getPrefs(source).observeChanges()\n        .onStart { emit(null) }\n        .map {\n            getAll(source)\n        }.distinctUntilChanged()\n        .flowOn(Dispatchers.Default)\n\n    suspend fun getAll(source: MangaSource): List<PersistableFilter> = withContext(Dispatchers.Default) {\n        val prefs = getPrefs(source)\n        val keys = prefs.all.keys.filter { it.startsWith(FILTER_PREFIX) }\n        keys.mapNotNull { key ->\n            val value = prefs.getString(key, null) ?: return@mapNotNull null\n            try {\n                Json.decodeFromString(value)\n            } catch (e: SerializationException) {\n                e.printStackTraceDebug()\n                null\n            }\n        }\n    }\n\n    suspend fun save(\n        source: MangaSource,\n        name: String,\n        filter: MangaListFilter,\n    ): PersistableFilter = withContext(Dispatchers.Default) {\n        val persistableFilter = PersistableFilter(\n            name = name,\n            source = source,\n            filter = filter,\n        )\n        persist(persistableFilter)\n        persistableFilter\n    }\n\n    suspend fun save(\n        filter: PersistableFilter,\n    ) = withContext(Dispatchers.Default) {\n        persist(filter)\n    }\n\n    suspend fun rename(source: MangaSource, id: Int, newName: String) = withContext(Dispatchers.Default) {\n        val filter = load(source, id) ?: return@withContext\n        val newFilter = filter.copy(name = newName)\n        val prefs = getPrefs(source)\n        prefs.edit(commit = true) {\n            remove(key(id))\n            putString(key(newFilter.id), Json.encodeToString(newFilter))\n        }\n        newFilter\n    }\n\n    suspend fun delete(source: MangaSource, id: Int) = withContext(Dispatchers.Default) {\n        val prefs = getPrefs(source)\n        prefs.edit(commit = true) {\n            remove(key(id))\n        }\n    }\n\n    private fun persist(persistableFilter: PersistableFilter) {\n        val prefs = getPrefs(persistableFilter.source)\n        val json = Json.encodeToString(persistableFilter)\n        prefs.edit(commit = true) {\n            putString(key(persistableFilter.id), json)\n        }\n    }\n\n    private fun load(source: MangaSource, id: Int): PersistableFilter? {\n        val prefs = getPrefs(source)\n        val json = prefs.getString(key(id), null) ?: return null\n        return try {\n            Json.decodeFromString<PersistableFilter>(json)\n        } catch (e: SerializationException) {\n            e.printStackTraceDebug()\n            null\n        }\n    }\n\n    private fun getPrefs(source: MangaSource): SharedPreferences {\n        val key = source.name.replace(File.separatorChar, '$')\n        return context.getSharedPreferences(key, Context.MODE_PRIVATE)\n    }\n\n    private companion object {\n\n        const val FILTER_PREFIX = \"__pf_\"\n\n        fun key(id: Int) = FILTER_PREFIX + id\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt",
    "content": "package org.koitharu.kotatsu.filter.ui\n\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.ViewModelLifecycle\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.distinctUntilChangedBy\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.LocaleComparator\nimport org.koitharu.kotatsu.core.util.ext.asFlow\nimport org.koitharu.kotatsu.core.util.ext.lifecycleScope\nimport org.koitharu.kotatsu.core.util.ext.sortedByOrdinal\nimport org.koitharu.kotatsu.core.util.ext.sortedWithSafe\nimport org.koitharu.kotatsu.filter.data.PersistableFilter\nimport org.koitharu.kotatsu.filter.data.SavedFiltersRepository\nimport org.koitharu.kotatsu.filter.ui.model.FilterProperty\nimport org.koitharu.kotatsu.filter.ui.tags.TagTitleComparator\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.Demographic\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.model.YEAR_MIN\nimport org.koitharu.kotatsu.parsers.util.ifZero\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport org.koitharu.kotatsu.remotelist.ui.RemoteListFragment\nimport org.koitharu.kotatsu.search.domain.MangaSearchRepository\nimport java.util.Calendar\nimport java.util.Locale\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass FilterCoordinator @Inject constructor(\n    savedStateHandle: SavedStateHandle,\n    mangaRepositoryFactory: MangaRepository.Factory,\n    private val searchRepository: MangaSearchRepository,\n    private val savedFiltersRepository: SavedFiltersRepository,\n    lifecycle: ViewModelLifecycle,\n) {\n\n    private val coroutineScope = lifecycle.lifecycleScope + Dispatchers.Default\n    private val repository = mangaRepositoryFactory.create(MangaSource(savedStateHandle[RemoteListFragment.ARG_SOURCE]))\n    private val sourceLocale = (repository.source as? MangaParserSource)?.locale\n\n    private val currentListFilter = MutableStateFlow(MangaListFilter.EMPTY)\n    private val currentSortOrder = MutableStateFlow(repository.defaultSortOrder)\n\n    private val availableSortOrders = repository.sortOrders\n    private val filterOptions = suspendLazy { repository.getFilterOptions() }\n\n    val capabilities = repository.filterCapabilities\n\n    val mangaSource: MangaSource\n        get() = repository.source\n\n    val isFilterApplied: Boolean\n        get() = currentListFilter.value.isNotEmpty()\n\n    val query: StateFlow<String?> = currentListFilter.map { it.query }\n        .stateIn(coroutineScope, SharingStarted.Eagerly, null)\n\n    val sortOrder: StateFlow<FilterProperty<SortOrder>> = currentSortOrder.map { selected ->\n        FilterProperty(\n            availableItems = availableSortOrders.sortedByOrdinal(),\n            selectedItem = selected,\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n\n    val tags: StateFlow<FilterProperty<MangaTag>> = combine(\n        getTopTags(TAGS_LIMIT),\n        currentListFilter.distinctUntilChangedBy { it.tags },\n    ) { available, selected ->\n        available.fold(\n            onSuccess = {\n                FilterProperty(\n                    availableItems = it.addFirstDistinct(selected.tags),\n                    selectedItems = selected.tags,\n                )\n            },\n            onFailure = {\n                FilterProperty.error(it)\n            },\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n\n    val tagsExcluded: StateFlow<FilterProperty<MangaTag>> = if (capabilities.isTagsExclusionSupported) {\n        combine(\n            getBottomTags(TAGS_LIMIT),\n            currentListFilter.distinctUntilChangedBy { it.tagsExclude },\n        ) { available, selected ->\n            available.fold(\n                onSuccess = {\n                    FilterProperty(\n                        availableItems = it.addFirstDistinct(selected.tagsExclude),\n                        selectedItems = selected.tagsExclude,\n                    )\n                },\n                onFailure = {\n                    FilterProperty.error(it)\n                },\n            )\n        }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n    } else {\n        MutableStateFlow(FilterProperty.EMPTY)\n    }\n\n    val authors: StateFlow<FilterProperty<String>> = if (capabilities.isAuthorSearchSupported) {\n        combine(\n            flow { emit(searchRepository.getAuthors(repository.source, TAGS_LIMIT)) },\n            currentListFilter.distinctUntilChangedBy { it.author },\n        ) { available, selected ->\n            FilterProperty(\n                availableItems = available,\n                selectedItems = setOfNotNull(selected.author),\n            )\n        }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n    } else {\n        MutableStateFlow(FilterProperty.EMPTY)\n    }\n\n    val states: StateFlow<FilterProperty<MangaState>> = combine(\n        filterOptions.asFlow(),\n        currentListFilter.distinctUntilChangedBy { it.states },\n    ) { available, selected ->\n        available.fold(\n            onSuccess = {\n                FilterProperty(\n                    availableItems = it.availableStates.sortedByOrdinal(),\n                    selectedItems = selected.states,\n                )\n            },\n            onFailure = {\n                FilterProperty.error(it)\n            },\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n\n    val contentRating: StateFlow<FilterProperty<ContentRating>> = combine(\n        filterOptions.asFlow(),\n        currentListFilter.distinctUntilChangedBy { it.contentRating },\n    ) { available, selected ->\n        available.fold(\n            onSuccess = {\n                FilterProperty(\n                    availableItems = it.availableContentRating.sortedByOrdinal(),\n                    selectedItems = selected.contentRating,\n                )\n            },\n            onFailure = {\n                FilterProperty.error(it)\n            },\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n\n    val contentTypes: StateFlow<FilterProperty<ContentType>> = combine(\n        filterOptions.asFlow(),\n        currentListFilter.distinctUntilChangedBy { it.types },\n    ) { available, selected ->\n        available.fold(\n            onSuccess = {\n                FilterProperty(\n                    availableItems = it.availableContentTypes.sortedByOrdinal(),\n                    selectedItems = selected.types,\n                )\n            },\n            onFailure = {\n                FilterProperty.error(it)\n            },\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n\n    val demographics: StateFlow<FilterProperty<Demographic>> = combine(\n        filterOptions.asFlow(),\n        currentListFilter.distinctUntilChangedBy { it.demographics },\n    ) { available, selected ->\n        available.fold(\n            onSuccess = {\n                FilterProperty(\n                    availableItems = it.availableDemographics.sortedByOrdinal(),\n                    selectedItems = selected.demographics,\n                )\n            },\n            onFailure = {\n                FilterProperty.error(it)\n            },\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n\n    val locale: StateFlow<FilterProperty<Locale?>> = combine(\n        filterOptions.asFlow(),\n        currentListFilter.distinctUntilChangedBy { it.locale },\n    ) { available, selected ->\n        available.fold(\n            onSuccess = {\n                FilterProperty(\n                    availableItems = it.availableLocales.sortedWithSafe(LocaleComparator()).addFirstDistinct(null),\n                    selectedItems = setOfNotNull(selected.locale),\n                )\n            },\n            onFailure = {\n                FilterProperty.error(it)\n            },\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n\n    val originalLocale: StateFlow<FilterProperty<Locale?>> = if (capabilities.isOriginalLocaleSupported) {\n        combine(\n            filterOptions.asFlow(),\n            currentListFilter.distinctUntilChangedBy { it.originalLocale },\n        ) { available, selected ->\n            available.fold(\n                onSuccess = {\n                    FilterProperty(\n                        availableItems = it.availableLocales.sortedWithSafe(LocaleComparator()).addFirstDistinct(null),\n                        selectedItems = setOfNotNull(selected.originalLocale),\n                    )\n                },\n                onFailure = {\n                    FilterProperty.error(it)\n                },\n            )\n        }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n    } else {\n        MutableStateFlow(FilterProperty.EMPTY)\n    }\n\n    val year: StateFlow<FilterProperty<Int>> = if (capabilities.isYearSupported) {\n        currentListFilter.distinctUntilChangedBy { it.year }.map { selected ->\n            FilterProperty(\n                availableItems = listOf(YEAR_MIN, MAX_YEAR),\n                selectedItems = setOf(selected.year),\n            )\n        }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n    } else {\n        MutableStateFlow(FilterProperty.EMPTY)\n    }\n\n    val yearRange: StateFlow<FilterProperty<Int>> = if (capabilities.isYearRangeSupported) {\n        currentListFilter.distinctUntilChanged { old, new ->\n            old.yearTo == new.yearTo && old.yearFrom == new.yearFrom\n        }.map { selected ->\n            FilterProperty(\n                availableItems = listOf(YEAR_MIN, MAX_YEAR),\n                selectedItems = setOf(selected.yearFrom.ifZero { YEAR_MIN }, selected.yearTo.ifZero { MAX_YEAR }),\n            )\n        }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.LOADING)\n    } else {\n        MutableStateFlow(FilterProperty.EMPTY)\n    }\n\n    val savedFilters: StateFlow<FilterProperty<PersistableFilter>> = combine(\n        savedFiltersRepository.observeAll(repository.source),\n        currentListFilter,\n    ) { available, applied ->\n        FilterProperty(\n            availableItems = available,\n            selectedItems = setOfNotNull(available.find { it.filter == applied }),\n        )\n    }.stateIn(coroutineScope, SharingStarted.Lazily, FilterProperty.EMPTY)\n\n    fun reset() {\n        currentListFilter.value = MangaListFilter.EMPTY\n    }\n\n    fun snapshot() = Snapshot(\n        sortOrder = currentSortOrder.value,\n        listFilter = currentListFilter.value,\n    )\n\n    fun observe(): Flow<Snapshot> = combine(currentSortOrder, currentListFilter, ::Snapshot)\n\n    fun setSortOrder(newSortOrder: SortOrder) {\n        currentSortOrder.value = newSortOrder\n        repository.defaultSortOrder = newSortOrder\n    }\n\n    fun set(value: MangaListFilter) {\n        currentListFilter.value = value\n    }\n\n    fun setAdjusted(value: MangaListFilter) {\n        var newFilter = value\n        if (!newFilter.author.isNullOrEmpty() && !capabilities.isAuthorSearchSupported) {\n            newFilter = newFilter.copy(\n                query = newFilter.author,\n                author = null,\n            )\n        }\n        if (!newFilter.query.isNullOrEmpty() && !newFilter.hasNonSearchOptions() && !capabilities.isSearchWithFiltersSupported) {\n            newFilter = MangaListFilter(query = newFilter.query)\n        }\n        set(newFilter)\n    }\n\n    fun saveCurrentFilter(name: String) = coroutineScope.launch {\n        savedFiltersRepository.save(repository.source, name, currentListFilter.value)\n    }\n\n    fun renameSavedFilter(id: Int, newName: String) = coroutineScope.launch {\n        savedFiltersRepository.rename(repository.source, id, newName)\n    }\n\n    fun deleteSavedFilter(id: Int) = coroutineScope.launch {\n        savedFiltersRepository.delete(repository.source, id)\n    }\n\n    fun setQuery(value: String?) {\n        val newQuery = value?.trim()?.nullIfEmpty()\n        currentListFilter.update { oldValue ->\n            if (capabilities.isSearchWithFiltersSupported || newQuery == null) {\n                oldValue.copy(query = newQuery)\n            } else {\n                MangaListFilter(query = newQuery)\n            }\n        }\n    }\n\n    fun setLocale(value: Locale?) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                locale = value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun setAuthor(value: String?) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                author = value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun setOriginalLocale(value: Locale?) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                originalLocale = value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun setYear(value: Int) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                year = value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun setYearRange(valueFrom: Int, valueTo: Int) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                yearFrom = valueFrom,\n                yearTo = valueTo,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun toggleState(value: MangaState, isSelected: Boolean) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                states = if (isSelected) oldValue.states + value else oldValue.states - value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun toggleContentRating(value: ContentRating, isSelected: Boolean) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                contentRating = if (isSelected) oldValue.contentRating + value else oldValue.contentRating - value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun toggleDemographic(value: Demographic, isSelected: Boolean) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                demographics = if (isSelected) oldValue.demographics + value else oldValue.demographics - value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun toggleContentType(value: ContentType, isSelected: Boolean) {\n        currentListFilter.update { oldValue ->\n            oldValue.copy(\n                types = if (isSelected) oldValue.types + value else oldValue.types - value,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun toggleTag(value: MangaTag, isSelected: Boolean) {\n        currentListFilter.update { oldValue ->\n            val newTags = if (capabilities.isMultipleTagsSupported) {\n                if (isSelected) oldValue.tags + value else oldValue.tags - value\n            } else {\n                if (isSelected) setOf(value) else emptySet()\n            }\n            oldValue.copy(\n                tags = newTags,\n                tagsExclude = oldValue.tagsExclude - newTags,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun toggleTagExclude(value: MangaTag, isSelected: Boolean) {\n        currentListFilter.update { oldValue ->\n            val newTagsExclude = if (capabilities.isMultipleTagsSupported) {\n                if (isSelected) oldValue.tagsExclude + value else oldValue.tagsExclude - value\n            } else {\n                if (isSelected) setOf(value) else emptySet()\n            }\n            oldValue.copy(\n                tags = oldValue.tags - newTagsExclude,\n                tagsExclude = newTagsExclude,\n                query = oldValue.takeQueryIfSupported(),\n            )\n        }\n    }\n\n    fun getAllTags(): Flow<Result<List<MangaTag>>> = filterOptions.asFlow().map {\n        it.map { x -> x.availableTags.sortedWithSafe(TagTitleComparator(sourceLocale)) }\n    }\n\n    private fun MangaListFilter.takeQueryIfSupported() = when {\n        capabilities.isSearchWithFiltersSupported -> query\n        query.isNullOrEmpty() -> query\n        hasNonSearchOptions() -> null\n        else -> query\n    }\n\n    private fun getTopTags(limit: Int): Flow<Result<List<MangaTag>>> = combine(\n        flow { emit(searchRepository.getTopTags(repository.source, limit)) },\n        filterOptions.asFlow(),\n    ) { suggested, options ->\n        val all = options.getOrNull()?.availableTags.orEmpty()\n        val result = ArrayList<MangaTag>(limit)\n        result.addAll(suggested.take(limit))\n        if (result.size < limit) {\n            result.addAll(all.shuffled().take(limit - result.size))\n        }\n        if (result.isNotEmpty()) {\n            Result.success(result)\n        } else {\n            options.map { result }\n        }\n    }.catch {\n        emit(Result.failure(it))\n    }\n\n    private fun getBottomTags(limit: Int): Flow<Result<List<MangaTag>>> = combine(\n        flow { emit(searchRepository.getRareTags(repository.source, limit)) },\n        filterOptions.asFlow(),\n    ) { suggested, options ->\n        val all = options.getOrNull()?.availableTags.orEmpty()\n        val result = ArrayList<MangaTag>(limit)\n        result.addAll(suggested.take(limit))\n        if (result.size < limit) {\n            result.addAll(all.shuffled().take(limit - result.size))\n        }\n        if (result.isNotEmpty()) {\n            Result.success(result)\n        } else {\n            options.map { result }\n        }\n    }.catch {\n        emit(Result.failure(it))\n    }\n\n    private fun <T> List<T>.addFirstDistinct(other: Collection<T>): List<T> {\n        val result = ArrayDeque<T>(this.size + other.size)\n        result.addAll(this)\n        for (item in other) {\n            if (item !in result) {\n                result.addFirst(item)\n            }\n        }\n        return result\n    }\n\n    private fun <T> List<T>.addFirstDistinct(item: T): List<T> {\n        val result = ArrayDeque<T>(this.size + 1)\n        result.addAll(this)\n        if (item !in result) {\n            result.addFirst(item)\n        }\n        return result\n    }\n\n    data class Snapshot(\n        val sortOrder: SortOrder,\n        val listFilter: MangaListFilter,\n    )\n\n    interface Owner {\n\n        val filterCoordinator: FilterCoordinator\n    }\n\n    companion object {\n\n        private const val TAGS_LIMIT = 12\n        private val MAX_YEAR = Calendar.getInstance()[Calendar.YEAR] + 1\n\n        fun find(fragment: Fragment): FilterCoordinator? {\n            (fragment.activity as? Owner)?.let {\n                return it.filterCoordinator\n            }\n            var f = fragment\n            while (true) {\n                (f as? Owner)?.let {\n                    return it.filterCoordinator\n                }\n                f = f.parentFragment ?: break\n            }\n            return null\n        }\n\n        fun require(fragment: Fragment): FilterCoordinator {\n            return find(fragment) ?: throw IllegalStateException(\"FilterCoordinator cannot be found\")\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterFieldLayout.kt",
    "content": "package org.koitharu.kotatsu.filter.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.RelativeLayout\nimport android.widget.TextView\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StringRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport androidx.core.view.setPadding\nimport androidx.core.widget.TextViewCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.getThemeColorStateList\nimport org.koitharu.kotatsu.core.util.ext.setThemeTextAppearance\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ViewFilterFieldBinding\nimport java.util.LinkedList\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nclass FilterFieldLayout @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : RelativeLayout(context, attrs) {\n\n\tprivate val contentViews = LinkedList<View>()\n\tprivate val binding = ViewFilterFieldBinding.inflate(LayoutInflater.from(context), this)\n\tprivate var errorView: TextView? = null\n\tprivate var isInitialized = true\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.FilterFieldLayout, defStyleAttr) {\n\t\t\tbinding.textViewTitle.text = getString(R.styleable.FilterFieldLayout_title)\n\t\t\tbinding.buttonMore.isInvisible = !getBoolean(R.styleable.FilterFieldLayout_showMoreButton, false)\n\t\t}\n\t}\n\n\toverride fun onViewAdded(child: View) {\n\t\tsuper.onViewAdded(child)\n\t\tif (!isInitialized) {\n\t\t\treturn\n\t\t}\n\t\tassert(child.id != NO_ID)\n\t\tval lp = (child.layoutParams as? LayoutParams) ?: (generateDefaultLayoutParams() as LayoutParams)\n\t\tlp.alignWithParent = true\n\t\tlp.width = 0\n\t\tlp.addRule(ALIGN_PARENT_START)\n\t\tlp.addRule(ALIGN_PARENT_END)\n\t\tlp.addRule(BELOW, contentViews.lastOrNull()?.id ?: binding.textViewTitle.id)\n\t\tchild.layoutParams = lp\n\t\tcontentViews.add(child)\n\t}\n\n\toverride fun onViewRemoved(child: View?) {\n\t\tsuper.onViewRemoved(child)\n\t\tcontentViews.remove(child)\n\t}\n\n\tfun setValueText(valueText: String?) {\n\t\tif (!binding.buttonMore.isVisible) {\n\t\t\tbinding.textViewValue.textAndVisible = valueText\n\t\t}\n\t}\n\n\tfun setTitle(@StringRes titleResId: Int) {\n\t\tbinding.textViewTitle.setText(titleResId)\n\t}\n\n\tfun setError(errorMessage: String?) {\n\t\tif (errorMessage == null && errorView == null) {\n\t\t\treturn\n\t\t}\n\t\tgetErrorLabel().textAndVisible = errorMessage\n\t}\n\n\tfun setOnMoreButtonClickListener(clickListener: OnClickListener?) {\n\t\tbinding.buttonMore.setOnClickListener(clickListener)\n\t}\n\n\tprivate fun getErrorLabel(): TextView {\n\t\terrorView?.let {\n\t\t\treturn it\n\t\t}\n\t\tval label = TextView(context)\n\t\tlabel.id = R.id.textView_error\n\t\tlabel.compoundDrawablePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)\n\t\tlabel.gravity = Gravity.CENTER_VERTICAL or Gravity.START\n\t\tlabel.setPadding(resources.getDimensionPixelOffset(R.dimen.margin_small))\n\t\tlabel.setThemeTextAppearance(\n\t\t\tmaterialR.attr.textAppearanceBodySmall,\n\t\t\tmaterialR.style.TextAppearance_Material3_BodySmall,\n\t\t)\n\t\tlabel.drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_error_small)\n\t\tTextViewCompat.setCompoundDrawableTintList(\n\t\t\tlabel,\n\t\t\tcontext.getThemeColorStateList(appcompatR.attr.colorControlNormal),\n\t\t)\n\t\taddView(label)\n\t\terrorView = label\n\t\treturn label\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderFragment.kt",
    "content": "package org.koitharu.kotatsu.filter.ui\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport com.google.android.material.chip.Chip\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.flowOn\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.databinding.FragmentFilterHeaderBinding\nimport org.koitharu.kotatsu.filter.data.PersistableFilter\nimport org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.Demographic\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.YEAR_UNKNOWN\nimport java.util.Locale\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass FilterHeaderFragment : BaseFragment<FragmentFilterHeaderBinding>(), ChipsView.OnChipClickListener,\n    ChipsView.OnChipCloseClickListener {\n\n    @Inject\n    lateinit var filterHeaderProducer: FilterHeaderProducer\n\n    private val filter: FilterCoordinator\n        get() = (requireActivity() as FilterCoordinator.Owner).filterCoordinator\n\n    override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFilterHeaderBinding {\n        return FragmentFilterHeaderBinding.inflate(inflater, container, false)\n    }\n\n    override fun onViewBindingCreated(binding: FragmentFilterHeaderBinding, savedInstanceState: Bundle?) {\n        super.onViewBindingCreated(binding, savedInstanceState)\n        binding.chipsTags.onChipClickListener = this\n        binding.chipsTags.onChipCloseClickListener = this\n        filterHeaderProducer.observeHeader(filter)\n            .flowOn(Dispatchers.Default)\n            .observe(viewLifecycleOwner, ::onDataChanged)\n    }\n\n    override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat = insets\n\n    override fun onChipClick(chip: Chip, data: Any?) {\n        when (data) {\n            is MangaTag -> filter.toggleTag(data, !chip.isChecked)\n            is PersistableFilter -> if (chip.isChecked) {\n                filter.reset()\n            } else {\n                filter.setAdjusted(data.filter)\n            }\n\n            is String -> Unit\n            null -> router.showTagsCatalogSheet(excludeMode = false)\n        }\n    }\n\n    override fun onChipCloseClick(chip: Chip, data: Any?) {\n        when (data) {\n            is String -> if (data == filter.snapshot().listFilter.author) {\n                filter.setAuthor(null)\n            } else {\n                filter.setQuery(null)\n            }\n\n            is ContentRating -> filter.toggleContentRating(data, false)\n            is Demographic -> filter.toggleDemographic(data, false)\n            is ContentType -> filter.toggleContentType(data, false)\n            is MangaState -> filter.toggleState(data, false)\n            is Locale -> filter.setLocale(null)\n            is Int -> filter.setYear(YEAR_UNKNOWN)\n            is IntRange -> filter.setYearRange(YEAR_UNKNOWN, YEAR_UNKNOWN)\n        }\n    }\n\n    private fun onDataChanged(header: FilterHeaderModel) {\n        val binding = viewBinding ?: return\n        val chips = header.chips\n        if (chips.isEmpty()) {\n            binding.chipsTags.setChips(emptyList())\n            binding.root.isVisible = false\n            return\n        }\n        binding.chipsTags.setChips(header.chips)\n        binding.root.isVisible = true\n        if (binding.root.context.isAnimationsEnabled) {\n            binding.scrollView.smoothScrollTo(0, 0)\n        } else {\n            binding.scrollView.scrollTo(0, 0)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterHeaderProducer.kt",
    "content": "package org.koitharu.kotatsu.filter.ui\n\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.combine\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.titleResId\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.filter.data.PersistableFilter\nimport org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel\nimport org.koitharu.kotatsu.filter.ui.model.FilterProperty\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.toTitleCase\nimport org.koitharu.kotatsu.search.domain.MangaSearchRepository\nimport javax.inject.Inject\nimport androidx.appcompat.R as appcompatR\n\nclass FilterHeaderProducer @Inject constructor(\n    private val searchRepository: MangaSearchRepository,\n) {\n\n    fun observeHeader(filterCoordinator: FilterCoordinator): Flow<FilterHeaderModel> {\n        return combine(\n            filterCoordinator.savedFilters,\n            filterCoordinator.tags,\n            filterCoordinator.observe(),\n        ) { saved, tags, snapshot ->\n            val chipList = createChipsList(\n                source = filterCoordinator.mangaSource,\n                capabilities = filterCoordinator.capabilities,\n                savedFilters = saved,\n                tagsProperty = tags,\n                snapshot = snapshot.listFilter,\n                limit = 12,\n            )\n            FilterHeaderModel(\n                chips = chipList,\n                sortOrder = snapshot.sortOrder,\n                isFilterApplied = !snapshot.listFilter.isEmpty(),\n            )\n        }\n    }\n\n    private suspend fun createChipsList(\n        source: MangaSource,\n        capabilities: MangaListFilterCapabilities,\n        savedFilters: FilterProperty<PersistableFilter>,\n        tagsProperty: FilterProperty<MangaTag>,\n        snapshot: MangaListFilter,\n        limit: Int,\n    ): List<ChipsView.ChipModel> {\n        val result = ArrayDeque<ChipsView.ChipModel>(savedFilters.availableItems.size + limit + 3)\n        if (snapshot.query.isNullOrEmpty() || capabilities.isSearchWithFiltersSupported) {\n            val selectedTags = tagsProperty.selectedItems.toMutableSet()\n            var tags = if (selectedTags.isEmpty()) {\n                searchRepository.getTagsSuggestion(\"\", limit, source)\n            } else {\n                searchRepository.getTagsSuggestion(selectedTags).take(limit)\n            }\n            if (tags.size < limit) {\n                tags = tags + tagsProperty.availableItems.take(limit - tags.size)\n            }\n            if (tags.isEmpty() && selectedTags.isEmpty()) {\n                return emptyList()\n            }\n            for (saved in savedFilters.availableItems) {\n                val model = ChipsView.ChipModel(\n                    title = saved.name,\n                    isChecked = saved in savedFilters.selectedItems,\n                    data = saved,\n                )\n                if (model.isChecked) {\n                    selectedTags.removeAll(saved.filter.tags)\n                    result.addFirst(model)\n                } else {\n                    result.addLast(model)\n                }\n            }\n            for (tag in tags) {\n                val model = ChipsView.ChipModel(\n                    title = tag.title,\n                    isChecked = selectedTags.remove(tag),\n                    data = tag,\n                )\n                if (model.isChecked) {\n                    result.addFirst(model)\n                } else {\n                    result.addLast(model)\n                }\n            }\n            for (tag in selectedTags) {\n                val model = ChipsView.ChipModel(\n                    title = tag.title,\n                    isChecked = true,\n                    data = tag,\n                )\n                result.addFirst(model)\n            }\n        }\n        snapshot.locale?.let {\n            result.addFirst(\n                ChipsView.ChipModel(\n                    title = it.getDisplayName(it).toTitleCase(it),\n                    icon = R.drawable.ic_language,\n                    isCloseable = true,\n                    data = it,\n                ),\n            )\n        }\n        snapshot.types.forEach {\n            result.addFirst(\n                ChipsView.ChipModel(\n                    titleResId = it.titleResId,\n                    isCloseable = true,\n                    data = it,\n                ),\n            )\n        }\n        snapshot.demographics.forEach {\n            result.addFirst(\n                ChipsView.ChipModel(\n                    titleResId = it.titleResId,\n                    isCloseable = true,\n                    data = it,\n                ),\n            )\n        }\n        snapshot.contentRating.forEach {\n            result.addFirst(\n                ChipsView.ChipModel(\n                    titleResId = it.titleResId,\n                    isCloseable = true,\n                    data = it,\n                ),\n            )\n        }\n        snapshot.states.forEach {\n            result.addFirst(\n                ChipsView.ChipModel(\n                    titleResId = it.titleResId,\n                    isCloseable = true,\n                    data = it,\n                ),\n            )\n        }\n        if (!snapshot.query.isNullOrEmpty()) {\n            result.addFirst(\n                ChipsView.ChipModel(\n                    title = snapshot.query,\n                    icon = appcompatR.drawable.abc_ic_search_api_material,\n                    isCloseable = true,\n                    data = snapshot.query,\n                ),\n            )\n        }\n        if (!snapshot.author.isNullOrEmpty()) {\n            result.addFirst(\n                ChipsView.ChipModel(\n                    title = snapshot.author,\n                    icon = R.drawable.ic_user,\n                    isCloseable = true,\n                    data = snapshot.author,\n                ),\n            )\n        }\n        val hasTags = result.any { it.data is MangaTag }\n        if (hasTags) {\n            result.addFirst(moreTagsChip())\n        }\n        return result\n    }\n\n    private fun moreTagsChip() = ChipsView.ChipModel(\n        titleResId = R.string.genres,\n        icon = R.drawable.ic_drawer_menu_open,\n    )\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterHeaderModel.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.model\n\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.parsers.model.SortOrder\n\ndata class FilterHeaderModel(\n\tval chips: Collection<ChipsView.ChipModel>,\n\tval sortOrder: SortOrder?,\n\tval isFilterApplied: Boolean,\n) {\n\n\tval textSummary: String\n\t\tget() = chips.mapNotNull { if (it.isChecked) it.title else null }.joinToString()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/FilterProperty.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.model\n\ndata class FilterProperty<out T>(\n\tval availableItems: List<T>,\n\tval selectedItems: Set<T>,\n\tval isLoading: Boolean,\n\tval error: Throwable?,\n) {\n\n\tconstructor(\n\t\tavailableItems: List<T>,\n\t\tselectedItems: Set<T>,\n\t) : this(\n\t\tavailableItems = availableItems,\n\t\tselectedItems = selectedItems,\n\t\tisLoading = false,\n\t\terror = null,\n\t)\n\n\tconstructor(\n\t\tavailableItems: List<T>,\n\t\tselectedItem: T,\n\t) : this(\n\t\tavailableItems = availableItems,\n\t\tselectedItems = setOf(selectedItem),\n\t\tisLoading = false,\n\t\terror = null,\n\t)\n\n\tfun isEmpty(): Boolean = availableItems.isEmpty()\n\n\tfun isEmptyAndSuccess(): Boolean = availableItems.isEmpty() && error == null\n\n\tcompanion object {\n\n\t\tval LOADING = FilterProperty<Nothing>(\n\t\t\tavailableItems = emptyList(),\n\t\t\tselectedItems = emptySet(),\n\t\t\tisLoading = true,\n\t\t\terror = null,\n\t\t)\n\n\t\tval EMPTY = FilterProperty<Nothing>(\n\t\t\tavailableItems = emptyList(),\n\t\t\tselectedItems = emptySet(),\n\t\t)\n\n\t\tfun error(error: Throwable) = FilterProperty<Nothing>(\n\t\t\tavailableItems = emptyList(),\n\t\t\tselectedItems = emptySet(),\n\t\t\tisLoading = false,\n\t\t\terror = error,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/model/TagCatalogItem.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.model\n\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.MangaTag\n\ndata class TagCatalogItem(\n\tval tag: MangaTag,\n\tval isChecked: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is TagCatalogItem && other.tag == tag\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\treturn if (previousState is TagCatalogItem && previousState.isChecked != isChecked) {\n\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/sheet/FilterSheetFragment.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.sheet\n\nimport android.os.Bundle\nimport android.text.InputFilter\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.inputmethod.EditorInfo\nimport android.widget.AdapterView\nimport android.widget.ArrayAdapter\nimport android.widget.LinearLayout\nimport android.widget.Toast\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport com.google.android.material.chip.Chip\nimport com.google.android.material.slider.RangeSlider\nimport com.google.android.material.slider.Slider\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.titleResId\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.dialog.setEditText\nimport org.koitharu.kotatsu.core.ui.model.titleRes\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.AlphanumComparator\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getDisplayName\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.parentView\nimport org.koitharu.kotatsu.core.util.ext.setValueRounded\nimport org.koitharu.kotatsu.core.util.ext.setValuesRounded\nimport org.koitharu.kotatsu.databinding.SheetFilterBinding\nimport org.koitharu.kotatsu.filter.data.PersistableFilter\nimport org.koitharu.kotatsu.filter.data.PersistableFilter.Companion.MAX_TITLE_LENGTH\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.filter.ui.model.FilterProperty\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.Demographic\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.model.YEAR_UNKNOWN\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.toIntUp\nimport java.util.Locale\nimport java.util.TreeSet\n\nclass FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),\n    AdapterView.OnItemSelectedListener,\n    View.OnClickListener,\n    ChipsView.OnChipClickListener,\n    ChipsView.OnChipLongClickListener,\n    ChipsView.OnChipCloseClickListener {\n\n    override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {\n        return SheetFilterBinding.inflate(inflater, container, false)\n    }\n\n    override fun onViewBindingCreated(binding: SheetFilterBinding, savedInstanceState: Bundle?) {\n        super.onViewBindingCreated(binding, savedInstanceState)\n        if (dialog == null) {\n            binding.adjustForEmbeddedLayout()\n        }\n        val filter = FilterCoordinator.require(this)\n        filter.sortOrder.observe(viewLifecycleOwner, this::onSortOrderChanged)\n        filter.locale.observe(viewLifecycleOwner, this::onLocaleChanged)\n        filter.originalLocale.observe(viewLifecycleOwner, this::onOriginalLocaleChanged)\n        filter.tags.observe(viewLifecycleOwner, this::onTagsChanged)\n        filter.tagsExcluded.observe(viewLifecycleOwner, this::onTagsExcludedChanged)\n        filter.authors.observe(viewLifecycleOwner, this::onAuthorsChanged)\n        filter.states.observe(viewLifecycleOwner, this::onStateChanged)\n        filter.contentTypes.observe(viewLifecycleOwner, this::onContentTypesChanged)\n        filter.contentRating.observe(viewLifecycleOwner, this::onContentRatingChanged)\n        filter.demographics.observe(viewLifecycleOwner, this::onDemographicsChanged)\n        filter.year.observe(viewLifecycleOwner, this::onYearChanged)\n        filter.yearRange.observe(viewLifecycleOwner, this::onYearRangeChanged)\n        filter.savedFilters.observe(viewLifecycleOwner, ::onSavedPresetsChanged)\n\n        binding.layoutGenres.setTitle(\n            if (filter.capabilities.isMultipleTagsSupported) {\n                R.string.genres\n            } else {\n                R.string.genre\n            },\n        )\n        binding.spinnerLocale.onItemSelectedListener = this\n        binding.spinnerOriginalLocale.onItemSelectedListener = this\n        binding.spinnerOrder.onItemSelectedListener = this\n        binding.chipsSavedFilters.onChipClickListener = this\n        binding.chipsState.onChipClickListener = this\n        binding.chipsTypes.onChipClickListener = this\n        binding.chipsContentRating.onChipClickListener = this\n        binding.chipsDemographics.onChipClickListener = this\n        binding.chipsGenres.onChipClickListener = this\n        binding.chipsGenresExclude.onChipClickListener = this\n        binding.chipsAuthor.onChipClickListener = this\n        binding.chipsSavedFilters.onChipLongClickListener = this\n        binding.chipsSavedFilters.onChipCloseClickListener = this\n        binding.sliderYear.addOnChangeListener(this::onSliderValueChange)\n        binding.sliderYearsRange.addOnChangeListener(this::onRangeSliderValueChange)\n        binding.layoutGenres.setOnMoreButtonClickListener {\n            router.showTagsCatalogSheet(excludeMode = false)\n        }\n        binding.layoutGenresExclude.setOnMoreButtonClickListener {\n            router.showTagsCatalogSheet(excludeMode = true)\n        }\n        combine(\n            filter.observe().map { it.listFilter.isNotEmpty() }.distinctUntilChanged(),\n            filter.savedFilters.map { it.selectedItems.isEmpty() }.distinctUntilChanged(),\n            Boolean::and,\n        ).flowOn(Dispatchers.Default)\n            .observe(viewLifecycleOwner) {\n                binding.buttonSave.isEnabled = it\n            }\n        binding.buttonSave.setOnClickListener(this)\n        binding.buttonDone.setOnClickListener(this)\n    }\n\n    private fun SheetFilterBinding.adjustForEmbeddedLayout() {\n        layoutBody.updatePadding(top = layoutBody.paddingBottom)\n        scrollView.scrollIndicators = 0\n        buttonDone.isVisible = false\n        this.root.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT\n        buttonSave.updateLayoutParams<LinearLayout.LayoutParams> {\n            weight = 0f\n            width = LinearLayout.LayoutParams.WRAP_CONTENT\n            gravity = Gravity.END or Gravity.CENTER_VERTICAL\n        }\n    }\n\n    override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n        val typeMask = WindowInsetsCompat.Type.systemBars()\n        viewBinding?.layoutBottom?.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n            bottomMargin = insets.getInsets(typeMask).bottom\n        }\n        return insets.consume(v, typeMask, bottom = true)\n    }\n\n    override fun onClick(v: View) {\n        when (v.id) {\n            R.id.button_done -> dismiss()\n            R.id.button_save -> onSaveFilterClick(\"\")\n        }\n    }\n\n    override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {\n        val filter = FilterCoordinator.require(this)\n        when (parent.id) {\n            R.id.spinner_order -> filter.setSortOrder(filter.sortOrder.value.availableItems[position])\n            R.id.spinner_locale -> filter.setLocale(filter.locale.value.availableItems[position])\n            R.id.spinner_original_locale -> filter.setOriginalLocale(filter.originalLocale.value.availableItems[position])\n        }\n    }\n\n    override fun onNothingSelected(parent: AdapterView<*>?) = Unit\n\n    private fun onSliderValueChange(slider: Slider, value: Float, fromUser: Boolean) {\n        if (!fromUser) {\n            return\n        }\n        val intValue = value.toInt()\n        val filter = FilterCoordinator.require(this)\n        when (slider.id) {\n            R.id.slider_year -> filter.setYear(\n                if (intValue <= slider.valueFrom.toIntUp()) {\n                    YEAR_UNKNOWN\n                } else {\n                    intValue\n                },\n            )\n        }\n    }\n\n    private fun onRangeSliderValueChange(slider: RangeSlider, value: Float, fromUser: Boolean) {\n        if (!fromUser) {\n            return\n        }\n        val filter = FilterCoordinator.require(this)\n        when (slider.id) {\n            R.id.slider_yearsRange -> filter.setYearRange(\n                valueFrom = slider.values.firstOrNull()?.let {\n                    if (it <= slider.valueFrom) YEAR_UNKNOWN else it.toInt()\n                } ?: YEAR_UNKNOWN,\n                valueTo = slider.values.lastOrNull()?.let {\n                    if (it >= slider.valueTo) YEAR_UNKNOWN else it.toInt()\n                } ?: YEAR_UNKNOWN,\n            )\n        }\n    }\n\n    override fun onChipClick(chip: Chip, data: Any?) {\n        val filter = FilterCoordinator.require(this)\n        when (data) {\n            is MangaState -> filter.toggleState(data, !chip.isChecked)\n            is MangaTag -> if (chip.parentView?.id == R.id.chips_genresExclude) {\n                filter.toggleTagExclude(data, !chip.isChecked)\n            } else {\n                filter.toggleTag(data, !chip.isChecked)\n            }\n\n            is ContentType -> filter.toggleContentType(data, !chip.isChecked)\n            is ContentRating -> filter.toggleContentRating(data, !chip.isChecked)\n            is Demographic -> filter.toggleDemographic(data, !chip.isChecked)\n            is PersistableFilter -> filter.setAdjusted(data.filter)\n            is String -> if (chip.isChecked) {\n                filter.setAuthor(null)\n            } else {\n                filter.setAuthor(data)\n            }\n            null -> router.showTagsCatalogSheet(excludeMode = chip.parentView?.id == R.id.chips_genresExclude)\n        }\n    }\n\n    override fun onChipLongClick(chip: Chip, data: Any?): Boolean {\n        return when (data) {\n            is PersistableFilter -> {\n                showSavedFilterMenu(chip, data)\n                true\n            }\n\n            else -> false\n        }\n    }\n\n    override fun onChipCloseClick(chip: Chip, data: Any?) {\n        when (data) {\n            is PersistableFilter -> {\n                showSavedFilterMenu(chip, data)\n            }\n        }\n    }\n\n    private fun onSortOrderChanged(value: FilterProperty<SortOrder>) {\n        val b = viewBinding ?: return\n        b.layoutOrder.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val selected = value.selectedItems.single()\n        b.spinnerOrder.adapter = ArrayAdapter(\n            b.spinnerOrder.context,\n            android.R.layout.simple_spinner_dropdown_item,\n            android.R.id.text1,\n            value.availableItems.map { b.spinnerOrder.context.getString(it.titleRes) },\n        )\n        val selectedIndex = value.availableItems.indexOf(selected)\n        if (selectedIndex >= 0) {\n            b.spinnerOrder.setSelection(selectedIndex, false)\n        }\n    }\n\n    private fun onLocaleChanged(value: FilterProperty<Locale?>) {\n        val b = viewBinding ?: return\n        b.layoutLocale.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val selected = value.selectedItems.singleOrNull()\n        b.spinnerLocale.adapter = ArrayAdapter(\n            b.spinnerLocale.context,\n            android.R.layout.simple_spinner_dropdown_item,\n            android.R.id.text1,\n            value.availableItems.map { it.getDisplayName(b.spinnerLocale.context) },\n        )\n        val selectedIndex = value.availableItems.indexOf(selected)\n        if (selectedIndex >= 0) {\n            b.spinnerLocale.setSelection(selectedIndex, false)\n        }\n    }\n\n    private fun onOriginalLocaleChanged(value: FilterProperty<Locale?>) {\n        val b = viewBinding ?: return\n        b.layoutOriginalLocale.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val selected = value.selectedItems.singleOrNull()\n        b.spinnerOriginalLocale.adapter = ArrayAdapter(\n            b.spinnerOriginalLocale.context,\n            android.R.layout.simple_spinner_dropdown_item,\n            android.R.id.text1,\n            value.availableItems.map { it.getDisplayName(b.spinnerOriginalLocale.context) },\n        )\n        val selectedIndex = value.availableItems.indexOf(selected)\n        if (selectedIndex >= 0) {\n            b.spinnerOriginalLocale.setSelection(selectedIndex, false)\n        }\n    }\n\n    private fun onTagsChanged(value: FilterProperty<MangaTag>) {\n        val b = viewBinding ?: return\n        b.layoutGenres.isGone = value.isEmptyAndSuccess()\n        b.layoutGenres.setError(value.error?.getDisplayMessage(resources))\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { tag ->\n            ChipsView.ChipModel(\n                title = tag.title,\n                isChecked = tag in value.selectedItems,\n                data = tag,\n            )\n        }\n        b.chipsGenres.setChips(chips)\n    }\n\n    private fun onTagsExcludedChanged(value: FilterProperty<MangaTag>) {\n        val b = viewBinding ?: return\n        b.layoutGenresExclude.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { tag ->\n            ChipsView.ChipModel(\n                title = tag.title,\n                isChecked = tag in value.selectedItems,\n                data = tag,\n            )\n        }\n        b.chipsGenresExclude.setChips(chips)\n    }\n\n    private fun onAuthorsChanged(value: FilterProperty<String>) {\n        val b = viewBinding ?: return\n        b.layoutAuthor.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { author ->\n            ChipsView.ChipModel(\n                title = author,\n                isChecked = author in value.selectedItems,\n                data = author,\n            )\n        }\n        b.chipsAuthor.setChips(chips)\n    }\n\n    private fun onStateChanged(value: FilterProperty<MangaState>) {\n        val b = viewBinding ?: return\n        b.layoutState.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { state ->\n            ChipsView.ChipModel(\n                title = getString(state.titleResId),\n                isChecked = state in value.selectedItems,\n                data = state,\n            )\n        }\n        b.chipsState.setChips(chips)\n    }\n\n    private fun onContentTypesChanged(value: FilterProperty<ContentType>) {\n        val b = viewBinding ?: return\n        b.layoutTypes.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { type ->\n            ChipsView.ChipModel(\n                title = getString(type.titleResId),\n                isChecked = type in value.selectedItems,\n                data = type,\n            )\n        }\n        b.chipsTypes.setChips(chips)\n    }\n\n    private fun onContentRatingChanged(value: FilterProperty<ContentRating>) {\n        val b = viewBinding ?: return\n        b.layoutContentRating.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { contentRating ->\n            ChipsView.ChipModel(\n                title = getString(contentRating.titleResId),\n                isChecked = contentRating in value.selectedItems,\n                data = contentRating,\n            )\n        }\n        b.chipsContentRating.setChips(chips)\n    }\n\n    private fun onDemographicsChanged(value: FilterProperty<Demographic>) {\n        val b = viewBinding ?: return\n        b.layoutDemographics.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { demographic ->\n            ChipsView.ChipModel(\n                title = getString(demographic.titleResId),\n                isChecked = demographic in value.selectedItems,\n                data = demographic,\n            )\n        }\n        b.chipsDemographics.setChips(chips)\n    }\n\n    private fun onYearChanged(value: FilterProperty<Int>) {\n        val b = viewBinding ?: return\n        b.layoutYear.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val currentValue = value.selectedItems.singleOrNull() ?: YEAR_UNKNOWN\n        b.layoutYear.setValueText(\n            if (currentValue == YEAR_UNKNOWN) {\n                getString(R.string.any)\n            } else {\n                currentValue.toString()\n            },\n        )\n        b.sliderYear.valueFrom = value.availableItems.first().toFloat()\n        b.sliderYear.valueTo = value.availableItems.last().toFloat()\n        b.sliderYear.setValueRounded(currentValue.toFloat())\n    }\n\n    private fun onYearRangeChanged(value: FilterProperty<Int>) {\n        val b = viewBinding ?: return\n        b.layoutYearsRange.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        b.sliderYearsRange.valueFrom = value.availableItems.first().toFloat()\n        b.sliderYearsRange.valueTo = value.availableItems.last().toFloat()\n        val currentValueFrom = value.selectedItems.firstOrNull()?.toFloat() ?: b.sliderYearsRange.valueFrom\n        val currentValueTo = value.selectedItems.lastOrNull()?.toFloat() ?: b.sliderYearsRange.valueTo\n        b.layoutYearsRange.setValueText(\n            getString(\n                R.string.memory_usage_pattern,\n                currentValueFrom.toInt().toString(),\n                currentValueTo.toInt().toString(),\n            ),\n        )\n        b.sliderYearsRange.setValuesRounded(currentValueFrom, currentValueTo)\n    }\n\n    private fun onSavedPresetsChanged(value: FilterProperty<PersistableFilter>) {\n        val b = viewBinding ?: return\n        b.layoutSavedFilters.isGone = value.isEmpty()\n        if (value.isEmpty()) {\n            return\n        }\n        val chips = value.availableItems.map { f ->\n            ChipsView.ChipModel(\n                title = f.name,\n                isChecked = f in value.selectedItems,\n                data = f,\n                isDropdown = true,\n            )\n        }\n        b.chipsSavedFilters.setChips(chips)\n    }\n\n    private fun showSavedFilterMenu(anchor: View, preset: PersistableFilter) {\n        val menu = PopupMenu(context ?: return, anchor)\n        val filter = FilterCoordinator.require(this)\n        menu.inflate(R.menu.popup_saved_filter)\n        menu.setOnMenuItemClickListener { menuItem ->\n            when (menuItem.itemId) {\n                R.id.action_delete -> filter.deleteSavedFilter(preset.id)\n                R.id.action_rename -> onRenameFilterClick(preset)\n            }\n            true\n        }\n        menu.show()\n    }\n\n    private fun onSaveFilterClick(name: String) {\n        val filter = FilterCoordinator.require(this)\n        val existingNames = filter.savedFilters.value.availableItems\n            .mapTo(TreeSet(AlphanumComparator()), PersistableFilter::name)\n        buildAlertDialog(context ?: return) {\n            val input = setEditText(\n                entries = existingNames.toList(),\n                inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES,\n                singleLine = true,\n            )\n            input.setHint(R.string.enter_name)\n            input.setText(name)\n            input.filters += InputFilter.LengthFilter(MAX_TITLE_LENGTH)\n            setTitle(R.string.save_filter)\n            setPositiveButton(R.string.save) { _, _ ->\n                val text = input.text?.toString()?.trim()\n                if (text.isNullOrEmpty()) {\n                    Toast.makeText(context, R.string.invalid_value_message, Toast.LENGTH_SHORT).show()\n                    onSaveFilterClick(\"\")\n                } else if (text in existingNames) {\n                    askForFilterOverwrite(filter, text)\n                } else {\n                    filter.saveCurrentFilter(text)\n                }\n            }\n            setNegativeButton(android.R.string.cancel, null)\n        }.show()\n    }\n\n    private fun onRenameFilterClick(preset: PersistableFilter) {\n        val filter = FilterCoordinator.require(this)\n        val existingNames = filter.savedFilters.value.availableItems.mapToSet { it.name }\n        buildAlertDialog(context ?: return) {\n            val input = setEditText(\n                inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES,\n                singleLine = true,\n            )\n            input.filters += InputFilter.LengthFilter(MAX_TITLE_LENGTH)\n            input.setHint(R.string.enter_name)\n            input.setText(preset.name)\n            setTitle(R.string.rename)\n            setPositiveButton(R.string.save) { _, _ ->\n                val text = input.text?.toString()?.trim()\n                if (text.isNullOrEmpty() || text in existingNames) {\n                    Toast.makeText(context, R.string.invalid_value_message, Toast.LENGTH_SHORT).show()\n                } else {\n                    filter.renameSavedFilter(preset.id, text)\n                }\n            }\n            setNegativeButton(android.R.string.cancel, null)\n        }.show()\n    }\n\n    private fun askForFilterOverwrite(filter: FilterCoordinator, name: String) {\n        buildAlertDialog(context ?: return) {\n            setTitle(R.string.save_filter)\n            setMessage(getString(R.string.filter_overwrite_confirm, name))\n            setPositiveButton(R.string.overwrite) { _, _ ->\n                filter.saveCurrentFilter(name)\n            }\n            setNegativeButton(android.R.string.cancel) { _, _ ->\n                onSaveFilterClick(name)\n            }\n        }.show()\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagTitleComparator.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.tags\n\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport java.text.Collator\nimport java.util.Locale\n\nclass TagTitleComparator(lc: String?) : Comparator<MangaTag> {\n\n\tprivate val collator = lc?.let { Collator.getInstance(Locale(it)) }\n\n\toverride fun compare(o1: MangaTag, o2: MangaTag): Int {\n\t\tval t1 = o1.title.lowercase()\n\t\tval t2 = o2.title.lowercase()\n\t\treturn collator?.compare(t1, t2) ?: compareValues(t1, t2)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogAdapter.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.tags\n\nimport android.content.Context\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.core.util.ext.setChecked\nimport org.koitharu.kotatsu.databinding.ItemCheckableNewBinding\nimport org.koitharu.kotatsu.filter.ui.model.TagCatalogItem\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.errorFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.errorStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass TagsCatalogAdapter(\n\tlistener: OnListItemClickListener<TagCatalogItem>,\n) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {\n\n\tinit {\n\t\taddDelegate(ListItemType.FILTER_TAG, tagCatalogDelegate(listener))\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())\n\t\taddDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(null))\n\t\taddDelegate(ListItemType.STATE_ERROR, errorStateListAD(null))\n\t}\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn (items.getOrNull(position) as? TagCatalogItem)?.tag?.title?.firstOrNull()?.uppercase()\n\t}\n\n\tprivate fun tagCatalogDelegate(\n\t\tlistener: OnListItemClickListener<TagCatalogItem>,\n\t) = adapterDelegateViewBinding<TagCatalogItem, ListModel, ItemCheckableNewBinding>(\n\t\t{ layoutInflater, parent -> ItemCheckableNewBinding.inflate(layoutInflater, parent, false) },\n\t) {\n\n\t\titemView.setOnClickListener {\n\t\t\tlistener.onItemClick(item, itemView)\n\t\t}\n\n\t\tbind { payloads ->\n\t\t\tbinding.root.text = item.tag.title\n\t\t\tbinding.root.setChecked(item.isChecked, ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED in payloads)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.tags\n\nimport android.os.Bundle\nimport android.text.Editable\nimport android.view.KeyEvent\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.inputmethod.EditorInfo\nimport android.widget.TextView\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport dagger.hilt.android.lifecycle.withCreationCallback\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior\nimport org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.databinding.SheetTagsBinding\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.filter.ui.model.TagCatalogItem\n\n@AndroidEntryPoint\nclass TagsCatalogSheet : BaseAdaptiveSheet<SheetTagsBinding>(),\n\tOnListItemClickListener<TagCatalogItem>,\n\tDefaultTextWatcher,\n\tAdaptiveSheetCallback,\n\tView.OnFocusChangeListener,\n\tTextView.OnEditorActionListener {\n\n\tprivate val viewModel by viewModels<TagsCatalogViewModel>(\n\t\textrasProducer = {\n\t\t\tdefaultViewModelCreationExtras.withCreationCallback<TagsCatalogViewModel.Factory> { factory ->\n\t\t\t\tfactory.create(\n\t\t\t\t\tfilter = FilterCoordinator.require(this),\n\t\t\t\t\tisExcludeTag = requireArguments().getBoolean(AppRouter.KEY_EXCLUDE),\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t)\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetTagsBinding {\n\t\treturn SheetTagsBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: SheetTagsBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval adapter = TagsCatalogAdapter(this)\n\t\tbinding.recyclerView.adapter = adapter\n\t\tbinding.recyclerView.setHasFixedSize(true)\n\t\tbinding.editSearch.setText(viewModel.searchQuery.value)\n\t\tbinding.editSearch.addTextChangedListener(this)\n\t\tbinding.editSearch.onFocusChangeListener = this\n\t\tbinding.editSearch.setOnEditorActionListener(this)\n\t\tviewModel.content.observe(viewLifecycleOwner, adapter)\n\t\taddSheetCallback(this, viewLifecycleOwner)\n\t\tdisableFitToContents()\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeBask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeBask)\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAll(typeBask)\n\t}\n\n\toverride fun onItemClick(item: TagCatalogItem, view: View) {\n\t\tviewModel.handleTagClick(item.tag, item.isChecked)\n\t}\n\n\toverride fun onFocusChange(v: View?, hasFocus: Boolean) {\n\t\tsetExpanded(\n\t\t\tisExpanded = hasFocus || isExpanded,\n\t\t\tisLocked = hasFocus,\n\t\t)\n\t}\n\n\toverride fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean {\n\t\treturn if (actionId == EditorInfo.IME_ACTION_SEARCH) {\n\t\t\tv.clearFocus()\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tval q = s?.toString().orEmpty()\n\t\tviewModel.searchQuery.value = q\n\t}\n\n\toverride fun onStateChanged(sheet: View, newState: Int) {\n\t\tviewBinding?.recyclerView?.isFastScrollerEnabled = newState == AdaptiveSheetBehavior.STATE_EXPANDED\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogViewModel.kt",
    "content": "package org.koitharu.kotatsu.filter.ui.tags\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.filter.ui.model.FilterProperty\nimport org.koitharu.kotatsu.filter.ui.model.TagCatalogItem\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorFooter\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\n\n@HiltViewModel(assistedFactory = TagsCatalogViewModel.Factory::class)\nclass TagsCatalogViewModel @AssistedInject constructor(\n\t@Assisted private val filter: FilterCoordinator,\n\t@Assisted private val isExcluded: Boolean,\n\tprivate val mangaDataRepository: MangaDataRepository,\n) : BaseViewModel() {\n\n\tval searchQuery = MutableStateFlow(\"\")\n\n\tprivate val filterProperty: StateFlow<FilterProperty<MangaTag>>\n\t\tget() = if (isExcluded) filter.tagsExcluded else filter.tags\n\n\t@Suppress(\"RemoveExplicitTypeArguments\")\n\tprivate val tags: StateFlow<List<ListModel>> = combine(\n\t\tfilter.getAllTags(),\n\t\tflow<Collection<MangaTag>> { emit(emptyList()); emit(mangaDataRepository.findTags(filter.mangaSource)) },\n\t\tfilterProperty.map { it.selectedItems },\n\t) { available, cached, selected ->\n\t\tbuildList(available, cached, selected)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tval content = combine(tags, searchQuery) { raw, query ->\n\t\traw.filter { x ->\n\t\t\tx !is TagCatalogItem || x.tag.title.contains(query, ignoreCase = true)\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, listOf(LoadingState))\n\n\tfun handleTagClick(tag: MangaTag, isChecked: Boolean) {\n\t\tif (isExcluded) {\n\t\t\tfilter.toggleTagExclude(tag, !isChecked)\n\t\t} else {\n\t\t\tfilter.toggleTag(tag, !isChecked)\n\t\t}\n\t}\n\n\tprivate fun buildList(\n\t\tavailable: Result<List<MangaTag>>,\n\t\tcached: Collection<MangaTag>,\n\t\tselected: Set<MangaTag>,\n\t): List<ListModel> {\n\t\tval capacity = (available.getOrNull()?.size ?: 1) + cached.size\n\t\tval result = ArrayList<ListModel>(capacity)\n\t\tval added = HashSet<String>(capacity)\n\t\tavailable.getOrNull()?.forEach { tag ->\n\t\t\tif (added.add(tag.title)) {\n\t\t\t\tresult.add(\n\t\t\t\t\tTagCatalogItem(\n\t\t\t\t\t\ttag = tag,\n\t\t\t\t\t\tisChecked = tag in selected,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tcached.forEach { tag ->\n\t\t\tif (added.add(tag.title)) {\n\t\t\t\tresult.add(\n\t\t\t\t\tTagCatalogItem(\n\t\t\t\t\t\ttag = tag,\n\t\t\t\t\t\tisChecked = tag in selected,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (result.isNotEmpty()) {\n\t\t\tval locale = (filter.mangaSource as? MangaParserSource)?.locale\n\t\t\tresult.sortWith(compareBy(TagTitleComparator(locale)) { (it as TagCatalogItem).tag })\n\t\t}\n\t\tavailable.exceptionOrNull()?.let { error ->\n\t\t\tresult.add(\n\t\t\t\tif (result.isEmpty()) {\n\t\t\t\t\terror.toErrorState(canRetry = false)\n\t\t\t\t} else {\n\t\t\t\t\terror.toErrorFooter()\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t\treturn result\n\t}\n\n\t@AssistedFactory\n\tinterface Factory {\n\t\tfun create(filter: FilterCoordinator, isExcludeTag: Boolean): TagsCatalogViewModel\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/data/EntityMapping.kt",
    "content": "package org.koitharu.kotatsu.history.data\n\nimport org.koitharu.kotatsu.core.model.MangaHistory\nimport java.time.Instant\n\nfun HistoryEntity.toMangaHistory() = MangaHistory(\n\tcreatedAt = Instant.ofEpochMilli(createdAt),\n\tupdatedAt = Instant.ofEpochMilli(updatedAt),\n\tchapterId = chapterId,\n\tpage = page,\n\tscroll = scroll.toInt(),\n\tpercent = percent,\n\tchaptersCount = chaptersCount,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryDao.kt",
    "content": "package org.koitharu.kotatsu.history.data\n\nimport android.database.DatabaseUtils.sqlEscapeString\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.RawQuery\nimport androidx.room.Transaction\nimport androidx.sqlite.db.SupportSQLiteQuery\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\nimport org.koitharu.kotatsu.core.db.MangaQueryBuilder\nimport org.koitharu.kotatsu.core.db.TABLE_HISTORY\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_COMPLETED\n\n@Dao\nabstract class HistoryDao : MangaQueryBuilder.ConditionCallback {\n\n\t@Transaction\n\t@Query(\"SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC LIMIT :limit OFFSET :offset\")\n\tabstract suspend fun findAll(offset: Int, limit: Int): List<HistoryWithManga>\n\n\t@Transaction\n\t@Query(\"SELECT manga.* FROM history LEFT JOIN manga ON manga.manga_id = history.manga_id WHERE history.deleted_at = 0 AND (manga.title LIKE :query OR manga.alt_title LIKE :query) LIMIT :limit\")\n\tabstract suspend fun searchByTitle(query: String, limit: Int): List<MangaWithTags>\n\n\t@Transaction\n\t@Query(\"SELECT manga.* FROM history LEFT JOIN manga ON manga.manga_id = history.manga_id WHERE history.deleted_at = 0 AND (manga.author LIKE :query) LIMIT :limit\")\n\tabstract suspend fun searchByAuthor(query: String, limit: Int): List<MangaWithTags>\n\n\t@Transaction\n\t@Query(\"SELECT manga.* FROM history LEFT JOIN manga ON manga.manga_id = history.manga_id WHERE history.deleted_at = 0 AND EXISTS(SELECT 1 FROM tags LEFT JOIN manga_tags ON manga_tags.tag_id = tags.tag_id WHERE manga_tags.manga_id = manga.manga_id AND tags.title LIKE :query) LIMIT :limit\")\n\tabstract suspend fun searchByTag(query: String, limit: Int): List<MangaWithTags>\n\n\t@Transaction\n\t@Query(\"SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC\")\n\tabstract fun observeAll(): Flow<List<HistoryWithManga>>\n\n\t@Transaction\n\t@Query(\"SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC LIMIT :limit\")\n\tabstract fun observeAll(limit: Int): Flow<List<HistoryWithManga>>\n\n\tfun observeAll(\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t): Flow<List<HistoryWithManga>> = observeAllImpl(\n\t\tMangaQueryBuilder(TABLE_HISTORY, this)\n\t\t\t.join(\"LEFT JOIN manga ON history.manga_id = manga.manga_id\")\n\t\t\t.where(\"history.deleted_at = 0\")\n\t\t\t.filters(filterOptions)\n\t\t\t.orderBy(\n\t\t\t\torderBy = when (order) {\n\t\t\t\t\tListSortOrder.LAST_READ -> \"history.updated_at DESC\"\n\t\t\t\t\tListSortOrder.LONG_AGO_READ -> \"history.updated_at ASC\"\n\t\t\t\t\tListSortOrder.NEWEST -> \"history.created_at DESC\"\n\t\t\t\t\tListSortOrder.OLDEST -> \"history.created_at ASC\"\n\t\t\t\t\tListSortOrder.PROGRESS -> \"history.percent DESC\"\n\t\t\t\t\tListSortOrder.UNREAD -> \"history.percent ASC\"\n\t\t\t\t\tListSortOrder.ALPHABETIC -> \"manga.title\"\n\t\t\t\t\tListSortOrder.ALPHABETIC_REVERSE -> \"manga.title DESC\"\n\t\t\t\t\tListSortOrder.NEW_CHAPTERS -> \"IFNULL((SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC\"\n\t\t\t\t\tListSortOrder.UPDATED -> \"IFNULL((SELECT last_chapter_date FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC\"\n\t\t\t\t\telse -> throw IllegalArgumentException(\"Sort order $order is not supported\")\n\t\t\t\t},\n\t\t\t)\n\t\t\t.groupBy(\"history.manga_id\")\n\t\t\t.limit(limit)\n\t\t\t.build(),\n\t)\n\n\t@Query(\"SELECT manga_id FROM history WHERE deleted_at = 0\")\n\tabstract suspend fun findAllIds(): LongArray\n\n\t@Query(\n\t\t\"\"\"SELECT tags.* FROM tags\n\t\tLEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id\n\t\tINNER JOIN history ON history.manga_id = manga_tags.manga_id\n\t\tWHERE history.deleted_at = 0\n\t\tGROUP BY manga_tags.tag_id \n\t\tORDER BY COUNT(manga_tags.manga_id) DESC \n\t\tLIMIT :limit\"\"\",\n\t)\n\tabstract suspend fun findPopularTags(limit: Int): List<TagEntity>\n\n\t@Query(\"SELECT manga.source AS count FROM history LEFT JOIN manga ON manga.manga_id = history.manga_id GROUP BY manga.source ORDER BY COUNT(manga.source) DESC LIMIT :limit\")\n\tabstract suspend fun findPopularSources(limit: Int): List<String>\n\n\t@Query(\"SELECT * FROM history WHERE manga_id = :id AND deleted_at = 0\")\n\tabstract suspend fun find(id: Long): HistoryEntity?\n\n\t@Query(\"SELECT * FROM history WHERE manga_id = :id AND deleted_at = 0\")\n\tabstract fun observe(id: Long): Flow<HistoryEntity?>\n\n\t@Query(\"SELECT COUNT(*) FROM history WHERE deleted_at = 0\")\n\tabstract fun observeCount(): Flow<Int>\n\n\t@Query(\"SELECT COUNT(*) FROM history WHERE deleted_at = 0\")\n\tabstract suspend fun getCount(): Int\n\n\t@Query(\"SELECT percent FROM history WHERE manga_id = :id AND deleted_at = 0\")\n\tabstract suspend fun findProgress(id: Long): Float?\n\n\tfun dump(): Flow<HistoryWithManga> = flow {\n\t\tval window = 10\n\t\tvar offset = 0\n\t\twhile (currentCoroutineContext().isActive) {\n\t\t\tval list = findAll(offset, window)\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset += window\n\t\t\tlist.forEach { emit(it) }\n\t\t}\n\t}\n\n\t@Insert(onConflict = OnConflictStrategy.IGNORE)\n\tabstract suspend fun insert(entity: HistoryEntity): Long\n\n\t@Query(\n\t\t\"UPDATE history SET page = :page, chapter_id = :chapterId, scroll = :scroll, percent = :percent, updated_at = :updatedAt, chapters = :chapters, deleted_at = 0 WHERE manga_id = :mangaId\",\n\t)\n\tabstract suspend fun update(\n\t\tmangaId: Long,\n\t\tpage: Int,\n\t\tchapterId: Long,\n\t\tscroll: Float,\n\t\tpercent: Float,\n\t\tchapters: Int,\n\t\tupdatedAt: Long,\n\t): Int\n\n\tsuspend fun delete(mangaId: Long) = setDeletedAt(mangaId, System.currentTimeMillis())\n\n\tsuspend fun recover(mangaId: Long) = setDeletedAt(mangaId, 0L)\n\n\t@Query(\"DELETE FROM history WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime\")\n\tabstract suspend fun gc(maxDeletionTime: Long)\n\n\tsuspend fun deleteAfter(minDate: Long) = setDeletedAtAfter(minDate, System.currentTimeMillis())\n\n\tsuspend fun deleteNotFavorite() = setDeletedAtNotFavorite(System.currentTimeMillis())\n\n\tsuspend fun clear() = setDeletedAtAfter(0L, System.currentTimeMillis())\n\n\tsuspend fun update(entity: HistoryEntity) = update(\n\t\tmangaId = entity.mangaId,\n\t\tpage = entity.page,\n\t\tchapterId = entity.chapterId,\n\t\tscroll = entity.scroll,\n\t\tpercent = entity.percent,\n\t\tchapters = entity.chaptersCount,\n\t\tupdatedAt = entity.updatedAt,\n\t)\n\n\t@Transaction\n\topen suspend fun upsert(entity: HistoryEntity): Boolean {\n\t\treturn if (update(entity) == 0) {\n\t\t\tinsert(entity)\n\t\t\ttrue\n\t\t} else false\n\t}\n\n\t@Transaction\n\topen suspend fun upsert(entities: Iterable<HistoryEntity>) {\n\t\tfor (e in entities) {\n\t\t\tif (update(e) == 0) {\n\t\t\t\tinsert(e)\n\t\t\t}\n\t\t}\n\t}\n\n\t@Query(\"UPDATE history SET deleted_at = :deletedAt WHERE manga_id = :mangaId\")\n\tprotected abstract suspend fun setDeletedAt(mangaId: Long, deletedAt: Long)\n\n\t@Query(\"UPDATE history SET deleted_at = :deletedAt WHERE created_at >= :minDate AND deleted_at = 0\")\n\tprotected abstract suspend fun setDeletedAtAfter(minDate: Long, deletedAt: Long)\n\n\t@Query(\"UPDATE history SET deleted_at = :deletedAt WHERE deleted_at = 0 AND NOT EXISTS(SELECT * FROM favourites WHERE history.manga_id = favourites.manga_id)\")\n\tprotected abstract suspend fun setDeletedAtNotFavorite(deletedAt: Long)\n\n\t@Transaction\n\t@RawQuery(observedEntities = [HistoryEntity::class])\n\tprotected abstract fun observeAllImpl(query: SupportSQLiteQuery): Flow<List<HistoryWithManga>>\n\n\toverride fun getCondition(option: ListFilterOption): String? = when (option) {\n\t\tis ListFilterOption.Favorite -> \"EXISTS(SELECT * FROM favourites WHERE history.manga_id = favourites.manga_id AND category_id = ${option.category.id})\"\n\t\tListFilterOption.Macro.COMPLETED -> \"percent >= $PROGRESS_COMPLETED\"\n\t\tListFilterOption.Macro.NEW_CHAPTERS -> \"(SELECT chapters_new FROM tracks WHERE tracks.manga_id = history.manga_id) > 0\"\n\t\tListFilterOption.Macro.FAVORITE -> \"EXISTS(SELECT * FROM favourites WHERE history.manga_id = favourites.manga_id)\"\n\t\tListFilterOption.Macro.NSFW -> \"manga.nsfw = 1\"\n\t\tis ListFilterOption.Tag -> \"EXISTS(SELECT * FROM manga_tags WHERE history.manga_id = manga_tags.manga_id AND tag_id = ${option.tagId})\"\n\t\tListFilterOption.Downloaded -> \"EXISTS(SELECT * FROM local_index WHERE local_index.manga_id = history.manga_id)\"\n\t\tis ListFilterOption.Source -> \"manga.source = ${sqlEscapeString(option.mangaSource.name)}\"\n\t\telse -> null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryEntity.kt",
    "content": "package org.koitharu.kotatsu.history.data\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.TABLE_HISTORY\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\n@Entity(\n\ttableName = TABLE_HISTORY,\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t],\n)\ndata class HistoryEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"manga_id\") val mangaId: Long,\n\t@ColumnInfo(name = \"created_at\") val createdAt: Long,\n\t@ColumnInfo(name = \"updated_at\") val updatedAt: Long,\n\t@ColumnInfo(name = \"chapter_id\") val chapterId: Long,\n\t@ColumnInfo(name = \"page\") val page: Int,\n\t@ColumnInfo(name = \"scroll\") val scroll: Float,\n\t@ColumnInfo(name = \"percent\") val percent: Float,\n\t@ColumnInfo(name = \"deleted_at\") val deletedAt: Long,\n\t@ColumnInfo(name = \"chapters\") val chaptersCount: Int,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryLocalObserver.kt",
    "content": "package org.koitharu.kotatsu.history.data\n\nimport dagger.Reusable\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.history.domain.model.MangaWithHistory\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndex\nimport org.koitharu.kotatsu.local.domain.LocalObserveMapper\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\n@Reusable\nclass HistoryLocalObserver @Inject constructor(\n\tlocalMangaIndex: LocalMangaIndex,\n\tprivate val db: MangaDatabase,\n) : LocalObserveMapper<HistoryWithManga, MangaWithHistory>(localMangaIndex) {\n\n\tfun observeAll(\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t) = db.getHistoryDao().observeAll(order, filterOptions, limit).mapToLocal()\n\n\toverride fun toManga(e: HistoryWithManga) = e.manga.toManga(e.tags.toMangaTags(), null)\n\n\toverride fun toResult(e: HistoryWithManga, manga: Manga) = MangaWithHistory(\n\t\tmanga = manga,\n\t\thistory = e.history.toMangaHistory(),\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryRepository.kt",
    "content": "package org.koitharu.kotatsu.history.data\n\nimport androidx.room.withTransaction\nimport dagger.Reusable\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toEntity\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaList\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.core.db.entity.toMangaTagsList\nimport org.koitharu.kotatsu.core.model.MangaHistory\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.model.toMangaSources\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode\nimport org.koitharu.kotatsu.core.ui.util.ReversibleHandle\nimport org.koitharu.kotatsu.core.util.ext.mapItems\nimport org.koitharu.kotatsu.history.domain.model.MangaWithHistory\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.levenshteinDistance\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.tryScrobble\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.tracker.domain.CheckNewChaptersUseCase\nimport javax.inject.Inject\nimport javax.inject.Provider\n\n@Reusable\nclass HistoryRepository @Inject constructor(\n\tprivate val db: MangaDatabase,\n\tprivate val settings: AppSettings,\n\tprivate val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,\n\tprivate val mangaRepository: MangaDataRepository,\n\tprivate val localObserver: HistoryLocalObserver,\n\tprivate val newChaptersUseCaseProvider: Provider<CheckNewChaptersUseCase>,\n) {\n\n\tsuspend fun getList(offset: Int, limit: Int): List<Manga> {\n\t\tval entities = db.getHistoryDao().findAll(offset, limit)\n\t\treturn entities.map { it.toManga() }\n\t}\n\n\tsuspend fun search(query: String, kind: SearchKind, limit: Int): List<Manga> {\n\t\tval dao = db.getHistoryDao()\n\t\tval q = \"%$query%\"\n\t\tval entities = when (kind) {\n\t\t\tSearchKind.SIMPLE,\n\t\t\tSearchKind.TITLE -> dao.searchByTitle(q, limit).sortedBy { it.manga.title.levenshteinDistance(query) }\n\n\t\t\tSearchKind.AUTHOR -> dao.searchByAuthor(q, limit)\n\t\t\tSearchKind.TAG -> dao.searchByTag(q, limit)\n\t\t}\n\t\treturn entities.toMangaList()\n\t}\n\n\tsuspend fun getLastOrNull(): Manga? {\n\t\tval entity = db.getHistoryDao().findAll(0, 1).firstOrNull() ?: return null\n\t\treturn entity.toManga()\n\t}\n\n\tfun observeLast(): Flow<Manga?> {\n\t\treturn db.getHistoryDao().observeAll(1).map {\n\t\t\tval first = it.firstOrNull()\n\t\t\tfirst?.toManga()\n\t\t}\n\t}\n\n\tfun observeAll(): Flow<List<Manga>> {\n\t\treturn db.getHistoryDao().observeAll().mapItems {\n\t\t\tit.toManga()\n\t\t}\n\t}\n\n\tfun observeAll(limit: Int): Flow<List<Manga>> {\n\t\treturn db.getHistoryDao().observeAll(limit).mapItems {\n\t\t\tit.toManga()\n\t\t}\n\t}\n\n\tfun observeAllWithHistory(\n\t\torder: ListSortOrder,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t\tlimit: Int\n\t): Flow<List<MangaWithHistory>> {\n\t\tif (ListFilterOption.Downloaded in filterOptions) {\n\t\t\treturn localObserver.observeAll(order, filterOptions, limit)\n\t\t}\n\t\treturn db.getHistoryDao().observeAll(order, filterOptions, limit).mapItems {\n\t\t\tMangaWithHistory(\n\t\t\t\tit.toManga(),\n\t\t\t\tit.history.toMangaHistory(),\n\t\t\t)\n\t\t}\n\t}\n\n\tfun observeOne(id: Long): Flow<MangaHistory?> {\n\t\treturn db.getHistoryDao().observe(id).map {\n\t\t\tit?.toMangaHistory()\n\t\t}\n\t}\n\n\tsuspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Int, percent: Float, force: Boolean) {\n\t\tif (!force && shouldSkip(manga)) {\n\t\t\treturn\n\t\t}\n\t\tassert(manga.chapters != null)\n\t\tdb.withTransaction {\n\t\t\tmangaRepository.storeManga(manga, replaceExisting = true)\n\t\t\tval branch = manga.chapters?.findById(chapterId)?.branch\n\t\t\tdb.getHistoryDao().upsert(\n\t\t\t\tHistoryEntity(\n\t\t\t\t\tmangaId = manga.id,\n\t\t\t\t\tcreatedAt = System.currentTimeMillis(),\n\t\t\t\t\tupdatedAt = System.currentTimeMillis(),\n\t\t\t\t\tchapterId = chapterId,\n\t\t\t\t\tpage = page,\n\t\t\t\t\tscroll = scroll.toFloat(), // we migrate to int, but decide to not update database\n\t\t\t\t\tpercent = percent,\n\t\t\t\t\tchaptersCount = manga.chapters?.count { it.branch == branch } ?: 0,\n\t\t\t\t\tdeletedAt = 0L,\n\t\t\t\t),\n\t\t\t)\n\t\t\tnewChaptersUseCaseProvider.get()(manga, chapterId)\n\t\t\tscrobblers.forEach { it.tryScrobble(manga, chapterId) }\n\t\t}\n\t}\n\n\tsuspend fun getOne(manga: Manga): MangaHistory? {\n\t\treturn db.getHistoryDao().find(manga.id)?.recoverIfNeeded(manga)?.toMangaHistory()\n\t}\n\n\tsuspend fun getProgress(mangaId: Long, mode: ProgressIndicatorMode): ReadingProgress? {\n\t\tval entity = db.getHistoryDao().find(mangaId) ?: return null\n\t\tval fixedPercent = if (ReadingProgress.isCompleted(entity.percent)) 1f else entity.percent\n\t\treturn ReadingProgress(\n\t\t\tpercent = fixedPercent,\n\t\t\ttotalChapters = entity.chaptersCount,\n\t\t\tmode = mode,\n\t\t).takeIf { it.isValid() }\n\t}\n\n\tsuspend fun clear() {\n\t\tdb.getHistoryDao().clear()\n\t}\n\n\tsuspend fun delete(manga: Manga) = db.withTransaction {\n\t\tdb.getHistoryDao().delete(manga.id)\n\t\tmangaRepository.gcChaptersCache()\n\t}\n\n\tsuspend fun deleteAfter(minDate: Long) = db.withTransaction {\n\t\tdb.getHistoryDao().deleteAfter(minDate)\n\t\tmangaRepository.gcChaptersCache()\n\t}\n\n\tsuspend fun deleteNotFavorite() = db.withTransaction {\n\t\tdb.getHistoryDao().deleteNotFavorite()\n\t\tmangaRepository.gcChaptersCache()\n\t}\n\n\tsuspend fun delete(ids: Collection<Long>): ReversibleHandle {\n\t\tdb.withTransaction {\n\t\t\tfor (id in ids) {\n\t\t\t\tdb.getHistoryDao().delete(id)\n\t\t\t}\n\t\t\tmangaRepository.gcChaptersCache()\n\t\t}\n\t\treturn ReversibleHandle {\n\t\t\trecover(ids)\n\t\t}\n\t}\n\n\t/**\n\t * Try to replace one manga with another one\n\t * Useful for replacing saved manga on deleting it with remote source\n\t */\n\tsuspend fun deleteOrSwap(manga: Manga, alternative: Manga?) {\n\t\tif (alternative == null || db.getMangaDao().update(alternative.toEntity()) <= 0) {\n\t\t\tdelete(manga)\n\t\t}\n\t}\n\n\tsuspend fun getPopularTags(limit: Int): List<MangaTag> {\n\t\treturn db.getHistoryDao().findPopularTags(limit).toMangaTagsList()\n\t}\n\n\tsuspend fun getPopularSources(limit: Int): List<MangaSource> {\n\t\treturn db.getHistoryDao().findPopularSources(limit).toMangaSources()\n\t}\n\n\tfun shouldSkip(manga: Manga): Boolean = settings.isIncognitoModeEnabled(manga.isNsfw())\n\n\tfun observeShouldSkip(manga: Manga): Flow<Boolean> {\n\t\treturn settings.observe(AppSettings.KEY_INCOGNITO_MODE, AppSettings.KEY_INCOGNITO_NSFW)\n\t\t\t.map { shouldSkip(manga) }\n\t\t\t.distinctUntilChanged()\n\t}\n\n\tprivate suspend fun recover(ids: Collection<Long>) {\n\t\tdb.withTransaction {\n\t\t\tfor (id in ids) {\n\t\t\t\tdb.getHistoryDao().recover(id)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun HistoryEntity.recoverIfNeeded(manga: Manga): HistoryEntity {\n\t\tval chapters = manga.chapters\n\t\tif (manga.isLocal || chapters.isNullOrEmpty() || chapters.findById(chapterId) != null) {\n\t\t\treturn this\n\t\t}\n\t\tval newChapterId = chapters.getOrNull(\n\t\t\t(chapters.size * percent).toInt(),\n\t\t)?.id ?: return this\n\t\tval newEntity = copy(chapterId = newChapterId)\n\t\tdb.getHistoryDao().update(newEntity)\n\t\treturn newEntity\n\t}\n\n\tprivate fun HistoryWithManga.toManga() = manga.toManga(tags.toMangaTags(), null)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/data/HistoryWithManga.kt",
    "content": "package org.koitharu.kotatsu.history.data\n\nimport androidx.room.Embedded\nimport androidx.room.Junction\nimport androidx.room.Relation\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaTagsEntity\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\nclass HistoryWithManga(\n\t@Embedded val history: HistoryEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"manga_id\"\n\t)\n\tval manga: MangaEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"tag_id\",\n\t\tassociateBy = Junction(MangaTagsEntity::class)\n\t)\n\tval tags: List<TagEntity>,\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt",
    "content": "package org.koitharu.kotatsu.history.domain\n\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.MangaListQuickFilter\nimport javax.inject.Inject\n\nclass HistoryListQuickFilter @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val repository: HistoryRepository,\n\tnetworkState: NetworkState,\n) : MangaListQuickFilter(settings) {\n\n\tinit {\n\t\tsetFilterOption(ListFilterOption.Downloaded, !networkState.value)\n\t}\n\n\toverride suspend fun getAvailableFilterOptions(): List<ListFilterOption> = buildList {\n\t\tadd(ListFilterOption.Downloaded)\n\t\tif (settings.isTrackerEnabled) {\n\t\t\tadd(ListFilterOption.Macro.NEW_CHAPTERS)\n\t\t}\n\t\tadd(ListFilterOption.Macro.COMPLETED)\n\t\tadd(ListFilterOption.Macro.FAVORITE)\n\t\tadd(ListFilterOption.NOT_FAVORITE)\n\t\tif (!settings.isNsfwContentDisabled) {\n\t\t\tadd(ListFilterOption.Macro.NSFW)\n\t\t}\n\t\trepository.getPopularTags(3).mapTo(this) {\n\t\t\tListFilterOption.Tag(it)\n\t\t}\n\t\trepository.getPopularSources(3).mapTo(this) {\n\t\t\tListFilterOption.Source(it)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryUpdateUseCase.kt",
    "content": "package org.koitharu.kotatsu.history.domain\n\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.NonCancellable\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport javax.inject.Inject\n\nclass HistoryUpdateUseCase @Inject constructor(\n\tprivate val historyRepository: HistoryRepository,\n) {\n\n\tsuspend operator fun invoke(manga: Manga, readerState: ReaderState, percent: Float) {\n\t\thistoryRepository.addOrUpdate(\n\t\t\tmanga = manga,\n\t\t\tchapterId = readerState.chapterId,\n\t\t\tpage = readerState.page,\n\t\t\tscroll = readerState.scroll,\n\t\t\tpercent = percent,\n\t\t\tforce = false,\n\t\t)\n\t}\n\n\tfun invokeAsync(\n\t\tmanga: Manga,\n\t\treaderState: ReaderState,\n\t\tpercent: Float\n\t) = processLifecycleScope.launch(Dispatchers.Default, CoroutineStart.ATOMIC) {\n\t\trunCatchingCancellable {\n\t\t\twithContext(NonCancellable) {\n\t\t\t\tinvoke(manga, readerState, percent)\n\t\t\t}\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/domain/MarkAsReadUseCase.kt",
    "content": "package org.koitharu.kotatsu.history.domain\n\nimport dagger.Reusable\nimport kotlinx.coroutines.joinAll\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.supervisorScope\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\n@Reusable\nclass MarkAsReadUseCase @Inject constructor(\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\n\tsuspend operator fun invoke(manga: Manga) {\n\t\tval repo = mangaRepositoryFactory.create(manga.source)\n\t\tval details = if (manga.chapters.isNullOrEmpty()) {\n\t\t\trepo.getDetails(manga)\n\t\t} else {\n\t\t\tmanga\n\t\t}\n\t\tval lastChapter = checkNotNull(details.chapters).last()\n\t\tval pages = repo.getPages(lastChapter)\n\t\thistoryRepository.addOrUpdate(\n\t\t\tmanga = details,\n\t\t\tchapterId = lastChapter.id,\n\t\t\tpage = pages.lastIndex,\n\t\t\tscroll = 0,\n\t\t\tpercent = 1f,\n\t\t\tforce = true,\n\t\t)\n\t}\n\n\tsuspend operator fun invoke(manga: Collection<Manga>) {\n\t\twhen (manga.size) {\n\t\t\t0 -> Unit\n\t\t\t1 -> invoke(manga.first())\n\t\t\telse -> supervisorScope {\n\t\t\t\tmanga.map {\n\t\t\t\t\tlaunch {\n\t\t\t\t\t\tinvoke(it)\n\t\t\t\t\t}\n\t\t\t\t}.joinAll()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/domain/model/MangaWithHistory.kt",
    "content": "package org.koitharu.kotatsu.history.domain.model\n\nimport org.koitharu.kotatsu.core.model.MangaHistory\nimport org.koitharu.kotatsu.parsers.model.Manga\n\ndata class MangaWithHistory(\n\tval manga: Manga,\n\tval history: MangaHistory\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryActivity.kt",
    "content": "package org.koitharu.kotatsu.history.ui\n\nimport org.koitharu.kotatsu.core.ui.FragmentContainerActivity\n\nclass HistoryActivity : FragmentContainerActivity(HistoryListFragment::class.java)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListAdapter.kt",
    "content": "package org.koitharu.kotatsu.history.ui\n\nimport android.content.Context\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListListener\nimport org.koitharu.kotatsu.list.ui.size.ItemSizeResolver\n\nclass HistoryListAdapter(\n\tlistener: MangaListListener,\n\tsizeResolver: ItemSizeResolver,\n) : MangaListAdapter(listener, sizeResolver), FastScroller.SectionIndexer {\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn findHeader(position)?.getText(context)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt",
    "content": "package org.koitharu.kotatsu.history.ui\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.appcompat.view.ActionMode\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.databinding.FragmentListBinding\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\nimport org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver\n\n@AndroidEntryPoint\nclass HistoryListFragment : MangaListFragment() {\n\n\toverride val viewModel by viewModels<HistoryListViewModel>()\n\toverride val isSwipeRefreshEnabled = false\n\n\toverride fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tRecyclerScrollKeeper(binding.recyclerView).attach()\n\t\taddMenuProvider(HistoryListMenuProvider(binding.root.context, router, viewModel))\n\t\tviewModel.isStatsEnabled.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))\n\t}\n\n\toverride fun onScrolledToEnd() = viewModel.requestMoreItems()\n\n\toverride fun onEmptyActionClick() = viewModel.clearFilter()\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_history, menu)\n\t\treturn super.onCreateActionMode(controller, menuInflater, menu)\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_remove -> {\n\t\t\t\tviewModel.removeFromHistory(selectedItemsIds)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_mark_current -> {\n\t\t\t\tval itemsSnapshot = selectedItems\n\t\t\t\tbuildAlertDialog(context ?: return false, isCentered = true) {\n\t\t\t\t\tsetTitle(item.title)\n\t\t\t\t\tsetIcon(item.icon)\n\t\t\t\t\tsetMessage(R.string.mark_as_completed_prompt)\n\t\t\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\t\t\tsetPositiveButton(android.R.string.ok) { _, _ ->\n\t\t\t\t\t\tviewModel.markAsRead(itemsSnapshot)\n\t\t\t\t\t\tmode?.finish()\n\t\t\t\t\t}\n\t\t\t\t}.show()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onActionItemClicked(controller, mode, item)\n\t\t}\n\t}\n\n\toverride fun onCreateAdapter() = HistoryListAdapter(\n\t\tthis,\n\t\tDynamicItemSizeResolver(resources, viewLifecycleOwner, settings, adjustWidth = false),\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.history.ui\n\nimport android.content.Context\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport java.time.Instant\nimport java.time.LocalDate\nimport java.time.ZoneId\nimport java.time.temporal.ChronoUnit\n\nclass HistoryListMenuProvider(\n\tprivate val context: Context,\n\tprivate val router: AppRouter,\n\tprivate val viewModel: HistoryListViewModel,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_history, menu)\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tmenu.findItem(R.id.action_stats)?.isVisible = viewModel.isStatsEnabled.value\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\treturn when (menuItem.itemId) {\n\t\t\tR.id.action_clear_history -> {\n\t\t\t\tshowClearHistoryDialog()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_stats -> {\n\t\t\t\trouter.openStatistic()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\tprivate fun showClearHistoryDialog() {\n\t\tval selectionListener = RememberSelectionDialogListener(1)\n\t\tbuildAlertDialog(context, isCentered = true) {\n\t\t\tsetTitle(R.string.clear_history)\n\t\t\tsetSingleChoiceItems(\n\t\t\t\tarrayOf(\n\t\t\t\t\tcontext.getString(R.string.last_2_hours),\n\t\t\t\t\tcontext.getString(R.string.today),\n\t\t\t\t\tcontext.getString(R.string.not_in_favorites),\n\t\t\t\t\tcontext.getString(R.string.clear_all_history),\n\t\t\t\t),\n\t\t\t\tselectionListener.selection,\n\t\t\t\tselectionListener,\n\t\t\t)\n\t\t\tsetIcon(R.drawable.ic_delete_all)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.clear) { _, _ ->\n\t\t\t\twhen (selectionListener.selection) {\n\t\t\t\t\t0 -> viewModel.clearHistory(Instant.now().minus(2, ChronoUnit.HOURS))\n\t\t\t\t\t1 -> viewModel.clearHistory(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant())\n\t\t\t\t\t2 -> viewModel.removeNotFavorite()\n\t\t\t\t\t3 -> viewModel.clearHistory(null)\n\t\t\t\t}\n\t\t\t}\n\t\t}.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt",
    "content": "package org.koitharu.kotatsu.history.ui\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.MangaHistory\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.calculateTimeAgo\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.flattenLatest\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.history.domain.HistoryListQuickFilter\nimport org.koitharu.kotatsu.history.domain.MarkAsReadUseCase\nimport org.koitharu.kotatsu.history.domain.model.MangaWithHistory\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.domain.QuickFilterListener\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.InfoModel\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.time.Instant\nimport java.util.concurrent.atomic.AtomicBoolean\nimport javax.inject.Inject\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport kotlinx.coroutines.flow.SharedFlow\n\nprivate const val PAGE_SIZE = 16\n\n@HiltViewModel\nclass HistoryListViewModel @Inject constructor(\n\tprivate val repository: HistoryRepository,\n\tsettings: AppSettings,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val markAsReadUseCase: MarkAsReadUseCase,\n\tprivate val quickFilter: HistoryListQuickFilter,\n\tmangaDataRepository: MangaDataRepository,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {\n\n\tprivate val sortOrder: StateFlow<ListSortOrder> = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.IO,\n\t\tkey = AppSettings.KEY_HISTORY_ORDER,\n\t\tvalueProducer = { historySortOrder },\n\t)\n\n\toverride val listMode = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_LIST_MODE_HISTORY,\n\t\tvalueProducer = { historyListMode },\n\t)\n\n\tprivate val isGroupingEnabled = settings.observeAsFlow(\n\t\tkey = AppSettings.KEY_HISTORY_GROUPING,\n\t\tvalueProducer = { isHistoryGroupingEnabled },\n\t).combine(sortOrder) { g, s ->\n\t\tg && s.isGroupingSupported()\n\t}\n\n\tprivate val limit = MutableStateFlow(PAGE_SIZE)\n\tprivate val isPaginationReady = AtomicBoolean(false)\n\n\tval isStatsEnabled = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_STATS_ENABLED,\n\t\tvalueProducer = { isStatsEnabled },\n\t)\n\n\toverride val content = combine(\n\t\tquickFilter.appliedOptions,\n\t\tobserveHistory(),\n\t\tisGroupingEnabled,\n\t\tobserveListModeWithTriggers(),\n\t\tsettings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled },\n\t) { filters, list, grouped, mode, incognito ->\n\t\tmapList(list, grouped, mode, filters, incognito)\n\t}.distinctUntilChanged().onEach {\n\t\tisPaginationReady.set(true)\n\t}.catch { e ->\n\t\temit(listOf(e.toErrorState(canRetry = false)))\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\toverride fun onRefresh() = Unit\n\n\toverride fun onRetry() = Unit\n\n\tfun clearHistory(minDate: Instant?) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval stringRes = if (minDate == null) {\n\t\t\t\trepository.clear()\n\t\t\t\tR.string.history_cleared\n\t\t\t} else {\n\t\t\t\trepository.deleteAfter(minDate.toEpochMilli())\n\t\t\t\tR.string.removed_from_history\n\t\t\t}\n\t\t\tonActionDone.call(ReversibleAction(stringRes, null))\n\t\t}\n\t}\n\n\tfun removeNotFavorite() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.deleteNotFavorite()\n\t\t\tonActionDone.call(ReversibleAction(R.string.removed_from_history, null))\n\t\t}\n\t}\n\n\tfun removeFromHistory(ids: Set<Long>) {\n\t\tif (ids.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval handle = repository.delete(ids)\n\t\t\tonActionDone.call(ReversibleAction(R.string.removed_from_history, handle))\n\t\t}\n\t}\n\n\tfun markAsRead(items: Set<Manga>) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tmarkAsReadUseCase(items)\n\t\t}\n\t}\n\n\tfun requestMoreItems() {\n\t\tif (isPaginationReady.compareAndSet(true, false)) {\n\t\t\tlimit.value += PAGE_SIZE\n\t\t}\n\t}\n\n\tprivate fun observeHistory() = combine(\n\t\tsortOrder,\n\t\tquickFilter.appliedOptions.combineWithSettings(),\n\t\tlimit,\n\t) { order, filters, limit ->\n\t\tisPaginationReady.set(false)\n\t\trepository.observeAllWithHistory(order, filters, limit)\n\t}.flattenLatest()\n\n\tprivate suspend fun mapList(\n\t\tlist: List<MangaWithHistory>,\n\t\tgrouped: Boolean,\n\t\tmode: ListMode,\n\t\tfilters: Set<ListFilterOption>,\n\t\tisIncognito: Boolean,\n\t): List<ListModel> {\n\t\tif (list.isEmpty()) {\n\t\t\treturn if (filters.isEmpty()) {\n\t\t\t\tlistOf(getEmptyState(hasFilters = false))\n\t\t\t} else {\n\t\t\t\tlistOfNotNull(quickFilter.filterItem(filters), getEmptyState(hasFilters = true))\n\t\t\t}\n\t\t}\n\t\tval result = ArrayList<ListModel>((if (grouped) (list.size * 1.4).toInt() else list.size) + 2)\n\t\tquickFilter.filterItem(filters)?.let(result::add)\n\t\tif (isIncognito) {\n\t\t\tresult += InfoModel(\n\t\t\t\tkey = AppSettings.KEY_INCOGNITO_MODE,\n\t\t\t\ttitle = R.string.incognito_mode,\n\t\t\t\ttext = R.string.incognito_mode_hint,\n\t\t\t\ticon = R.drawable.ic_incognito,\n\t\t\t)\n\t\t}\n\t\tval order = sortOrder.value\n\t\tvar prevHeader: ListHeader? = null\n\t\tvar isEmpty = true\n\t\tfor ((manga, history) in list) {\n\t\t\tisEmpty = false\n\t\t\tif (grouped) {\n\t\t\t\tval header = history.header(order)\n\t\t\t\tif (header != prevHeader) {\n\t\t\t\t\tif (header != null) {\n\t\t\t\t\t\tresult += header\n\t\t\t\t\t}\n\t\t\t\t\tprevHeader = header\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult += mangaListMapper.toListModel(manga, mode)\n\t\t}\n\t\tif (filters.isNotEmpty() && isEmpty) {\n\t\t\tresult += getEmptyState(hasFilters = true)\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate fun MangaHistory.header(order: ListSortOrder): ListHeader? = when (order) {\n\t\tListSortOrder.LAST_READ,\n\t\tListSortOrder.LONG_AGO_READ -> calculateTimeAgo(updatedAt)?.let {\n\t\t\tListHeader(it)\n\t\t} ?: ListHeader(R.string.unknown)\n\n\t\tListSortOrder.OLDEST,\n\t\tListSortOrder.NEWEST -> calculateTimeAgo(createdAt)?.let {\n\t\t\tListHeader(it)\n\t\t} ?: ListHeader(R.string.unknown)\n\n\t\tListSortOrder.UNREAD,\n\t\tListSortOrder.PROGRESS -> ListHeader(\n\t\t\twhen {\n\t\t\t\tReadingProgress.isCompleted(percent) -> R.string.status_completed\n\t\t\t\tpercent in 0f..0.01f -> R.string.status_planned\n\t\t\t\tpercent in 0f..1f -> R.string.status_reading\n\t\t\t\telse -> R.string.unknown\n\t\t\t},\n\t\t)\n\n\t\tListSortOrder.ALPHABETIC,\n\t\tListSortOrder.ALPHABETIC_REVERSE,\n\t\tListSortOrder.RELEVANCE,\n\t\tListSortOrder.NEW_CHAPTERS,\n\t\tListSortOrder.UPDATED,\n\t\tListSortOrder.RATING -> null\n\t}\n\n\tprivate fun getEmptyState(hasFilters: Boolean) = if (hasFilters) {\n\t\tEmptyState(\n\t\t\ticon = R.drawable.ic_empty_history,\n\t\t\ttextPrimary = R.string.nothing_found,\n\t\t\ttextSecondary = R.string.text_empty_holder_secondary_filtered,\n\t\t\tactionStringRes = R.string.reset_filter,\n\t\t)\n\t} else {\n\t\tEmptyState(\n\t\t\ticon = R.drawable.ic_empty_history,\n\t\t\ttextPrimary = R.string.text_history_holder_primary,\n\t\t\ttextSecondary = R.string.text_history_holder_secondary,\n\t\t\tactionStringRes = 0,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/ui/util/ReadingProgressDrawable.kt",
    "content": "package org.koitharu.kotatsu.history.ui.util\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport androidx.annotation.StyleRes\nimport androidx.appcompat.content.res.AppCompatResources\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.image.PaintDrawable\nimport org.koitharu.kotatsu.core.util.ext.hasFocusStateSpecified\nimport org.koitharu.kotatsu.core.util.ext.scale\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\n\nclass ReadingProgressDrawable(\n\tcontext: Context,\n\t@StyleRes styleResId: Int,\n) : PaintDrawable() {\n\n\toverride val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG)\n\tprivate val checkDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_check)\n\tprivate val lineColor: ColorStateList\n\tprivate val outlineColor: ColorStateList\n\tprivate val backgroundColor: ColorStateList\n\tprivate val textColor: ColorStateList\n\tprivate val textBounds = Rect()\n\tprivate val tempRect = Rect()\n\tprivate val desiredHeight: Int\n\tprivate val desiredWidth: Int\n\tprivate val autoFitTextSize: Boolean\n\n\tprivate var currentLineColor: Int = Color.TRANSPARENT\n\tprivate var currentOutlineColor: Int = Color.TRANSPARENT\n\tprivate var currentBackgroundColor: Int = Color.TRANSPARENT\n\tprivate var currentTextColor: Int = Color.TRANSPARENT\n\tprivate var hasBackground: Boolean = false\n\tprivate var hasOutline: Boolean = false\n\tprivate var hasText: Boolean = false\n\n\n\tvar percent: Float = PROGRESS_NONE\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tinvalidateSelf()\n\t\t}\n\n\tvar text = \"\"\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tpaint.getTextBounds(text, 0, text.length, textBounds)\n\t\t\tinvalidateSelf()\n\t\t}\n\n\tinit {\n\t\tval ta = context.obtainStyledAttributes(styleResId, R.styleable.ProgressDrawable)\n\t\tdesiredHeight = ta.getDimensionPixelSize(R.styleable.ProgressDrawable_android_height, -1)\n\t\tdesiredWidth = ta.getDimensionPixelSize(R.styleable.ProgressDrawable_android_width, -1)\n\t\tautoFitTextSize = ta.getBoolean(R.styleable.ProgressDrawable_autoFitTextSize, false)\n\t\tlineColor = ta.getColorStateList(R.styleable.ProgressDrawable_android_strokeColor) ?: ColorStateList.valueOf(\n\t\t\tColor.BLACK,\n\t\t)\n\t\toutlineColor =\n\t\t\tta.getColorStateList(R.styleable.ProgressDrawable_outlineColor) ?: ColorStateList.valueOf(Color.TRANSPARENT)\n\t\tbackgroundColor = ta.getColorStateList(R.styleable.ProgressDrawable_android_fillColor)?.withAlpha(\n\t\t\t(255 * ta.getFloat(R.styleable.ProgressDrawable_android_fillAlpha, 0f)).toInt(),\n\t\t) ?: ColorStateList.valueOf(Color.TRANSPARENT)\n\t\ttextColor = ta.getColorStateList(R.styleable.ProgressDrawable_android_textColor) ?: lineColor\n\t\tpaint.strokeCap = Paint.Cap.ROUND\n\t\tpaint.textAlign = Paint.Align.CENTER\n\t\tpaint.textSize = ta.getDimension(R.styleable.ProgressDrawable_android_textSize, paint.textSize)\n\t\tpaint.strokeWidth = ta.getDimension(R.styleable.ProgressDrawable_strokeWidth, 1f)\n\t\tta.recycle()\n\t\tcheckDrawable?.setTintList(textColor)\n\t\tonStateChange(state)\n\t}\n\n\toverride fun onBoundsChange(bounds: Rect) {\n\t\tsuper.onBoundsChange(bounds)\n\t\tif (autoFitTextSize) {\n\t\t\tval innerWidth = bounds.width() - (paint.strokeWidth * 2f)\n\t\t\tpaint.textSize = getTextSizeForWidth(innerWidth, \"100%\")\n\t\t\tpaint.getTextBounds(text, 0, text.length, textBounds)\n\t\t\tinvalidateSelf()\n\t\t}\n\t}\n\n\toverride fun draw(canvas: Canvas) {\n\t\tif (percent < 0f) {\n\t\t\treturn\n\t\t}\n\t\tval cx = bounds.exactCenterX()\n\t\tval cy = bounds.exactCenterY()\n\t\tval radius = minOf(bounds.width(), bounds.height()) / 2f\n\t\tif (hasBackground) {\n\t\t\tpaint.style = Paint.Style.FILL\n\t\t\tpaint.color = currentBackgroundColor\n\t\t\tcanvas.drawCircle(cx, cy, radius, paint)\n\t\t}\n\t\tval innerRadius = radius - paint.strokeWidth / 2f\n\t\tpaint.style = Paint.Style.STROKE\n\t\tif (hasOutline) {\n\t\t\tpaint.color = currentOutlineColor\n\t\t\tcanvas.drawCircle(cx, cy, innerRadius, paint)\n\t\t}\n\t\tpaint.color = currentLineColor\n\t\tcanvas.drawArc(\n\t\t\tcx - innerRadius,\n\t\t\tcy - innerRadius,\n\t\t\tcx + innerRadius,\n\t\t\tcy + innerRadius,\n\t\t\t-90f,\n\t\t\t360f * percent,\n\t\t\tfalse,\n\t\t\tpaint,\n\t\t)\n\t\tif (hasText) {\n\t\t\tif (checkDrawable != null && ReadingProgress.isCompleted(percent)) {\n\t\t\t\ttempRect.set(bounds)\n\t\t\t\ttempRect.scale(0.6)\n\t\t\t\tcheckDrawable.bounds = tempRect\n\t\t\t\tcheckDrawable.draw(canvas)\n\t\t\t} else {\n\t\t\t\tpaint.style = Paint.Style.FILL\n\t\t\t\tpaint.color = currentTextColor\n\t\t\t\tval ty = bounds.height() / 2f + textBounds.height() / 2f - textBounds.bottom\n\t\t\t\tcanvas.drawText(text, cx, ty, paint)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun getIntrinsicHeight() = desiredHeight\n\n\toverride fun getIntrinsicWidth() = desiredWidth\n\n\toverride fun isStateful(): Boolean = lineColor.isStateful ||\n\t\toutlineColor.isStateful ||\n\t\tbackgroundColor.isStateful ||\n\t\ttextColor.isStateful ||\n\t\tcheckDrawable?.isStateful == true\n\n\t@RequiresApi(Build.VERSION_CODES.S)\n\toverride fun hasFocusStateSpecified(): Boolean = lineColor.hasFocusStateSpecified() ||\n\t\toutlineColor.hasFocusStateSpecified() ||\n\t\tbackgroundColor.hasFocusStateSpecified() ||\n\t\ttextColor.hasFocusStateSpecified() ||\n\t\tcheckDrawable?.hasFocusStateSpecified() == true\n\n\toverride fun onStateChange(state: IntArray): Boolean {\n\t\tval prevLineColor = currentLineColor\n\t\tcurrentLineColor = lineColor.getColorForState(state, lineColor.defaultColor)\n\t\tval prevOutlineColor = currentOutlineColor\n\t\tcurrentOutlineColor = outlineColor.getColorForState(state, outlineColor.defaultColor)\n\t\tval prevBackgroundColor = currentBackgroundColor\n\t\tcurrentBackgroundColor = backgroundColor.getColorForState(state, backgroundColor.defaultColor)\n\t\tval prevTextColor = currentTextColor\n\t\tcurrentTextColor = textColor.getColorForState(state, textColor.defaultColor)\n\t\thasBackground = Color.alpha(currentBackgroundColor) != 0\n\t\thasOutline = Color.alpha(currentOutlineColor) != 0\n\t\thasText = Color.alpha(currentTextColor) != 0 && paint.textSize > 0\n\t\treturn checkDrawable?.setState(state) == true ||\n\t\t\tprevLineColor != currentLineColor ||\n\t\t\tprevOutlineColor != currentOutlineColor ||\n\t\t\tprevBackgroundColor != currentBackgroundColor ||\n\t\t\tprevTextColor != currentTextColor\n\t}\n\n\tprivate fun getTextSizeForWidth(width: Float, text: String): Float {\n\t\tval testTextSize = 48f\n\t\tpaint.textSize = testTextSize\n\t\tpaint.getTextBounds(text, 0, text.length, tempRect)\n\t\treturn testTextSize * width / tempRect.width()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/history/ui/util/ReadingProgressView.kt",
    "content": "package org.koitharu.kotatsu.history.ui.util\n\nimport android.animation.Animator\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.graphics.Outline\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewOutlineProvider\nimport android.view.animation.AccelerateDecelerateInterpolator\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StyleRes\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.CHAPTERS_LEFT\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.CHAPTERS_READ\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.NONE\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.PERCENT_LEFT\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.PERCENT_READ\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\n\nclass ReadingProgressView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : View(context, attrs, defStyleAttr), ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {\n\n\tprivate val percentPattern = context.getString(R.string.percent_string_pattern)\n\tprivate var percentAnimator: ValueAnimator? = null\n\tprivate val animationDuration = context.getAnimationDuration(android.R.integer.config_shortAnimTime)\n\n\t@StyleRes\n\tprivate val drawableStyle: Int\n\n\tvar progress: ReadingProgress? = null\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tcancelAnimation()\n\t\t\tgetProgressDrawable().also {\n\t\t\t\tit.percent = value?.percent ?: PROGRESS_NONE\n\t\t\t\tit.text = when (value?.mode) {\n\t\t\t\t\tnull,\n\t\t\t\t\tNONE -> \"\"\n\n\t\t\t\t\tPERCENT_READ -> percentPattern.format(ReadingProgress.percentToString(value.percent))\n\t\t\t\t\tPERCENT_LEFT -> \"-\" + percentPattern.format(ReadingProgress.percentToString(value.percentLeft))\n\n\t\t\t\t\tCHAPTERS_READ -> value.chapters.toString()\n\t\t\t\t\tCHAPTERS_LEFT -> \"-\" + value.chaptersLeft.toString()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tinit {\n\t\tval ta = context.obtainStyledAttributes(attrs, R.styleable.ReadingProgressView, defStyleAttr, 0)\n\t\tdrawableStyle = ta.getResourceId(R.styleable.ReadingProgressView_progressStyle, R.style.ProgressDrawable)\n\t\tta.recycle()\n\t\toutlineProvider = OutlineProvider()\n\t\tif (isInEditMode) {\n\t\t\tprogress = ReadingProgress(\n\t\t\t\tpercent = 0.27f,\n\t\t\t\ttotalChapters = 20,\n\t\t\t\tmode = PERCENT_READ,\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun onDetachedFromWindow() {\n\t\tsuper.onDetachedFromWindow()\n\t\tpercentAnimator?.run {\n\t\t\tif (isRunning) end()\n\t\t}\n\t\tpercentAnimator = null\n\t}\n\n\toverride fun onAnimationUpdate(animation: ValueAnimator) {\n\t\tval p = animation.animatedValue as Float\n\t\tgetProgressDrawable().percent = p\n\t}\n\n\toverride fun onAnimationStart(animation: Animator) = Unit\n\n\toverride fun onAnimationEnd(animation: Animator) {\n\t\tif (percentAnimator === animation) {\n\t\t\tpercentAnimator = null\n\t\t}\n\t}\n\n\toverride fun onAnimationCancel(animation: Animator) = Unit\n\n\toverride fun onAnimationRepeat(animation: Animator) = Unit\n\n\tfun setProgress(percent: Float, animate: Boolean) {\n\t\tsetProgress(\n\t\t\tvalue = ReadingProgress(percent, 1, PERCENT_READ),\n\t\t\tanimate = animate,\n\t\t)\n\t}\n\n\tfun setProgress(value: ReadingProgress?, animate: Boolean) {\n\t\tval currentDrawable = peekProgressDrawable()\n\t\tif (!animate || currentDrawable == null || value == null) {\n\t\t\tprogress = value\n\t\t\treturn\n\t\t}\n\t\tpercentAnimator?.cancel()\n\t\tval currentPercent = currentDrawable.percent.coerceAtLeast(0f)\n\t\tprogress = value.copy(percent = currentPercent)\n\t\tpercentAnimator = ValueAnimator.ofFloat(\n\t\t\tcurrentDrawable.percent.coerceAtLeast(0f),\n\t\t\tvalue.percent,\n\t\t).apply {\n\t\t\tduration = animationDuration\n\t\t\tinterpolator = AccelerateDecelerateInterpolator()\n\t\t\taddUpdateListener(this@ReadingProgressView)\n\t\t\taddListener(this@ReadingProgressView)\n\t\t\tstart()\n\t\t}\n\t}\n\n\tprivate fun cancelAnimation() {\n\t\tpercentAnimator?.cancel()\n\t\tpercentAnimator = null\n\t}\n\n\tprivate fun peekProgressDrawable(): ReadingProgressDrawable? {\n\t\treturn background as? ReadingProgressDrawable\n\t}\n\n\tprivate fun getProgressDrawable(): ReadingProgressDrawable {\n\t\tvar d = peekProgressDrawable()\n\t\tif (d != null) {\n\t\t\treturn d\n\t\t}\n\t\td = ReadingProgressDrawable(context, drawableStyle)\n\t\tbackground = d\n\t\treturn d\n\t}\n\n\tprivate class OutlineProvider : ViewOutlineProvider() {\n\n\t\toverride fun getOutline(view: View, outline: Outline) {\n\t\t\toutline.setOval(0, 0, view.width, view.height)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/image/ui/CoverImageView.kt",
    "content": "package org.koitharu.kotatsu.image.ui\n\nimport android.content.Context\nimport android.graphics.drawable.LayerDrawable\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.ViewGroup\nimport android.view.ViewTreeObserver\nimport android.view.ViewTreeObserver.OnPreDrawListener\nimport androidx.annotation.AttrRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.graphics.drawable.toDrawable\nimport coil3.network.HttpException\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.SuccessResult\nimport coil3.request.transformations\nimport coil3.size.Dimension\nimport coil3.size.Size\nimport coil3.size.ViewSizeResolver\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport okio.FileNotFoundException\nimport org.jsoup.HttpStatusException\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException\nimport org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException\nimport org.koitharu.kotatsu.core.image.CoilImageView\nimport org.koitharu.kotatsu.core.ui.image.AnimatedPlaceholderDrawable\nimport org.koitharu.kotatsu.core.ui.image.TextDrawable\nimport org.koitharu.kotatsu.core.ui.image.TrimTransformation\nimport org.koitharu.kotatsu.core.util.ext.bookmarkExtra\nimport org.koitharu.kotatsu.core.util.ext.decodeRegion\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.isNetworkError\nimport org.koitharu.kotatsu.core.util.ext.mangaExtra\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.favourites.domain.model.Cover\nimport org.koitharu.kotatsu.parsers.exception.ContentUnavailableException\nimport org.koitharu.kotatsu.parsers.exception.ParseException\nimport org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport kotlin.coroutines.resume\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nclass CoverImageView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = R.attr.coverImageViewStyle,\n) : CoilImageView(context, attrs, defStyleAttr) {\n\n\tprivate var aspectRationHeight: Int = 0\n\tprivate var aspectRationWidth: Int = 0\n\tvar trimImage: Boolean = false\n\n\tprivate val hasAspectRatio: Boolean\n\t\tget() = aspectRationHeight > 0 && aspectRationWidth > 0\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.CoverImageView, defStyleAttr) {\n\t\t\taspectRationHeight = getInt(R.styleable.CoverImageView_aspectRationHeight, aspectRationHeight)\n\t\t\taspectRationWidth = getInt(R.styleable.CoverImageView_aspectRationWidth, aspectRationWidth)\n\t\t\ttrimImage = getBoolean(R.styleable.CoverImageView_trimImage, trimImage)\n\t\t}\n\t\tif (placeholderDrawable == null) {\n\t\t\tplaceholderDrawable = AnimatedPlaceholderDrawable(context)\n\t\t}\n\t\tif (errorDrawable == null) {\n\t\t\terrorDrawable = ColorUtils.blendARGB(\n\t\t\t\tcontext.getThemeColor(materialR.attr.colorErrorContainer),\n\t\t\t\tcontext.getThemeColor(appcompatR.attr.colorBackgroundFloating),\n\t\t\t\t0.25f,\n\t\t\t).toDrawable()\n\t\t}\n\t\tif (fallbackDrawable == null) {\n\t\t\tfallbackDrawable = context.getThemeColor(materialR.attr.colorSurfaceContainer).toDrawable()\n\t\t}\n\t\taddImageRequestListener(ErrorForegroundListener())\n\t}\n\n\toverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\t\tsuper.onMeasure(widthMeasureSpec, heightMeasureSpec)\n\t\tif (!hasAspectRatio) {\n\t\t\treturn\n\t\t}\n\t\tval isExactWidth = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY\n\t\tval isExactHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY\n\t\twhen {\n\t\t\tisExactHeight && isExactWidth -> Unit\n\t\t\tisExactHeight -> setMeasuredDimension(\n\t\t\t\t/* measuredWidth = */ measuredHeight * aspectRationWidth / aspectRationHeight,\n\t\t\t\t/* measuredHeight = */ measuredHeight,\n\t\t\t)\n\n\t\t\tisExactWidth -> setMeasuredDimension(\n\t\t\t\t/* measuredWidth = */ measuredWidth,\n\t\t\t\t/* measuredHeight = */ measuredWidth * aspectRationHeight / aspectRationWidth,\n\t\t\t)\n\t\t}\n\t}\n\n\tfun setImageAsync(page: ReaderPage) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(page.toMangaPage())\n\t\t\t.mangaSourceExtra(page.source)\n\t\t\t.build(),\n\t)\n\n\tfun setImageAsync(page: MangaPage) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(page)\n\t\t\t.mangaSourceExtra(page.source)\n\t\t\t.build(),\n\t)\n\n\tfun setImageAsync(cover: Cover?) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(cover?.url)\n\t\t\t.mangaSourceExtra(cover?.mangaSource)\n\t\t\t.build(),\n\t)\n\n\tfun setImageAsync(\n\t\tcoverUrl: String?,\n\t\tmanga: Manga?,\n\t) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(coverUrl)\n\t\t\t.mangaExtra(manga)\n\t\t\t.build(),\n\t)\n\n\tfun setImageAsync(\n\t\tcoverUrl: String?,\n\t\tsource: MangaSource,\n\t) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(coverUrl)\n\t\t\t.mangaSourceExtra(source)\n\t\t\t.build(),\n\t)\n\n\tfun setImageAsync(\n\t\tbookmark: Bookmark\n\t) = enqueueRequest(\n\t\tnewRequestBuilder()\n\t\t\t.data(bookmark.toMangaPage())\n\t\t\t.decodeRegion(bookmark.scroll)\n\t\t\t.bookmarkExtra(bookmark)\n\t\t\t.build(),\n\t)\n\n\toverride fun newRequestBuilder() = super.newRequestBuilder().apply {\n\t\tif (trimImage) {\n\t\t\ttransformations(listOf(TrimTransformation()))\n\t\t}\n\t\tif (hasAspectRatio) {\n\t\t\tsize(CoverSizeResolver(this@CoverImageView))\n\t\t}\n\t}\n\n\tprivate inner class ErrorForegroundListener : ImageRequest.Listener {\n\n\t\toverride fun onSuccess(request: ImageRequest, result: SuccessResult) {\n\t\t\tsuper.onSuccess(request, result)\n\t\t\tforeground = null\n\t\t}\n\n\t\toverride fun onCancel(request: ImageRequest) {\n\t\t\tsuper.onCancel(request)\n\t\t\tforeground = null\n\t\t}\n\n\t\toverride fun onStart(request: ImageRequest) {\n\t\t\tsuper.onStart(request)\n\t\t\tforeground = null\n\t\t}\n\n\t\toverride fun onError(request: ImageRequest, result: ErrorResult) {\n\t\t\tsuper.onError(request, result)\n\t\t\tforeground = if (result.throwable.isNetworkError() && !networkState.isOnline()) {\n\t\t\t\tContextCompat.getDrawable(context, R.drawable.ic_offline)?.let {\n\t\t\t\t\tLayerDrawable(arrayOf(it)).apply {\n\t\t\t\t\t\tsetLayerGravity(0, Gravity.CENTER)\n\t\t\t\t\t\tsetTint(ContextCompat.getColor(context, R.color.dim_lite))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tresult.throwable.getShortMessage()?.let { text ->\n\t\t\t\t\tTextDrawable.create(context, text, materialR.attr.textAppearanceTitleSmall)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate fun Throwable.getShortMessage(): String? = when (this) {\n\t\t\tis HttpException -> response.code.toString()\n\t\t\tis HttpStatusException -> statusCode.toString()\n\t\t\tis ContentUnavailableException,\n\t\t\tis FileNotFoundException -> \"404\"\n\n\t\t\tis TooManyRequestExceptions -> \"429\"\n\t\t\tis ParseException -> \"</>\"\n\t\t\tis UnsupportedSourceException -> \"X\"\n\t\t\tis CloudFlareProtectedException -> \"?\"\n\t\t\telse -> cause?.getShortMessage()\n\t\t}\n\t}\n\n\tprivate class CoverSizeResolver(\n\t\toverride val view: CoverImageView,\n\t) : ViewSizeResolver<CoverImageView> {\n\n\t\toverride suspend fun size(): Size {\n\t\t\t// Fast path: the view is already measured.\n\t\t\tgetSize()?.let { return it }\n\n\t\t\t// Slow path: wait for the view to be measured.\n\t\t\treturn suspendCancellableCoroutine { continuation ->\n\t\t\t\tval viewTreeObserver = view.viewTreeObserver\n\n\t\t\t\tval preDrawListener = object : OnPreDrawListener {\n\t\t\t\t\tprivate var isResumed = false\n\n\t\t\t\t\toverride fun onPreDraw(): Boolean {\n\t\t\t\t\t\tval size = getSize()\n\t\t\t\t\t\tif (size != null) {\n\t\t\t\t\t\t\tviewTreeObserver.removePreDrawListenerSafe(this)\n\n\t\t\t\t\t\t\tif (!isResumed) {\n\t\t\t\t\t\t\t\tisResumed = true\n\t\t\t\t\t\t\t\tcontinuation.resume(size)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tviewTreeObserver.addOnPreDrawListener(preDrawListener)\n\n\t\t\t\tcontinuation.invokeOnCancellation {\n\t\t\t\t\tviewTreeObserver.removePreDrawListenerSafe(preDrawListener)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate fun getSize(): Size? {\n\t\t\tvar width = getWidth()\n\t\t\tvar height = getHeight()\n\t\t\twhen {\n\t\t\t\twidth == null && height == null -> {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\theight == null -> {\n\t\t\t\t\theight = Dimension(width!!.px * view.aspectRationHeight / view.aspectRationWidth)\n\t\t\t\t}\n\n\t\t\t\twidth == null -> {\n\t\t\t\t\twidth = Dimension(height.px * view.aspectRationWidth / view.aspectRationHeight)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Size(width, height)\n\t\t}\n\n\t\tprivate fun getWidth() = getDimension(\n\t\t\tparamSize = view.layoutParams?.width ?: -1,\n\t\t\tviewSize = view.width,\n\t\t\tpaddingSize = if (subtractPadding) view.paddingLeft + view.paddingRight else 0,\n\t\t)\n\n\t\tprivate fun getHeight() = getDimension(\n\t\t\tparamSize = view.layoutParams?.height ?: -1,\n\t\t\tviewSize = view.height,\n\t\t\tpaddingSize = if (subtractPadding) view.paddingTop + view.paddingBottom else 0,\n\t\t)\n\n\t\tprivate fun getDimension(paramSize: Int, viewSize: Int, paddingSize: Int): Dimension.Pixels? {\n\t\t\tif (paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tval insetParamSize = paramSize - paddingSize\n\t\t\tif (insetParamSize > 0) {\n\t\t\t\treturn Dimension(insetParamSize)\n\t\t\t}\n\t\t\tval insetViewSize = viewSize - paddingSize\n\t\t\tif (insetViewSize > 0) {\n\t\t\t\treturn Dimension(insetViewSize)\n\t\t\t}\n\t\t\treturn null\n\t\t}\n\n\t\tprivate fun ViewTreeObserver.removePreDrawListenerSafe(victim: OnPreDrawListener) {\n\t\t\tif (isAlive) {\n\t\t\t\tremoveOnPreDrawListener(victim)\n\t\t\t} else {\n\t\t\t\tview.viewTreeObserver.removeOnPreDrawListener(victim)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/image/ui/CoverStackView.kt",
    "content": "package org.koitharu.kotatsu.image.ui\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport androidx.annotation.AttrRes\nimport androidx.annotation.Px\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.children\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.widget.ImageViewCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.core.ui.widgets.StackLayout\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.databinding.ViewCoverStackBinding\nimport org.koitharu.kotatsu.favourites.domain.model.Cover\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nclass CoverStackView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : StackLayout(context, attrs, defStyleAttr) {\n\n\tprivate val binding = ViewCoverStackBinding.inflate(LayoutInflater.from(context), this)\n\tprivate val coverViews = arrayOf(\n\t\tbinding.imageViewCover1,\n\t\tbinding.imageViewCover2,\n\t\tbinding.imageViewCover3,\n\t)\n\tprivate var hideEmptyView: Boolean = false\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.CoverStackView, defStyleAttr) {\n\t\t\thideEmptyView = getBoolean(R.styleable.CoverStackView_hideEmptyViews, hideEmptyView)\n\t\t\tchildren.forEach { it.isGone = hideEmptyView }\n\t\t\tval coverSize = getDimension(R.styleable.CoverStackView_coverSize, 0f)\n\t\t\tif (coverSize > 0f) {\n\t\t\t\tsetCoverSize(coverSize)\n\t\t\t}\n\t\t}\n\t\tval backgroundColor = context.getThemeColor(android.R.attr.colorBackground)\n\t\tImageViewCompat.setImageTintList(\n\t\t\tbinding.imageViewCover3,\n\t\t\tColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)),\n\t\t)\n\t\tImageViewCompat.setImageTintList(\n\t\t\tbinding.imageViewCover2,\n\t\t\tColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)),\n\t\t)\n\t\tbinding.imageViewCover2.backgroundTintList = ColorStateList.valueOf(\n\t\t\tColorUtils.setAlphaComponent(backgroundColor, 76),\n\t\t)\n\t\tbinding.imageViewCover3.backgroundTintList = ColorStateList.valueOf(\n\t\t\tColorUtils.setAlphaComponent(backgroundColor, 153),\n\t\t)\n\t\tcoverViews.forEachIndexed { index, view ->\n\t\t\tview.crossfadeDurationFactor = index + 1f\n\t\t}\n\t}\n\n\tfun setCoversAsync(covers: List<Cover>) {\n\t\tcoverViews.forEachIndexed { index, view ->\n\t\t\tview.setImageAsync(covers.getOrNull(index))\n\t\t}\n\t}\n\n\t@JvmName(\"setMangaCoversAsync\")\n\tfun setCoversAsync(manga: List<Manga>) {\n\t\tcoverViews.forEachIndexed { index, view ->\n\t\t\tval m = manga.getOrNull(index)\n\t\t\tview.setCoverOrHide(m?.coverUrl, m, m?.source)\n\t\t}\n\t}\n\n\tfun setCoverSize(@Px coverSize: Float) {\n\t\tval coverWidth = (coverSize * 13f).toInt()\n\t\tval coverHeight = (coverSize * 18f).toInt()\n\t\tchildren.forEach {\n\t\t\tit.updateLayoutParams {\n\t\t\t\twidth = coverWidth\n\t\t\t\theight = coverHeight\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun CoverImageView.setCoverOrHide(url: String?, manga: Manga?, source: MangaSource?) {\n\t\tif (url.isNullOrEmpty() && hideEmptyView) {\n\t\t\tdisposeImage()\n\t\t\tisVisible = false\n\t\t} else {\n\t\t\tisVisible = true\n\t\t\tif (manga != null) {\n\t\t\t\tsetImageAsync(url, manga)\n\t\t\t} else {\n\t\t\t\tsetImageAsync(url, source ?: UnknownMangaSource)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/image/ui/ImageActivity.kt",
    "content": "package org.koitharu.kotatsu.image.ui\n\nimport android.graphics.drawable.Drawable\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.viewModels\nimport androidx.core.graphics.drawable.toBitmap\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.swiperefreshlayout.widget.CircularProgressDrawable\nimport coil3.ImageLoader\nimport coil3.request.CachePolicy\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.SuccessResult\nimport coil3.request.lifecycle\nimport coil3.target.GenericViewTarget\nimport com.davemorrissey.labs.subscaleview.ImageSource\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.image.CoilMemoryCacheKey\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.util.PopupMenuMediator\nimport org.koitharu.kotatsu.core.util.ShareHelper\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.enqueueWith\nimport org.koitharu.kotatsu.core.util.ext.getDisplayIcon\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.databinding.ActivityImageBinding\nimport org.koitharu.kotatsu.databinding.ItemErrorStateBinding\nimport javax.inject.Inject\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\nclass ImageActivity : BaseActivity<ActivityImageBinding>(),\n\tImageRequest.Listener,\n\tView.OnClickListener {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate var errorBinding: ItemErrorStateBinding? = null\n\tprivate val viewModel: ImageViewModel by viewModels()\n\tprivate lateinit var menuMediator: PopupMenuMediator\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityImageBinding.inflate(layoutInflater))\n\t\tviewBinding.buttonBack.setOnClickListener(this)\n\t\tviewBinding.buttonMenu.setOnClickListener(this)\n\n\t\tval menuProvider = ImageMenuProvider(\n\t\t\tactivity = this,\n\t\t\tsnackbarHost = viewBinding.root,\n\t\t\tviewModel = viewModel,\n\t\t)\n\t\tmenuMediator = PopupMenuMediator(menuProvider)\n\t\tviewModel.isLoading.observe(this, ::onLoadingStateChanged)\n\t\tviewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.root, null))\n\t\tviewModel.onImageSaved.observeEvent(this, ::onImageSaved)\n\t\tloadImage()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_back -> dispatchNavigateUp()\n\t\t\tR.id.button_menu -> menuMediator.onLongClick(v)\n\t\t\telse -> loadImage()\n\t\t}\n\t}\n\n\toverride fun onError(request: ImageRequest, result: ErrorResult) {\n\t\tviewBinding.progressBar.hide()\n\t\twith(errorBinding ?: ItemErrorStateBinding.bind(viewBinding.stubError.inflate())) {\n\t\t\terrorBinding = this\n\t\t\troot.isVisible = true\n\t\t\ttextViewError.text = result.throwable.getDisplayMessage(resources)\n\t\t\ttextViewError.setCompoundDrawablesWithIntrinsicBounds(0, result.throwable.getDisplayIcon(), 0, 0)\n\t\t\tbuttonRetry.isVisible = true\n\t\t\tbuttonRetry.setOnClickListener(this@ImageActivity)\n\t\t}\n\t}\n\n\toverride fun onStart(request: ImageRequest) {\n\t\tviewBinding.progressBar.show()\n\t\t(errorBinding?.root ?: viewBinding.stubError).isVisible = false\n\t}\n\n\toverride fun onSuccess(request: ImageRequest, result: SuccessResult) {\n\t\tviewBinding.progressBar.hide()\n\t\t(errorBinding?.root ?: viewBinding.stubError).isVisible = false\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tval baseMargin = v.resources.getDimensionPixelOffset(R.dimen.screen_padding)\n\t\tviewBinding.buttonMenu.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\tmarginEnd = barsInsets.end(v) + baseMargin\n\t\t\ttopMargin = barsInsets.top + baseMargin\n\t\t}\n\t\tviewBinding.buttonBack.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\tmarginStart = barsInsets.start(v) + baseMargin\n\t\t\ttopMargin = barsInsets.top + baseMargin\n\t\t}\n\t\treturn insets.consumeAll(typeMask)\n\t}\n\n\tprivate fun loadImage() {\n\t\tImageRequest.Builder(this)\n\t\t\t.data(intent.data)\n\t\t\t.memoryCacheKey(intent.getParcelableExtraCompat<CoilMemoryCacheKey>(AppRouter.KEY_PREVIEW)?.data)\n\t\t\t.memoryCachePolicy(CachePolicy.READ_ONLY)\n\t\t\t.lifecycle(this)\n\t\t\t.listener(this)\n\t\t\t.mangaSourceExtra(MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE)))\n\t\t\t.target(SsivTarget(viewBinding.ssiv))\n\t\t\t.enqueueWith(coil)\n\t}\n\n\tprivate fun onImageSaved(uri: Uri) {\n\t\tSnackbar.make(viewBinding.root, R.string.page_saved, Snackbar.LENGTH_LONG)\n\t\t\t.setAction(R.string.share) {\n\t\t\t\tShareHelper(this).shareImage(uri)\n\t\t\t}.show()\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tval button = viewBinding.buttonMenu\n\t\tbutton.isClickable = !isLoading\n\t\tif (isLoading) {\n\t\t\tbutton.setImageDrawable(\n\t\t\t\tCircularProgressDrawable(this).also {\n\t\t\t\t\tit.setStyle(CircularProgressDrawable.LARGE)\n\t\t\t\t\tit.setColorSchemeColors(getThemeColor(appcompatR.attr.colorControlNormal))\n\t\t\t\t\tit.start()\n\t\t\t\t},\n\t\t\t)\n\t\t} else {\n\t\t\tbutton.setImageResource(appcompatR.drawable.abc_ic_menu_overflow_material)\n\t\t}\n\t}\n\n\tprivate class SsivTarget(\n\t\toverride val view: SubsamplingScaleImageView,\n\t) : GenericViewTarget<SubsamplingScaleImageView>() {\n\n\t\toverride var drawable: Drawable? = null\n\t\t\tset(value) {\n\t\t\t\tfield = value\n\t\t\t\tsetImageDrawable(value)\n\t\t\t}\n\n\t\toverride fun equals(other: Any?): Boolean {\n\t\t\treturn (this === other) || (other is SsivTarget && view == other.view)\n\t\t}\n\n\t\toverride fun hashCode() = view.hashCode()\n\n\t\toverride fun toString() = \"SsivTarget(view=$view)\"\n\n\t\tprivate fun setImageDrawable(drawable: Drawable?) {\n\t\t\tif (drawable != null) {\n\t\t\t\tview.setImage(ImageSource.bitmap(drawable.toBitmap()))\n\t\t\t} else {\n\t\t\t\tview.recycle()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/image/ui/ImageMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.image.ui\n\nimport android.Manifest\nimport android.os.Build\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.ComponentActivity\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.view.MenuProvider\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.isZipUri\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\n\nclass ImageMenuProvider(\n\tprivate val activity: ComponentActivity,\n\tprivate val snackbarHost: View,\n\tprivate val viewModel: ImageViewModel,\n) : MenuProvider {\n\n\tprivate val permissionLauncher = activity.registerForActivityResult(\n\t\tActivityResultContracts.RequestPermission(),\n\t) { isGranted ->\n\t\tif (isGranted) {\n\t\t\tsaveImage()\n\t\t}\n\t}\n\n\tprivate val saveLauncher = activity.registerForActivityResult(\n\t\tActivityResultContracts.CreateDocument(\"image/png\"),\n\t) { uri ->\n\t\tif (uri != null) {\n\t\t\tviewModel.saveImage(uri)\n\t\t}\n\t}\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_image, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n\t\tR.id.action_save -> {\n\t\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {\n\t\t\t\tpermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)\n\t\t\t} else {\n\t\t\t\tsaveImage()\n\t\t\t}\n\t\t\ttrue\n\t\t}\n\n\t\telse -> false\n\t}\n\n\tprivate fun saveImage() {\n\t\tval name = activity.intent.data?.let {\n\t\t\tif (it.isZipUri()) {\n\t\t\t\tit.fragment\n\t\t\t} else {\n\t\t\t\tit.lastPathSegment\n\t\t\t}?.substringBeforeLast('.')?.plus(\".png\")\n\t\t}\n\t\tif (name == null || !saveLauncher.tryLaunch(name)) {\n\t\t\tSnackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/image/ui/ImageViewModel.kt",
    "content": "package org.koitharu.kotatsu.image.ui\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.net.Uri\nimport androidx.core.graphics.drawable.toBitmap\nimport androidx.lifecycle.SavedStateHandle\nimport coil3.ImageLoader\nimport coil3.request.CachePolicy\nimport coil3.request.ImageRequest\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.require\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ImageViewModel @Inject constructor(\n\t@ApplicationContext private val context: Context,\n\tprivate val savedStateHandle: SavedStateHandle,\n\tprivate val coil: ImageLoader,\n) : BaseViewModel() {\n\n\tval onImageSaved = MutableEventFlow<Uri>()\n\n\tfun saveImage(destination: Uri) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval request = ImageRequest.Builder(context)\n\t\t\t\t.memoryCachePolicy(CachePolicy.READ_ONLY)\n\t\t\t\t.data(savedStateHandle.require<Uri>(AppRouter.KEY_DATA))\n\t\t\t\t.memoryCachePolicy(CachePolicy.DISABLED)\n\t\t\t\t.mangaSourceExtra(MangaSource(savedStateHandle[AppRouter.KEY_SOURCE]))\n\t\t\t\t.build()\n\t\t\tval bitmap = coil.execute(request).getDrawableOrThrow().toBitmap()\n\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\tcontext.contentResolver.openOutputStream(destination)?.use { output ->\n\t\t\t\t\tcheck(bitmap.compress(Bitmap.CompressFormat.PNG, 100, output))\n\t\t\t\t} ?: error(\"Cannot open output stream\")\n\t\t\t}\n\t\t\tonImageSaved.call(destination)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/domain/ListFilterOption.kt",
    "content": "package org.koitharu.kotatsu.list.domain\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.entity.toEntity\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.unwrap\nimport org.koitharu.kotatsu.core.parser.external.ExternalMangaSource\nimport org.koitharu.kotatsu.core.parser.favicon.faviconUri\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\n\nsealed interface ListFilterOption {\n\n\t@get:StringRes\n\tval titleResId: Int\n\n\t@get:DrawableRes\n\tval iconResId: Int\n\n\tval titleText: CharSequence?\n\n\tval groupKey: String\n\n\tfun getIconData(): Any? = null\n\n\tdata object Downloaded : ListFilterOption {\n\n\t\toverride val titleResId: Int\n\t\t\tget() = R.string.on_device\n\n\t\toverride val iconResId: Int\n\t\t\tget() = R.drawable.ic_storage\n\n\t\toverride val titleText: CharSequence?\n\t\t\tget() = null\n\n\t\toverride val groupKey: String\n\t\t\tget() = \"_downloaded\"\n\t}\n\n\tenum class Macro(\n\t\t@StringRes override val titleResId: Int,\n\t\t@DrawableRes override val iconResId: Int,\n\t) : ListFilterOption {\n\n\t\tCOMPLETED(R.string.status_completed, R.drawable.ic_state_finished),\n\t\tNEW_CHAPTERS(R.string.new_chapters, R.drawable.ic_updated),\n\t\tFAVORITE(R.string.favourites, R.drawable.ic_heart_outline),\n\t\tNSFW(R.string.nsfw, R.drawable.ic_nsfw),\n\t\t;\n\n\t\toverride val titleText: CharSequence?\n\t\t\tget() = null\n\n\t\toverride val groupKey: String\n\t\t\tget() = name\n\t}\n\n\tdata class Branch(\n\t\toverride val titleText: String?,\n\t\tval chaptersCount: Int,\n\t) : ListFilterOption {\n\n\t\toverride val titleResId: Int\n\t\t\tget() = if (titleText == null) R.string.system_default else 0\n\n\t\toverride val iconResId: Int\n\t\t\tget() = R.drawable.ic_language\n\n\t\toverride val groupKey: String\n\t\t\tget() = \"_branch\"\n\t}\n\n\tdata class Tag(\n\t\tval tag: MangaTag\n\t) : ListFilterOption {\n\n\t\tval tagId: Long = tag.toEntity().id\n\n\t\toverride val titleResId: Int\n\t\t\tget() = 0\n\n\t\toverride val iconResId: Int\n\t\t\tget() = R.drawable.ic_tag\n\n\t\toverride val titleText: String\n\t\t\tget() = tag.title\n\n\t\toverride val groupKey: String\n\t\t\tget() = \"_tag\"\n\t}\n\n\tdata class Favorite(\n\t\tval category: FavouriteCategory\n\t) : ListFilterOption {\n\n\t\toverride val titleResId: Int\n\t\t\tget() = 0\n\n\t\toverride val iconResId: Int\n\t\t\tget() = R.drawable.ic_heart_outline\n\n\t\toverride val titleText: String\n\t\t\tget() = category.title\n\n\t\toverride val groupKey: String\n\t\t\tget() = \"_favcat\"\n\t}\n\n\tdata class Source(\n\t\tval mangaSource: MangaSource\n\t) : ListFilterOption {\n\t\toverride val titleResId: Int\n\t\t\tget() = when (mangaSource.unwrap()) {\n\t\t\t\tis ExternalMangaSource -> R.string.external_source\n\t\t\t\tLocalMangaSource -> R.string.local_storage\n\t\t\t\telse -> 0\n\t\t\t}\n\n\t\toverride val iconResId: Int\n\t\t\tget() = R.drawable.ic_web\n\n\t\toverride val titleText: CharSequence?\n\t\t\tget() = when (val source = mangaSource.unwrap()) {\n\t\t\t\tis MangaParserSource -> source.title\n\t\t\t\telse -> null\n\t\t\t}\n\n\t\toverride val groupKey: String\n\t\t\tget() = \"_source\"\n\n\t\toverride fun getIconData() = mangaSource.faviconUri()\n\t}\n\n\tdata class Inverted(\n\t\tval option: ListFilterOption,\n\t\toverride val iconResId: Int,\n\t\toverride val titleResId: Int,\n\t\toverride val titleText: CharSequence?,\n\t) : ListFilterOption {\n\n\t\toverride val groupKey: String\n\t\t\tget() = \"_inv\" + option.groupKey\n\t}\n\n\tcompanion object {\n\n\t\tval SFW\n\t\t\tget() = Inverted(\n\t\t\t\toption = Macro.NSFW,\n\t\t\t\ticonResId = R.drawable.ic_sfw,\n\t\t\t\ttitleResId = R.string.sfw,\n\t\t\t\ttitleText = null,\n\t\t\t)\n\n\t\tval NOT_FAVORITE\n\t\t\tget() = Inverted(\n\t\t\t\toption = Macro.FAVORITE,\n\t\t\t\ticonResId = R.drawable.ic_heart_off,\n\t\t\t\ttitleResId = R.string.not_in_favorites,\n\t\t\t\ttitleText = null,\n\t\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/domain/ListSortOrder.kt",
    "content": "package org.koitharu.kotatsu.list.domain\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.util.find\nimport java.util.EnumSet\n\nenum class ListSortOrder(\n\t@StringRes val titleResId: Int,\n) {\n\n\tNEWEST(R.string.order_added),\n\tOLDEST(R.string.order_oldest),\n\tPROGRESS(R.string.progress),\n\tUNREAD(R.string.unread),\n\tALPHABETIC(R.string.by_name),\n\tALPHABETIC_REVERSE(R.string.by_name_reverse),\n\tRATING(R.string.by_rating),\n\tRELEVANCE(R.string.by_relevance),\n\tNEW_CHAPTERS(R.string.new_chapters),\n\tLAST_READ(R.string.last_read),\n\tLONG_AGO_READ(R.string.long_ago_read),\n\tUPDATED(R.string.updated),\n\t;\n\n\tfun isGroupingSupported() = this == LAST_READ || this == NEWEST || this == PROGRESS\n\n\tcompanion object {\n\n\t\tval HISTORY: Set<ListSortOrder> = EnumSet.of(\n\t\t\tLAST_READ,\n\t\t\tLONG_AGO_READ,\n\t\t\tNEWEST,\n\t\t\tOLDEST,\n\t\t\tPROGRESS,\n\t\t\tUNREAD,\n\t\t\tALPHABETIC,\n\t\t\tALPHABETIC_REVERSE,\n\t\t\tNEW_CHAPTERS,\n\t\t\tUPDATED,\n\t\t)\n\t\tval FAVORITES: Set<ListSortOrder> = EnumSet.of(\n\t\t\tALPHABETIC,\n\t\t\tALPHABETIC_REVERSE,\n\t\t\tNEWEST,\n\t\t\tOLDEST,\n\t\t\tRATING,\n\t\t\tNEW_CHAPTERS,\n\t\t\tPROGRESS,\n\t\t\tUNREAD,\n\t\t\tLAST_READ,\n\t\t\tLONG_AGO_READ,\n\t\t\tUPDATED,\n\t\t)\n\t\tval SUGGESTIONS: Set<ListSortOrder> = EnumSet.of(RELEVANCE)\n\n\t\toperator fun invoke(value: String, fallback: ListSortOrder) = entries.find(value) ?: fallback\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListMapper.kt",
    "content": "package org.koitharu.kotatsu.list.domain\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport androidx.annotation.ColorRes\nimport androidx.annotation.IntDef\nimport androidx.collection.MutableScatterSet\nimport androidx.collection.ScatterSet\nimport dagger.Reusable\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.ui.model.MangaCompactListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaDetailedListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaGridModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndex\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.tracker.domain.TrackingRepository\nimport org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem\nimport org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem\nimport javax.inject.Inject\n\n@Reusable\nclass MangaListMapper @Inject constructor(\n\t@ApplicationContext context: Context,\n\tprivate val settings: AppSettings,\n\tprivate val trackingRepository: TrackingRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val favouritesRepository: FavouritesRepository,\n\tprivate val localMangaIndex: LocalMangaIndex,\n\tprivate val dataRepository: MangaDataRepository,\n) {\n\n\tprivate val dict by lazy { readTagsDict(context) }\n\n\tsuspend fun toListModelList(\n\t\tmanga: Collection<Manga>,\n\t\tmode: ListMode,\n\t\t@Flags flags: Int = DEFAULTS,\n\t): List<MangaListModel> = ArrayList<MangaListModel>(manga.size).apply {\n\t\ttoListModelList(\n\t\t\tdestination = this,\n\t\t\tmanga = manga,\n\t\t\tmode = mode,\n\t\t\tflags = flags,\n\t\t)\n\t}\n\n\tsuspend fun toListModelList(\n\t\tdestination: MutableCollection<in MangaListModel>,\n\t\tmanga: Collection<Manga>,\n\t\tmode: ListMode,\n\t\t@Flags flags: Int = DEFAULTS,\n\t) {\n\t\tval options = getOptions(flags)\n\t\tval overrides = dataRepository.getOverrides()\n\t\tmanga.mapTo(destination) {\n\t\t\ttoListModelImpl(it, mode, options, overrides[it.id])\n\t\t}\n\t}\n\n\tsuspend fun toListModel(\n\t\tmanga: Manga,\n\t\tmode: ListMode,\n\t\t@Flags flags: Int = DEFAULTS,\n\t): MangaListModel = toListModelImpl(\n\t\tmanga = manga,\n\t\tmode = mode,\n\t\toptions = getOptions(flags),\n\t\toverride = dataRepository.getOverride(manga.id),\n\t)\n\n\tsuspend fun toFeedItem(logItem: TrackingLogItem) = FeedItem(\n\t\tid = logItem.id,\n\t\toverride = dataRepository.getOverride(logItem.manga.id),\n\t\tcount = logItem.chapters.size,\n\t\tmanga = logItem.manga,\n\t\tisNew = logItem.isNew,\n\t)\n\n\tfun mapTags(tags: Collection<MangaTag>) = tags.map {\n\t\tChipsView.ChipModel(\n\t\t\ttint = getTagTint(it),\n\t\t\ttitle = it.title,\n\t\t\tdata = it,\n\t\t)\n\t}\n\n\tprivate suspend fun toCompactListModel(\n\t\tmanga: Manga,\n\t\t@Options options: Int,\n\t\toverride: MangaOverride?,\n\t) = MangaCompactListModel(\n\t\tmanga = manga,\n\t\toverride = override,\n\t\tsubtitle = manga.tags.joinToString(\", \") { it.title },\n\t\tcounter = getCounter(manga.id, options),\n\t)\n\n\tprivate suspend fun toDetailedListModel(\n\t\tmanga: Manga,\n\t\t@Options options: Int,\n\t\toverride: MangaOverride?,\n\t) = MangaDetailedListModel(\n\t\tsubtitle = manga.altTitles.firstOrNull(),\n\t\tmanga = manga,\n\t\toverride = override,\n\t\tcounter = getCounter(manga.id, options),\n\t\tprogress = getProgress(manga.id, options),\n\t\tisFavorite = isFavorite(manga.id, options),\n\t\tisSaved = isSaved(manga.id, options),\n\t\ttags = mapTags(manga.tags),\n\t)\n\n\tprivate suspend fun toGridModel(\n\t\tmanga: Manga,\n\t\t@Options options: Int,\n\t\toverride: MangaOverride?\n\t) = MangaGridModel(\n\t\tmanga = manga,\n\t\toverride = override,\n\t\tcounter = getCounter(manga.id, options),\n\t\tprogress = getProgress(manga.id, options),\n\t\tisFavorite = isFavorite(manga.id, options),\n\t\tisSaved = isSaved(manga.id, options),\n\t)\n\n\tprivate suspend fun toListModelImpl(\n\t\tmanga: Manga,\n\t\tmode: ListMode,\n\t\t@Options options: Int,\n\t\toverride: MangaOverride?,\n\t): MangaListModel = when (mode) {\n\t\tListMode.LIST -> toCompactListModel(manga, options, override)\n\t\tListMode.DETAILED_LIST -> toDetailedListModel(manga, options, override)\n\t\tListMode.GRID -> toGridModel(manga, options, override)\n\t}\n\n\tprivate suspend fun getCounter(mangaId: Long, @Options options: Int): Int {\n\t\treturn if (settings.isTrackerEnabled) {\n\t\t\ttrackingRepository.getNewChaptersCount(mangaId)\n\t\t} else {\n\t\t\t0\n\t\t}\n\t}\n\n\tprivate suspend fun getProgress(mangaId: Long, @Options options: Int): ReadingProgress? {\n\t\treturn if (options.isBadgeEnabled(PROGRESS)) {\n\t\t\thistoryRepository.getProgress(mangaId, settings.progressIndicatorMode)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprivate suspend fun isFavorite(mangaId: Long, @Options options: Int): Boolean {\n\t\treturn options.isBadgeEnabled(FAVORITE) && favouritesRepository.isFavorite(mangaId)\n\t}\n\n\tprivate suspend fun isSaved(mangaId: Long, @Options options: Int): Boolean {\n\t\treturn options.isBadgeEnabled(SAVED) && mangaId in localMangaIndex\n\t}\n\n\t@ColorRes\n\tprivate fun getTagTint(tag: MangaTag): Int {\n\t\treturn if (settings.isTagsWarningsEnabled && tag.title.lowercase() in dict) {\n\t\t\tR.color.warning\n\t\t} else {\n\t\t\t0\n\t\t}\n\t}\n\n\tprivate fun readTagsDict(context: Context): ScatterSet<String> =\n\t\tcontext.resources.openRawResource(R.raw.tags_warnlist).use {\n\t\t\tval set = MutableScatterSet<String>()\n\t\t\tit.bufferedReader().forEachLine { x ->\n\t\t\t\tval line = x.trim()\n\t\t\t\tif (line.isNotEmpty()) {\n\t\t\t\t\tset.add(line)\n\t\t\t\t}\n\t\t\t}\n\t\t\tset.trim()\n\t\t\tset\n\t\t}\n\n\tprivate fun Int.isBadgeEnabled(@Options badge: Int) = this and badge == badge\n\n\t@Options\n\t@SuppressLint(\"WrongConstant\")\n\tprivate fun getOptions(@Flags flags: Int): Int {\n\t\tvar options = settings.getMangaListBadges() or PROGRESS\n\t\toptions = options and flags.inv()\n\t\treturn options\n\t}\n\n\t@IntDef(DEFAULTS, NO_SAVED, NO_PROGRESS, NO_FAVORITE, flag = true)\n\t@Retention(AnnotationRetention.SOURCE)\n\tannotation class Flags\n\n\t@IntDef(NONE, SAVED, FAVORITE, PROGRESS)\n\t@Retention(AnnotationRetention.SOURCE)\n\tprivate annotation class Options\n\n\tcompanion object {\n\n\t\tprivate const val NONE = 0\n\t\tprivate const val SAVED = 1\n\t\tprivate const val PROGRESS = 2\n\t\tprivate const val FAVORITE = 4\n\n\t\tconst val DEFAULTS = NONE\n\t\tconst val NO_SAVED = SAVED\n\t\tconst val NO_PROGRESS = PROGRESS\n\t\tconst val NO_FAVORITE = FAVORITE\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListQuickFilter.kt",
    "content": "package org.koitharu.kotatsu.list.domain\n\nimport androidx.collection.ArraySet\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport org.koitharu.kotatsu.core.model.toChipModel\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.list.ui.model.QuickFilter\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\n\nabstract class MangaListQuickFilter(\n\tprivate val settings: AppSettings,\n) : QuickFilterListener {\n\n\tprivate val appliedFilter = MutableStateFlow<Set<ListFilterOption>>(emptySet())\n\tprivate val availableFilterOptions = suspendLazy {\n\t\tgetAvailableFilterOptions()\n\t}\n\n\tval appliedOptions\n\t\tget() = appliedFilter.asStateFlow()\n\n\toverride fun setFilterOption(option: ListFilterOption, isApplied: Boolean) {\n\t\tappliedFilter.value = ArraySet(appliedFilter.value).also {\n\t\t\tif (isApplied) {\n\t\t\t\tit.addNoConflicts(option)\n\t\t\t} else {\n\t\t\t\tit.remove(option)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun toggleFilterOption(option: ListFilterOption) {\n\t\tappliedFilter.value = ArraySet(appliedFilter.value).also {\n\t\t\tif (option in it) {\n\t\t\t\tit.remove(option)\n\t\t\t} else {\n\t\t\t\tit.addNoConflicts(option)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun clearFilter() {\n\t\tappliedFilter.value = emptySet()\n\t}\n\n\tsuspend fun filterItem(\n\t\tselectedOptions: Set<ListFilterOption>,\n\t): QuickFilter? {\n\t\tif (!settings.isQuickFilterEnabled) {\n\t\t\treturn null\n\t\t}\n\t\tval availableOptions = availableFilterOptions.getOrNull()?.map { option ->\n\t\t\toption.toChipModel(isChecked = option in selectedOptions)\n\t\t}.orEmpty()\n\t\treturn if (availableOptions.isNotEmpty()) {\n\t\t\tQuickFilter(availableOptions)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tprotected abstract suspend fun getAvailableFilterOptions(): List<ListFilterOption>\n\n\tprivate fun ArraySet<ListFilterOption>.addNoConflicts(option: ListFilterOption) {\n\t\tadd(option)\n\t\tif (option is ListFilterOption.Inverted) {\n\t\t\tremove(option.option)\n\t\t} else {\n\t\t\tremoveIf { it is ListFilterOption.Inverted && it.option == option }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/domain/QuickFilterListener.kt",
    "content": "package org.koitharu.kotatsu.list.domain\n\ninterface QuickFilterListener {\n\n\tfun setFilterOption(option: ListFilterOption, isApplied: Boolean)\n\n\tfun toggleFilterOption(option: ListFilterOption)\n\n\tfun clearFilter()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/domain/ReadingProgress.kt",
    "content": "package org.koitharu.kotatsu.list.domain\n\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.CHAPTERS_LEFT\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.CHAPTERS_READ\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.NONE\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.PERCENT_LEFT\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode.PERCENT_READ\n\ndata class ReadingProgress(\n\tval percent: Float,\n\tval totalChapters: Int,\n\tval mode: ProgressIndicatorMode,\n) {\n\n\tval percentLeft: Float\n\t\tget() = 1f - percent\n\n\tval chapters: Int\n\t\tget() = (totalChapters * percent).toInt()\n\n\tval chaptersLeft: Int\n\t\tget() = (totalChapters * percentLeft).toInt()\n\n\tfun isValid() = when (mode) {\n\t\tNONE -> false\n\t\tPERCENT_READ,\n\t\tPERCENT_LEFT -> percent in 0f..1f\n\n\t\tCHAPTERS_READ,\n\t\tCHAPTERS_LEFT -> totalChapters > 0 && percent in 0f..1f\n\t}\n\n\tfun isCompleted() = isCompleted(percent)\n\n\tfun isReversed() = mode == PERCENT_LEFT || mode == CHAPTERS_LEFT\n\n\tcompanion object {\n\n\t\tconst val PROGRESS_NONE = -1f\n\t\tconst val PROGRESS_COMPLETED = 1f\n\t\tprivate const val PROGRESS_COMPLETED_THRESHOLD = 0.99999f\n\n\t\tfun isValid(percent: Float) = percent in 0f..1f\n\n\t\tfun isCompleted(percent: Float) = percent >= PROGRESS_COMPLETED_THRESHOLD\n\n\t\tfun percentToString(percent: Float): String = if (isValid(percent)) {\n\t\t\tif (isCompleted(percent)) \"100\" else (percent * 100f).toInt().toString()\n\t\t} else {\n\t\t\t\"0\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/GridSpanResolver.kt",
    "content": "package org.koitharu.kotatsu.list.ui\n\nimport android.content.res.Resources\nimport android.view.View\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\nimport kotlin.math.abs\nimport kotlin.math.roundToInt\n\nclass GridSpanResolver(\n\tresources: Resources,\n) : View.OnLayoutChangeListener {\n\n\tvar spanCount = 3\n\t\tprivate set\n\n\tprivate val gridWidth = resources.getDimension(R.dimen.preferred_grid_width)\n\tprivate val spacing = resources.getDimension(R.dimen.grid_spacing)\n\tprivate var cellWidth = -1f\n\n\toverride fun onLayoutChange(\n\t\tv: View?,\n\t\tleft: Int,\n\t\ttop: Int,\n\t\tright: Int,\n\t\tbottom: Int,\n\t\toldLeft: Int,\n\t\toldTop: Int,\n\t\toldRight: Int,\n\t\toldBottom: Int,\n\t) {\n\t\tif (cellWidth <= 0f) {\n\t\t\treturn\n\t\t}\n\t\tval rv = v as? RecyclerView ?: return\n\t\tval width = abs(right - left)\n\t\tif (width == 0) {\n\t\t\treturn\n\t\t}\n\t\tresolveGridSpanCount(width)\n\t\t(rv.layoutManager as? GridLayoutManager)?.spanCount = spanCount\n\t}\n\n\tfun setGridSize(scaleFactor: Float, rv: RecyclerView) {\n\t\tcellWidth = (gridWidth * scaleFactor) + spacing\n\t\tval lm = rv.layoutManager as? GridLayoutManager ?: return\n\t\tval innerWidth = lm.width - lm.paddingEnd - lm.paddingStart\n\t\tif (innerWidth > 0) {\n\t\t\tresolveGridSpanCount(innerWidth)\n\t\t\tlm.spanCount = spanCount\n\t\t}\n\t}\n\n\tprivate fun resolveGridSpanCount(width: Int) {\n\t\tval estimatedCount = (width / cellWidth).roundToInt()\n\t\tspanCount = estimatedCount.coerceAtLeast(2)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/ListModelDiffCallback.kt",
    "content": "package org.koitharu.kotatsu.list.ui\n\nimport androidx.recyclerview.widget.DiffUtil\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nopen class ListModelDiffCallback<T : ListModel> : DiffUtil.ItemCallback<T>() {\n\n\toverride fun areItemsTheSame(oldItem: T, newItem: T): Boolean {\n\t\treturn oldItem.areItemsTheSame(newItem)\n\t}\n\n\toverride fun areContentsTheSame(oldItem: T, newItem: T): Boolean {\n\t\treturn oldItem == newItem\n\t}\n\n\toverride fun getChangePayload(oldItem: T, newItem: T): Any? {\n\t\treturn newItem.getChangePayload(oldItem)\n\t}\n\n\tcompanion object : ListModelDiffCallback<ListModel>() {\n\n\t\tval PAYLOAD_CHECKED_CHANGED = Any()\n\t\tval PAYLOAD_NESTED_LIST_CHANGED = Any()\n\t\tval PAYLOAD_PROGRESS_CHANGED = Any()\n\t\tval PAYLOAD_ANYTHING_CHANGED = Any()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt",
    "content": "package org.koitharu.kotatsu.list.ui\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.CallSuper\nimport androidx.appcompat.view.ActionMode\nimport androidx.collection.ArraySet\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.swiperefreshlayout.widget.SwipeRefreshLayout\nimport coil3.ImageLoader\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.alternatives.ui.AutoFixService\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.list.FitHeightGridLayoutManager\nimport org.koitharu.kotatsu.core.ui.list.FitHeightLinearLayoutManager\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.PaginationScrollListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.ui.widgets.TipView\nimport org.koitharu.kotatsu.core.util.ShareHelper\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\nimport org.koitharu.kotatsu.databinding.FragmentListBinding\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.QuickFilterListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListListener\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.search.ui.MangaListActivity\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nabstract class MangaListFragment :\n\tBaseFragment<FragmentListBinding>(),\n\tPaginationScrollListener.Callback,\n\tMangaListListener,\n\tRecyclerViewOwner,\n\tSwipeRefreshLayout.OnRefreshListener,\n\tListSelectionController.Callback,\n\tFastScroller.FastScrollListener {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate var listAdapter: MangaListAdapter? = null\n\tprivate var paginationListener: PaginationScrollListener? = null\n\tprivate var selectionController: ListSelectionController? = null\n\tprivate var spanResolver: GridSpanResolver? = null\n\tprivate val spanSizeLookup = SpanSizeLookup()\n\topen val isSwipeRefreshEnabled = true\n\n\tprotected abstract val viewModel: MangaListViewModel\n\n\tprotected val selectedItemsIds: Set<Long>\n\t\tget() = selectionController?.snapshot().orEmpty()\n\n\tprotected val selectedItems: Set<Manga>\n\t\tget() = collectSelectedItems()\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerView\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentListBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tlistAdapter = onCreateAdapter()\n\t\tspanResolver = GridSpanResolver(binding.root.resources)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = checkNotNull(findAppCompatDelegate()),\n\t\t\tdecoration = MangaSelectionDecoration(binding.root.context),\n\t\t\tregistryOwner = this,\n\t\t\tcallback = this,\n\t\t)\n\t\tpaginationListener = PaginationScrollListener(4, this)\n\t\twith(binding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\tadapter = listAdapter\n\t\t\tcheckNotNull(selectionController).attachToRecyclerView(this)\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\taddOnScrollListener(checkNotNull(paginationListener))\n\t\t\tfastScroller.setFastScrollListener(this@MangaListFragment)\n\t\t}\n\t\twith(binding.swipeRefreshLayout) {\n\t\t\tsetOnRefreshListener(this@MangaListFragment)\n\t\t\tisEnabled = isSwipeRefreshEnabled\n\t\t}\n\t\taddMenuProvider(MangaListMenuProvider(this))\n\n\t\tviewModel.listMode.observe(viewLifecycleOwner, ::onListModeChanged)\n\t\tviewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged)\n\t\tviewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)\n\t\tviewModel.content.observe(viewLifecycleOwner, ::onListChanged)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))\n\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tval basePadding = v.resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\tleft = barsInsets.left + basePadding,\n\t\t\ttop = basePadding,\n\t\t\tright = barsInsets.right + basePadding,\n\t\t\tbottom = barsInsets.bottom + basePadding,\n\t\t)\n\t\treturn insets.consumeAll(typeMask)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tlistAdapter = null\n\t\tpaginationListener = null\n\t\tselectionController = null\n\t\tspanResolver = null\n\t\tspanSizeLookup.invalidateCache()\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onItemClick(item: MangaListModel, view: View) {\n\t\tif (selectionController?.onItemClick(item.id) != true) {\n\t\t\tval manga = item.toMangaWithOverride()\n\t\t\tif ((activity as? MangaListActivity)?.showPreview(manga) != true) {\n\t\t\t\trouter.openDetails(manga)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onItemLongClick(item: MangaListModel, view: View): Boolean {\n\t\treturn selectionController?.onItemLongClick(view, item.id) == true\n\t}\n\n\toverride fun onItemContextClick(item: MangaListModel, view: View): Boolean {\n\t\treturn selectionController?.onItemContextClick(view, item.id) == true\n\t}\n\n\toverride fun onReadClick(manga: Manga, view: View) {\n\t\tif (selectionController?.onItemClick(manga.id) != true) {\n\t\t\trouter.openReader(manga)\n\t\t}\n\t}\n\n\toverride fun onTagClick(manga: Manga, tag: MangaTag, view: View) {\n\t\tif (selectionController?.onItemClick(manga.id) != true) {\n\t\t\trouter.showTagDialog(tag)\n\t\t}\n\t}\n\n\t@CallSuper\n\toverride fun onRefresh() {\n\t\trequireViewBinding().swipeRefreshLayout.isRefreshing = true\n\t\tviewModel.onRefresh()\n\t}\n\n\tprivate suspend fun onListChanged(list: List<ListModel>) {\n\t\tlistAdapter?.emit(list)\n\t\tspanSizeLookup.invalidateCache()\n\t\tviewBinding?.recyclerView?.let {\n\t\t\tpaginationListener?.postInvalidate(it)\n\t\t}\n\t}\n\n\tprivate fun resolveException(e: Throwable) {\n\t\tif (ExceptionResolver.canResolve(e)) {\n\t\t\tviewLifecycleScope.launch {\n\t\t\t\tif (exceptionResolver.resolve(e)) {\n\t\t\t\t\tviewModel.onRetry()\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tviewModel.onRetry()\n\t\t}\n\t}\n\n\t@CallSuper\n\tprotected open fun onLoadingStateChanged(isLoading: Boolean) {\n\t\trequireViewBinding().swipeRefreshLayout.isEnabled = requireViewBinding().swipeRefreshLayout.isRefreshing ||\n\t\t\tisSwipeRefreshEnabled && !isLoading\n\t\tif (!isLoading) {\n\t\t\trequireViewBinding().swipeRefreshLayout.isRefreshing = false\n\t\t}\n\t}\n\n\tprotected open fun onCreateAdapter(): MangaListAdapter {\n\t\treturn MangaListAdapter(\n\t\t\tlistener = this,\n\t\t\tsizeResolver = DynamicItemSizeResolver(resources, viewLifecycleOwner, settings, adjustWidth = false),\n\t\t)\n\t}\n\n\toverride fun onFilterOptionClick(option: ListFilterOption) {\n\t\tselectionController?.clear()\n\t\t(viewModel as? QuickFilterListener)?.toggleFilterOption(option)\n\t}\n\n\toverride fun onFilterClick(view: View?) = Unit\n\n\toverride fun onEmptyActionClick() = Unit\n\n\toverride fun onListHeaderClick(item: ListHeader, view: View) = Unit\n\n\toverride fun onPrimaryButtonClick(tipView: TipView) = Unit\n\n\toverride fun onSecondaryButtonClick(tipView: TipView) = Unit\n\n\toverride fun onRetryClick(error: Throwable) {\n\t\tresolveException(error)\n\t}\n\n\tprivate fun onGridScaleChanged(scale: Float) {\n\t\tspanSizeLookup.invalidateCache()\n\t\tspanResolver?.setGridSize(scale, requireViewBinding().recyclerView)\n\t}\n\n\tprivate fun onListModeChanged(mode: ListMode) {\n\t\tspanSizeLookup.invalidateCache()\n\t\twith(requireViewBinding().recyclerView) {\n\t\t\tremoveOnLayoutChangeListener(spanResolver)\n\t\t\twhen (mode) {\n\t\t\t\tListMode.LIST -> {\n\t\t\t\t\tlayoutManager = FitHeightLinearLayoutManager(context)\n\t\t\t\t}\n\n\t\t\t\tListMode.DETAILED_LIST -> {\n\t\t\t\t\tlayoutManager = FitHeightLinearLayoutManager(context)\n\t\t\t\t}\n\n\t\t\t\tListMode.GRID -> {\n\t\t\t\t\tlayoutManager = FitHeightGridLayoutManager(context, checkNotNull(spanResolver).spanCount).also {\n\t\t\t\t\t\tit.spanSizeLookup = spanSizeLookup\n\t\t\t\t\t}\n\t\t\t\t\taddOnLayoutChangeListener(spanResolver)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@CallSuper\n\toverride fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {\n\t\tval hasNoLocal = selectedItems.none { it.isLocal }\n\t\tval isSingleSelection = controller.count == 1\n\t\tmenu.findItem(R.id.action_save)?.isVisible = hasNoLocal\n\t\tmenu.findItem(R.id.action_fix)?.isVisible = hasNoLocal\n\t\tmenu.findItem(R.id.action_edit_override)?.isVisible = isSingleSelection\n\t\treturn super.onPrepareActionMode(controller, mode, menu)\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\treturn menu.hasVisibleItems()\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_select_all -> {\n\t\t\t\tval ids = listAdapter?.items?.mapNotNull {\n\t\t\t\t\t(it as? MangaListModel)?.id\n\t\t\t\t} ?: return false\n\t\t\t\tselectionController?.addAll(ids)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_share -> {\n\t\t\t\tShareHelper(requireContext()).shareMangaLinks(selectedItems)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_favourite -> {\n\t\t\t\trouter.showFavoriteDialog(selectedItems)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_save -> {\n\t\t\t\trouter.showDownloadDialog(selectedItems, viewBinding?.recyclerView)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_edit_override -> {\n\t\t\t\trouter.openMangaOverrideConfig(selectedItems.singleOrNull() ?: return false)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_fix -> {\n\t\t\t\tval itemsSnapshot = selectedItemsIds\n\t\t\t\tbuildAlertDialog(context ?: return false, isCentered = true) {\n\t\t\t\t\tsetTitle(item.title)\n\t\t\t\t\tsetIcon(item.icon)\n\t\t\t\t\tsetMessage(R.string.manga_fix_prompt)\n\t\t\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\t\t\tsetPositiveButton(R.string.fix) { _, _ ->\n\t\t\t\t\t\tAutoFixService.start(context, itemsSnapshot)\n\t\t\t\t\t\tmode?.finish()\n\t\t\t\t\t}\n\t\t\t\t}.show()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\tviewBinding?.recyclerView?.invalidateItemDecorations()\n\t}\n\n\toverride fun onFastScrollStart(fastScroller: FastScroller) {\n\t\t(activity as? AppBarOwner)?.appBar?.setExpanded(false, true)\n\t\trequireViewBinding().swipeRefreshLayout.isEnabled = false\n\t}\n\n\toverride fun onFastScrollStop(fastScroller: FastScroller) {\n\t\trequireViewBinding().swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled\n\t}\n\n\tprivate fun collectSelectedItems(): Set<Manga> {\n\t\tval checkedIds = selectionController?.peekCheckedIds() ?: return emptySet()\n\t\tval items = listAdapter?.items ?: return emptySet()\n\t\tval result = ArraySet<Manga>(checkedIds.size)\n\t\tfor (item in items) {\n\t\t\tif (item is MangaListModel && item.id in checkedIds) {\n\t\t\t\tresult.add(item.manga)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() {\n\n\t\tinit {\n\t\t\tisSpanIndexCacheEnabled = true\n\t\t\tisSpanGroupIndexCacheEnabled = true\n\t\t}\n\n\t\toverride fun getSpanSize(position: Int): Int {\n\t\t\tval total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1\n\t\t\treturn when (listAdapter?.getItemViewType(position)) {\n\t\t\t\tListItemType.MANGA_GRID.ordinal -> 1\n\t\t\t\telse -> total\n\t\t\t}\n\t\t}\n\n\t\tfun invalidateCache() {\n\t\t\tinvalidateSpanGroupIndexCache()\n\t\t\tinvalidateSpanIndexCache()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.list.ui\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.Fragment\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment\nimport org.koitharu.kotatsu.history.ui.HistoryListFragment\nimport org.koitharu.kotatsu.list.ui.config.ListConfigSection\nimport org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment\nimport org.koitharu.kotatsu.tracker.ui.updates.UpdatesFragment\n\nclass MangaListMenuProvider(\n\tprivate val fragment: Fragment,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_list, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n\t\tR.id.action_list_mode -> {\n\t\t\tval section: ListConfigSection = when (fragment) {\n\t\t\t\tis HistoryListFragment -> ListConfigSection.History\n\t\t\t\tis SuggestionsFragment -> ListConfigSection.Suggestions\n\t\t\t\tis FavouritesListFragment -> ListConfigSection.Favorites(fragment.categoryId)\n\t\t\t\tis UpdatesFragment -> ListConfigSection.Updated\n\t\t\t\telse -> ListConfigSection.General\n\t\t\t}\n\t\t\tfragment.router.showListConfigSheet(section)\n\t\t\ttrue\n\t\t}\n\n\t\telse -> false\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui\n\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.filter\nimport kotlinx.coroutines.flow.merge\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\n\nabstract class MangaListViewModel(\n\tprivate val settings: AppSettings,\n\tprivate val mangaDataRepository: MangaDataRepository,\n\t@param:LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,\n) : BaseViewModel() {\n\n\tabstract val content: StateFlow<List<ListModel>>\n\topen val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE) { listMode }\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.listMode)\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\tval gridScale = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_GRID_SIZE,\n\t\tvalueProducer = { gridSize / 100f },\n\t)\n\n\tval isIncognitoModeEnabled: Boolean\n\t\tget() = settings.isIncognitoModeEnabled\n\n\tabstract fun onRefresh()\n\n\tabstract fun onRetry()\n\n\tprotected fun List<Manga>.skipNsfwIfNeeded() = if (settings.isNsfwContentDisabled) {\n\t\tfilterNot { it.isNsfw() }\n\t} else {\n\t\tthis\n\t}\n\n\tprotected fun Flow<Set<ListFilterOption>>.combineWithSettings(): Flow<Set<ListFilterOption>> = combine(\n\t\tsettings.observeAsFlow(AppSettings.KEY_DISABLE_NSFW) { isNsfwContentDisabled },\n\t) { filters, skipNsfw ->\n\t\tif (skipNsfw) {\n\t\t\tfilters + ListFilterOption.SFW\n\t\t} else {\n\t\t\tfilters\n\t\t}\n\t}\n\n\tprotected fun observeListModeWithTriggers(): Flow<ListMode> = combine(\n\t\tlistMode,\n\t\tmerge(\n\t\t\tmangaDataRepository.observeOverridesTrigger(emitInitialState = true),\n\t\t\tmangaDataRepository.observeFavoritesTrigger(emitInitialState = true),\n\t\t\tlocalStorageChanges.onStart { emit(null) },\n\t\t),\n\t\tsettings.observeChanges().filter { key ->\n\t\t\tkey == AppSettings.KEY_PROGRESS_INDICATORS\n\t\t\t\t|| key == AppSettings.KEY_TRACKER_ENABLED\n\t\t\t\t|| key == AppSettings.KEY_QUICK_FILTER\n\t\t\t\t|| key == AppSettings.KEY_MANGA_LIST_BADGES\n\t\t}.onStart { emit(\"\") },\n\t) { mode, _, _ ->\n\t\tmode\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.list.ui\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.view.View\nimport androidx.cardview.widget.CardView\nimport androidx.core.graphics.ColorUtils\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.NO_ID\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nopen class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {\n\n\tprotected val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprotected val strokeColor = context.getThemeColor(appcompatR.attr.colorPrimary, Color.RED)\n\tprotected val fillColor = ColorUtils.setAlphaComponent(\n\t\tColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),\n\t\t0x74,\n\t)\n\tprotected val defaultRadius = context.resources.getDimension(R.dimen.list_selector_corner)\n\n\tinit {\n\t\thasBackground = false\n\t\thasForeground = true\n\t\tisIncludeDecorAndMargins = false\n\n\t\tpaint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)\n\t}\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return NO_ID\n\t\tval item = holder.getItem(MangaListModel::class.java) ?: return NO_ID\n\t\treturn item.id\n\t}\n\n\toverride fun onDrawForeground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\tval radius = (child as? CardView)?.radius ?: defaultRadius\n\t\tpaint.color = fillColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t\tpaint.color = strokeColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRoundRect(bounds, radius, radius, paint)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/BadgeADUtil.kt",
    "content": "@file:androidx.annotation.OptIn(ExperimentalBadgeUtils::class)\n\npackage org.koitharu.kotatsu.list.ui.adapter\n\nimport android.view.View\nimport androidx.annotation.CheckResult\nimport androidx.cardview.widget.CardView\nimport androidx.core.view.doOnNextLayout\nimport com.google.android.material.badge.BadgeDrawable\nimport com.google.android.material.badge.BadgeUtils\nimport com.google.android.material.badge.ExperimentalBadgeUtils\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\n\n@Deprecated(\"\")\n@CheckResult\nfun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {\n\treturn bindBadgeImpl(badge, null, counter)\n}\n\n@Deprecated(\"\")\n@CheckResult\nfun View.bindBadge(badge: BadgeDrawable?, text: String?): BadgeDrawable? {\n\treturn bindBadgeImpl(badge, text, 0)\n}\n\n@Deprecated(\"\")\nfun View.clearBadge(badge: BadgeDrawable?) {\n\tBadgeUtils.detachBadgeDrawable(badge, this)\n}\n\nprivate fun View.bindBadgeImpl(\n\tbadge: BadgeDrawable?,\n\ttext: String?,\n\tcounter: Int,\n): BadgeDrawable? = if (text != null || counter > 0) {\n\tval badgeDrawable = badge ?: initBadge(this)\n\tif (counter > 0) {\n\t\tbadgeDrawable.number = counter\n\t} else {\n\t\tbadgeDrawable.text = text?.nullIfEmpty()\n\t}\n\tbadgeDrawable.isVisible = true\n\tbadgeDrawable.align(this)\n\tbadgeDrawable\n} else {\n\tbadge?.isVisible = false\n\tbadge\n}\n\nprivate fun initBadge(anchor: View): BadgeDrawable {\n\tval badge = BadgeDrawable.create(anchor.context)\n\tval resources = anchor.resources\n\tbadge.maxCharacterCount = resources.getInteger(R.integer.manga_badge_max_character_count)\n\tanchor.doOnNextLayout {\n\t\tBadgeUtils.attachBadgeDrawable(badge, it)\n\t\tbadge.align(it)\n\t}\n\treturn badge\n}\n\nprivate fun BadgeDrawable.align(anchor: View) {\n\tval extraOffset = if (anchor is CardView) {\n\t\t(anchor.radius / 2f).toInt()\n\t} else {\n\t\tanchor.resources.getDimensionPixelOffset(R.dimen.badge_offset)\n\t}\n\thorizontalOffset = intrinsicWidth + extraOffset\n\tverticalOffset = intrinsicHeight + extraOffset\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ButtonFooterAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.databinding.ItemButtonFooterBinding\nimport org.koitharu.kotatsu.list.ui.model.ButtonFooter\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun buttonFooterAD(\n\tlistener: ListStateHolderListener,\n) = adapterDelegateViewBinding<ButtonFooter, ListModel, ItemButtonFooterBinding>(\n\t{ inflater, parent -> ItemButtonFooterBinding.inflate(inflater, parent, false) },\n) {\n\n\tbinding.button.setOnClickListener {\n\t\tlistener.onFooterButtonClick()\n\t}\n\n\tbind {\n\t\tbinding.button.setText(item.textResId)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyHintAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.ItemEmptyCardBinding\nimport org.koitharu.kotatsu.list.ui.model.EmptyHint\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun emptyHintAD(\n\tlistener: ListStateHolderListener,\n) = adapterDelegateViewBinding<EmptyHint, ListModel, ItemEmptyCardBinding>(\n\t{ inflater, parent -> ItemEmptyCardBinding.inflate(inflater, parent, false) },\n) {\n\n\tbinding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() }\n\n\tbind {\n\t\tbinding.icon.setImageAsync(item.icon)\n\t\tbinding.textPrimary.setText(item.textPrimary)\n\t\tbinding.textSecondary.setTextAndVisible(item.textSecondary)\n\t\tbinding.buttonRetry.setTextAndVisible(item.actionStringRes)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyStateListAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.ItemEmptyStateBinding\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun emptyStateListAD(\n\tlistener: ListStateHolderListener?,\n) = adapterDelegateViewBinding<EmptyState, ListModel, ItemEmptyStateBinding>(\n\t{ inflater, parent -> ItemEmptyStateBinding.inflate(inflater, parent, false) },\n) {\n\n\tif (listener != null) {\n\t\tbinding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() }\n\t}\n\n\tbind {\n\t\tif (item.icon == 0) {\n\t\t\tbinding.icon.isVisible = false\n\t\t\tbinding.icon.disposeImage()\n\t\t} else {\n\t\t\tbinding.icon.isVisible = true\n\t\t\tbinding.icon.setImageAsync(item.icon)\n\t\t}\n\t\tbinding.textPrimary.setText(item.textPrimary)\n\t\tbinding.textSecondary.setTextAndVisible(item.textSecondary)\n\t\tif (listener != null) {\n\t\t\tbinding.buttonRetry.setTextAndVisible(item.actionStringRes)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ErrorFooterAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.databinding.ItemErrorFooterBinding\nimport org.koitharu.kotatsu.list.ui.model.ErrorFooter\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun errorFooterAD(\n\tlistener: ListStateHolderListener?,\n) = adapterDelegateViewBinding<ErrorFooter, ListModel, ItemErrorFooterBinding>(\n\t{ inflater, parent -> ItemErrorFooterBinding.inflate(inflater, parent, false) },\n) {\n\n\tif (listener != null) {\n\t\tbinding.root.setOnClickListener {\n\t\t\tlistener.onRetryClick(item.exception)\n\t\t}\n\t}\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.exception.getDisplayMessage(context.resources)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ErrorStateListAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport android.view.View\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.ItemErrorStateBinding\nimport org.koitharu.kotatsu.list.ui.model.ErrorState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun errorStateListAD(\n\tlistener: ListStateHolderListener?,\n) = adapterDelegateViewBinding<ErrorState, ListModel, ItemErrorStateBinding>(\n\t{ inflater, parent -> ItemErrorStateBinding.inflate(inflater, parent, false) },\n) {\n\n\tif (listener != null) {\n\t\tval onClickListener = View.OnClickListener { v ->\n\t\t\twhen (v.id) {\n\t\t\t\tR.id.button_retry -> listener.onRetryClick(item.exception)\n\t\t\t\tR.id.button_secondary -> listener.onSecondaryErrorActionClick(item.exception)\n\t\t\t}\n\t\t}\n\n\t\tbinding.buttonRetry.setOnClickListener(onClickListener)\n\t\tbinding.buttonSecondary.setOnClickListener(onClickListener)\n\t}\n\n\tbind {\n\t\twith(binding.textViewError) {\n\t\t\ttext = item.exception.getDisplayMessage(context.resources)\n\t\t\tsetCompoundDrawablesWithIntrinsicBounds(0, item.icon, 0, 0)\n\t\t}\n\t\twith(binding.buttonRetry) {\n\t\t\tisVisible = item.canRetry && listener != null\n\t\t\tsetText(item.buttonText)\n\t\t}\n\t\tbinding.buttonSecondary.setTextAndVisible(item.secondaryButtonText)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/InfoAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.ItemInfoBinding\nimport org.koitharu.kotatsu.list.ui.model.InfoModel\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun infoAD() = adapterDelegateViewBinding<InfoModel, ListModel, ItemInfoBinding>(\n\t{ layoutInflater, parent -> ItemInfoBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbind {\n\t\tbinding.textViewTitle.setText(item.title)\n\t\tbinding.textViewBody.setTextAndVisible(item.text)\n\t\tbinding.textViewTitle.setCompoundDrawablesRelativeWithIntrinsicBounds(\n\t\t\titem.icon, 0, 0, 0,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport com.google.android.material.badge.BadgeDrawable\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.databinding.ItemHeaderBinding\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nfun listHeaderAD(\n\tlistener: ListHeaderClickListener?,\n) = adapterDelegateViewBinding<ListHeader, ListModel, ItemHeaderBinding>(\n\t{ inflater, parent -> ItemHeaderBinding.inflate(inflater, parent, false) },\n) {\n\tvar badge: BadgeDrawable? = null\n\n\tif (listener != null) {\n\t\tbinding.buttonMore.setOnClickListener {\n\t\t\tlistener.onListHeaderClick(item, it)\n\t\t}\n\t}\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.getText(context)\n\t\tif (item.buttonTextRes == 0) {\n\t\t\tbinding.buttonMore.isInvisible = true\n\t\t\tbinding.buttonMore.text = null\n\t\t\tbinding.buttonMore.clearBadge(badge)\n\t\t} else {\n\t\t\tbinding.buttonMore.setText(item.buttonTextRes)\n\t\t\tbinding.buttonMore.isVisible = true\n\t\t\tbadge = itemView.bindBadge(badge, item.badge)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListHeaderClickListener.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport android.view.View\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\n\ninterface ListHeaderClickListener {\n\n\tfun onListHeaderClick(item: ListHeader, view: View)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListItemType.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nenum class ListItemType {\n\n\tQUICK_FILTER,\n\tFILTER_SORT,\n\tFILTER_TAG,\n\tFILTER_TAG_MULTI,\n\tFILTER_STATE,\n\tFILTER_LANGUAGE,\n\tHEADER,\n\tMANGA_LIST,\n\tMANGA_LIST_DETAILED,\n\tMANGA_GRID,\n\tMANGA_NESTED_GROUP,\n\tFOOTER_LOADING,\n\tFOOTER_ERROR,\n\tFOOTER_BUTTON,\n\tSTATE_LOADING,\n\tSTATE_ERROR,\n\tSTATE_EMPTY,\n\tEXPLORE_BUTTONS,\n\tEXPLORE_SOURCE_GRID,\n\tEXPLORE_SOURCE_LIST,\n\tEXPLORE_SUGGESTION,\n\tTIP,\n\tINFO,\n\tHINT_EMPTY,\n\tPAGE_THUMB,\n\tFEED,\n\tDOWNLOAD,\n\tCATEGORY_LARGE,\n\tMANGA_SCROBBLING,\n\tNAV_ITEM,\n\tCHAPTER_LIST,\n\tCHAPTER_GRID,\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListStateHolderListener.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\ninterface ListStateHolderListener {\n\n\tfun onRetryClick(error: Throwable)\n\n\tfun onSecondaryErrorActionClick(error: Throwable) = Unit\n\n\tfun onEmptyActionClick()\n\n\tfun onFooterButtonClick() = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/LoadingFooterAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingFooter\n\nfun loadingFooterAD() = adapterDelegate<LoadingFooter, ListModel>(R.layout.item_loading_footer) {\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/LoadingStateAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\n\nfun loadingStateAD() = adapterDelegate<LoadingState, ListModel>(R.layout.item_loading_state) {\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaDetailsClickListener.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport android.view.View\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\n\ninterface MangaDetailsClickListener : OnListItemClickListener<MangaListModel> {\n\n\tfun onReadClick(manga: Manga, view: View)\n\n\tfun onTagClick(manga: Manga, tag: MangaTag, view: View)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.databinding.ItemMangaGridBinding\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaGridModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.size.ItemSizeResolver\n\nfun mangaGridItemAD(\n\tsizeResolver: ItemSizeResolver,\n\tclickListener: OnListItemClickListener<MangaListModel>,\n) = adapterDelegateViewBinding<MangaGridModel, ListModel, ItemMangaGridBinding>(\n\t{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)\n\tsizeResolver.attachToView(itemView, binding.textViewTitle, binding.progressView)\n\n\tbind { payloads ->\n\t\titemView.setTooltipCompat(item.getSummary(context))\n\t\tbinding.textViewTitle.text = item.title\n\t\tbinding.progressView.setProgress(item.progress, PAYLOAD_PROGRESS_CHANGED in payloads)\n\t\twith(binding.iconsView) {\n\t\t\tclearIcons()\n\t\t\tif (item.isSaved) addIcon(R.drawable.ic_storage)\n\t\t\tif (item.isFavorite) addIcon(R.drawable.ic_heart_outline)\n\t\t\tisVisible = iconsCount > 0\n\t\t}\n\t\tbinding.imageViewCover.setImageAsync(item.coverUrl, item.manga)\n\t\tbinding.badge.number = item.counter\n\t\tbinding.badge.isVisible = item.counter > 0\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.size.ItemSizeResolver\n\nopen class MangaListAdapter(\n\tlistener: MangaListListener,\n\tsizeResolver: ItemSizeResolver,\n) : BaseListAdapter<ListModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.MANGA_LIST, mangaListItemAD(listener))\n\t\taddDelegate(ListItemType.MANGA_LIST_DETAILED, mangaListDetailedItemAD(listener))\n\t\taddDelegate(ListItemType.MANGA_GRID, mangaGridItemAD(sizeResolver, listener))\n\t\taddDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener))\n\t\taddDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener))\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listener))\n\t\taddDelegate(ListItemType.HINT_EMPTY, emptyHintAD(listener))\n\t\taddDelegate(ListItemType.HEADER, listHeaderAD(listener))\n\t\taddDelegate(ListItemType.QUICK_FILTER, quickFilterAD(listener))\n\t\taddDelegate(ListItemType.TIP, tipAD(listener))\n\t\taddDelegate(ListItemType.INFO, infoAD())\n\t\taddDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(listener))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaDetailedListModel\n\nfun mangaListDetailedItemAD(\n\tclickListener: MangaDetailsClickListener,\n) = adapterDelegateViewBinding<MangaDetailedListModel, ListModel, ItemMangaListDetailsBinding>(\n\t{ inflater, parent -> ItemMangaListDetailsBinding.inflate(inflater, parent, false) },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, clickListener)\n\t\t.attach(itemView)\n\n\tbind { payloads ->\n\t\tbinding.textViewTitle.text = item.title\n\t\tbinding.textViewAuthor.textAndVisible = item.manga.authors.joinToString(\", \")\n\t\tbinding.progressView.setProgress(\n\t\t\tvalue = item.progress,\n\t\t\tanimate = ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads,\n\t\t)\n\t\twith(binding.iconsView) {\n\t\t\tclearIcons()\n\t\t\tif (item.isSaved) addIcon(R.drawable.ic_storage)\n\t\t\tif (item.isFavorite) addIcon(R.drawable.ic_heart_outline)\n\t\t\tisVisible = iconsCount > 0\n\t\t}\n\t\tbinding.imageViewCover.setImageAsync(item.coverUrl, item.manga)\n\t\tbinding.textViewTags.text = item.tags.joinToString(separator = \", \") { it.title ?: \"\" }\n\t\tbinding.badge.number = item.counter\n\t\tbinding.badge.isVisible = item.counter > 0\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemMangaListBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaCompactListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\n\nfun mangaListItemAD(\n\tclickListener: OnListItemClickListener<MangaListModel>,\n) = adapterDelegateViewBinding<MangaCompactListModel, ListModel, ItemMangaListBinding>(\n\t{ inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)\n\n\tbind {\n\t\titemView.setTooltipCompat(item.getSummary(context))\n\t\tbinding.textViewTitle.text = item.title\n\t\tbinding.textViewSubtitle.textAndVisible = item.subtitle\n\t\tbinding.imageViewCover.setImageAsync(item.coverUrl, item.manga)\n\t\tbinding.badge.number = item.counter\n\t\tbinding.badge.isVisible = item.counter > 0\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListListener.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport android.view.View\nimport org.koitharu.kotatsu.core.ui.widgets.TipView\n\ninterface MangaListListener : MangaDetailsClickListener, ListStateHolderListener, ListHeaderClickListener,\n\tTipView.OnButtonClickListener, QuickFilterClickListener {\n\n\tfun onFilterClick(view: View?)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/QuickFilterAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.databinding.ItemQuickFilterBinding\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.QuickFilter\n\nfun quickFilterAD(\n\tlistener: QuickFilterClickListener,\n) = adapterDelegateViewBinding<QuickFilter, ListModel, ItemQuickFilterBinding>(\n\t{ layoutInflater, parent -> ItemQuickFilterBinding.inflate(layoutInflater, parent, false) }\n) {\n\n\tbinding.chipsTags.onChipClickListener = ChipsView.OnChipClickListener { chip, data ->\n\t\tif (data is ListFilterOption) {\n\t\t\tlistener.onFilterOptionClick(data)\n\t\t}\n\t}\n\n\tbind {\n\t\tbinding.chipsTags.setChips(item.items)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/QuickFilterClickListener.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\n\ninterface QuickFilterClickListener {\n\n\tfun onFilterOptionClick(option: ListFilterOption)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TipAD.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.widgets.TipView\nimport org.koitharu.kotatsu.databinding.ItemTip2Binding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.TipModel\n\nfun tipAD(\n\tlistener: TipView.OnButtonClickListener,\n) = adapterDelegateViewBinding<TipModel, ListModel, ItemTip2Binding>(\n\t{ layoutInflater, parent -> ItemTip2Binding.inflate(layoutInflater, parent, false) }\n) {\n\n\tbinding.root.onButtonClickListener = listener\n\n\tbind {\n\t\twith(binding.root) {\n\t\t\ttag = item\n\t\t\tsetTitle(item.title)\n\t\t\tsetText(item.text)\n\t\t\tsetIcon(item.icon)\n\t\t\tsetPrimaryButtonText(item.primaryButtonText)\n\t\t\tsetSecondaryButtonText(item.secondaryButtonText)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TypedListSpacingDecoration.kt",
    "content": "package org.koitharu.kotatsu.list.ui.adapter\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport org.koitharu.kotatsu.R\n\nclass TypedListSpacingDecoration(\n\tcontext: Context,\n\tprivate val addHorizontalPadding: Boolean,\n) : ItemDecoration() {\n\n\tprivate val spacingSmall = context.resources.getDimensionPixelOffset(R.dimen.list_spacing_small)\n\tprivate val spacingNormal =\n\t\tcontext.resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)\n\tprivate val spacingLarge = context.resources.getDimensionPixelOffset(R.dimen.list_spacing_large)\n\n\toverride fun getItemOffsets(\n\t\toutRect: Rect,\n\t\tview: View,\n\t\tparent: RecyclerView,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\tval itemType = parent.getChildViewHolder(view)?.itemViewType?.let {\n\t\t\tListItemType.entries.getOrNull(it)\n\t\t}\n\t\twhen (itemType) {\n\t\t\tListItemType.FILTER_SORT,\n\t\t\tListItemType.FILTER_TAG,\n\t\t\tListItemType.FILTER_TAG_MULTI,\n\t\t\tListItemType.FILTER_STATE,\n\t\t\tListItemType.FILTER_LANGUAGE,\n\t\t\tListItemType.QUICK_FILTER,\n\t\t\t\t-> outRect.set(0)\n\n\t\t\tListItemType.HEADER,\n\t\t\tListItemType.FEED,\n\t\t\tListItemType.EXPLORE_SOURCE_LIST,\n\t\t\tListItemType.MANGA_SCROBBLING,\n\t\t\tListItemType.MANGA_LIST,\n\t\t\t\t-> outRect.set(0)\n\n\t\t\tListItemType.DOWNLOAD,\n\t\t\tListItemType.HINT_EMPTY,\n\t\t\tListItemType.MANGA_LIST_DETAILED,\n\t\t\t\t-> outRect.set(spacingNormal)\n\n\t\t\tListItemType.PAGE_THUMB -> outRect.set(spacingNormal)\n\t\t\tListItemType.MANGA_GRID -> outRect.set(0)\n\n\t\t\tListItemType.EXPLORE_BUTTONS -> outRect.set(spacingNormal)\n\n\t\t\tListItemType.FOOTER_LOADING,\n\t\t\tListItemType.FOOTER_ERROR,\n\t\t\tListItemType.FOOTER_BUTTON,\n\t\t\tListItemType.STATE_LOADING,\n\t\t\tListItemType.STATE_ERROR,\n\t\t\tListItemType.STATE_EMPTY,\n\t\t\tListItemType.EXPLORE_SOURCE_GRID,\n\t\t\tListItemType.EXPLORE_SUGGESTION,\n\t\t\tListItemType.MANGA_NESTED_GROUP,\n\t\t\tListItemType.CATEGORY_LARGE,\n\t\t\tListItemType.NAV_ITEM,\n\t\t\tListItemType.CHAPTER_LIST,\n\t\t\tListItemType.INFO,\n\t\t\tnull,\n\t\t\t\t-> outRect.set(0)\n\n\t\t\tListItemType.CHAPTER_GRID -> outRect.set(spacingSmall)\n\n\t\t\tListItemType.TIP -> outRect.set(0) // TODO\n\t\t}\n\t\tif (addHorizontalPadding && !itemType.isEdgeToEdge()) {\n\t\t\toutRect.set(\n\t\t\t\toutRect.left + spacingNormal,\n\t\t\t\toutRect.top,\n\t\t\t\toutRect.right + spacingNormal,\n\t\t\t\toutRect.bottom,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun Rect.set(spacing: Int) = set(spacing, spacing, spacing, spacing)\n\n\tprivate fun ListItemType?.isEdgeToEdge() = this == ListItemType.MANGA_NESTED_GROUP\n\t\t|| this == ListItemType.FILTER_SORT\n\t\t|| this == ListItemType.FILTER_TAG\n\t\t|| this == ListItemType.CHAPTER_LIST\n\t\t|| this == ListItemType.CHAPTER_GRID\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigBottomSheet.kt",
    "content": "package org.koitharu.kotatsu.list.ui.config\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.AdapterView\nimport android.widget.ArrayAdapter\nimport android.widget.CompoundButton\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.button.MaterialButtonToggleGroup\nimport com.google.android.material.slider.Slider\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.setValueRounded\nimport org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter\nimport org.koitharu.kotatsu.databinding.SheetListModeBinding\n\n@AndroidEntryPoint\nclass ListConfigBottomSheet :\n\tBaseAdaptiveSheet<SheetListModeBinding>(),\n\tSlider.OnChangeListener,\n\tMaterialButtonToggleGroup.OnButtonCheckedListener, CompoundButton.OnCheckedChangeListener,\n\tAdapterView.OnItemSelectedListener {\n\n\tprivate val viewModel by viewModels<ListConfigViewModel>()\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = SheetListModeBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: SheetListModeBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval mode = viewModel.listMode\n\t\tbinding.buttonList.isChecked = mode == ListMode.LIST\n\t\tbinding.buttonListDetailed.isChecked = mode == ListMode.DETAILED_LIST\n\t\tbinding.buttonGrid.isChecked = mode == ListMode.GRID\n\t\tbinding.textViewGridTitle.isVisible = mode == ListMode.GRID\n\t\tbinding.sliderGrid.isVisible = mode == ListMode.GRID\n\n\t\tbinding.sliderGrid.setLabelFormatter(IntPercentLabelFormatter(binding.root.context))\n\t\tbinding.sliderGrid.setValueRounded(viewModel.gridSize.toFloat())\n\t\tbinding.sliderGrid.addOnChangeListener(this)\n\n\t\tbinding.checkableGroup.addOnButtonCheckedListener(this)\n\n\t\tbinding.switchGrouping.isVisible = viewModel.isGroupingSupported\n\t\tif (viewModel.isGroupingSupported) {\n\t\t\tbinding.switchGrouping.isEnabled = viewModel.isGroupingAvailable\n\t\t}\n\t\tbinding.switchGrouping.isChecked = viewModel.isGroupingEnabled\n\t\tbinding.switchGrouping.setOnCheckedChangeListener(this)\n\n\t\tval sortOrders = viewModel.getSortOrders()\n\t\tif (sortOrders != null) {\n\t\t\tbinding.textViewOrderTitle.isVisible = true\n\t\t\tbinding.spinnerOrder.adapter = ArrayAdapter(\n\t\t\t\tbinding.spinnerOrder.context,\n\t\t\t\tandroid.R.layout.simple_spinner_dropdown_item,\n\t\t\t\tandroid.R.id.text1,\n\t\t\t\tsortOrders.map { binding.spinnerOrder.context.getString(it.titleResId) },\n\t\t\t)\n\t\t\tval selected = sortOrders.indexOf(viewModel.getSelectedSortOrder())\n\t\t\tif (selected >= 0) {\n\t\t\t\tbinding.spinnerOrder.setSelection(selected, false)\n\t\t\t}\n\t\t\tbinding.spinnerOrder.onItemSelectedListener = this\n\t\t\tbinding.cardOrder.isVisible = true\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tviewBinding?.scrollView?.updatePadding(\n\t\t\tbottom = insets.getInsets(typeMask).bottom,\n\t\t)\n\t\treturn insets.consume(v, typeMask, bottom = true)\n\t}\n\n\toverride fun onButtonChecked(group: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean) {\n\t\tif (!isChecked) {\n\t\t\treturn\n\t\t}\n\t\tval mode = when (checkedId) {\n\t\t\tR.id.button_list -> ListMode.LIST\n\t\t\tR.id.button_list_detailed -> ListMode.DETAILED_LIST\n\t\t\tR.id.button_grid -> ListMode.GRID\n\t\t\telse -> return\n\t\t}\n\t\trequireViewBinding().textViewGridTitle.isVisible = mode == ListMode.GRID\n\t\trequireViewBinding().sliderGrid.isVisible = mode == ListMode.GRID\n\t\tviewModel.listMode = mode\n\t}\n\n\toverride fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {\n\t\twhen (buttonView.id) {\n\t\t\tR.id.switch_grouping -> viewModel.isGroupingEnabled = isChecked\n\t\t}\n\t}\n\n\toverride fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {\n\t\tif (fromUser) {\n\t\t\tviewModel.gridSize = value.toInt()\n\t\t}\n\t}\n\n\toverride fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {\n\t\twhen (parent.id) {\n\t\t\tR.id.spinner_order -> {\n\t\t\t\tviewModel.setSortOrder(position)\n\t\t\t\tviewBinding?.switchGrouping?.isEnabled = viewModel.isGroupingAvailable\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onNothingSelected(parent: AdapterView<*>?) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigSection.kt",
    "content": "package org.koitharu.kotatsu.list.ui.config\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\nsealed interface ListConfigSection : Parcelable {\n\n\t@Parcelize\n\tdata object History : ListConfigSection\n\n\t@Parcelize\n\tdata object General : ListConfigSection\n\n\t@Parcelize\n\tdata class Favorites(\n\t\tval categoryId: Long,\n\t) : ListConfigSection\n\n\t@Parcelize\n\tdata object Suggestions : ListConfigSection\n\n\t@Parcelize\n\tdata object Updated : ListConfigSection\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.config\n\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.core.util.ext.sortedByOrdinal\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID\nimport org.koitharu.kotatsu.list.domain.ListSortOrder\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ListConfigViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val settings: AppSettings,\n\tprivate val favouritesRepository: FavouritesRepository,\n) : BaseViewModel() {\n\n\tval section = savedStateHandle.require<ListConfigSection>(AppRouter.KEY_LIST_SECTION)\n\n\tvar listMode: ListMode\n\t\tget() = when (section) {\n\t\t\tis ListConfigSection.Favorites -> settings.favoritesListMode\n\t\t\tListConfigSection.History -> settings.historyListMode\n\t\t\tListConfigSection.Suggestions -> settings.suggestionsListMode\n\t\t\tListConfigSection.General,\n\t\t\tListConfigSection.Updated -> settings.listMode\n\t\t}\n\t\tset(value) {\n\t\t\twhen (section) {\n\t\t\t\tis ListConfigSection.Favorites -> settings.favoritesListMode = value\n\t\t\t\tListConfigSection.History -> settings.historyListMode = value\n\t\t\t\tListConfigSection.Suggestions -> settings.suggestionsListMode = value\n\t\t\t\tListConfigSection.Updated,\n\t\t\t\tListConfigSection.General -> settings.listMode = value\n\t\t\t}\n\t\t}\n\n\tvar gridSize: Int\n\t\tget() = settings.gridSize\n\t\tset(value) {\n\t\t\tsettings.gridSize = value\n\t\t}\n\n\tval isGroupingSupported: Boolean\n\t\tget() = section == ListConfigSection.History || section == ListConfigSection.Updated\n\n\tval isGroupingAvailable: Boolean\n\t\tget() = when (section) {\n\t\t\tListConfigSection.History -> settings.historySortOrder.isGroupingSupported()\n\t\t\tListConfigSection.Updated -> true\n\t\t\telse -> false\n\t\t}\n\n\tvar isGroupingEnabled: Boolean\n\t\tget() = when (section) {\n\t\t\tListConfigSection.History -> settings.isHistoryGroupingEnabled\n\t\t\tListConfigSection.Updated -> settings.isUpdatedGroupingEnabled\n\t\t\telse -> false\n\t\t}\n\t\tset(value) = when (section) {\n\t\t\tListConfigSection.History -> settings.isHistoryGroupingEnabled = value\n\t\t\tListConfigSection.Updated -> settings.isUpdatedGroupingEnabled = value\n\t\t\telse -> Unit\n\t\t}\n\n\tfun getSortOrders(): List<ListSortOrder>? = when (section) {\n\t\tis ListConfigSection.Favorites -> ListSortOrder.FAVORITES\n\t\tListConfigSection.General -> null\n\t\tListConfigSection.History -> ListSortOrder.HISTORY\n\t\tListConfigSection.Suggestions -> ListSortOrder.SUGGESTIONS\n\t\tListConfigSection.Updated -> null\n\t}?.sortedByOrdinal()\n\n\tfun getSelectedSortOrder(): ListSortOrder? = when (section) {\n\t\tis ListConfigSection.Favorites -> getCategorySortOrder(section.categoryId)\n\t\tListConfigSection.General -> null\n\t\tListConfigSection.Updated -> null\n\t\tListConfigSection.History -> settings.historySortOrder\n\t\tListConfigSection.Suggestions -> ListSortOrder.RELEVANCE\n\t}\n\n\tfun setSortOrder(position: Int) {\n\t\tval value = getSortOrders()?.getOrNull(position) ?: return\n\t\twhen (section) {\n\t\t\tis ListConfigSection.Favorites -> launchJob {\n\t\t\t\tif (section.categoryId == NO_ID) {\n\t\t\t\t\tsettings.allFavoritesSortOrder = value\n\t\t\t\t} else {\n\t\t\t\t\tfavouritesRepository.setCategoryOrder(section.categoryId, value)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tListConfigSection.General -> Unit\n\t\t\tListConfigSection.History -> settings.historySortOrder = value\n\n\t\t\tListConfigSection.Suggestions -> Unit\n\t\t\tListConfigSection.Updated -> Unit\n\t\t}\n\t}\n\n\tprivate fun getCategorySortOrder(id: Long): ListSortOrder = if (id == NO_ID) {\n\t\tsettings.allFavoritesSortOrder\n\t} else runBlocking {\n\t\trunCatchingCancellable {\n\t\t\tfavouritesRepository.getCategory(id).order\n\t\t}.getOrElse {\n\t\t\tsettings.allFavoritesSortOrder\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ButtonFooter.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport androidx.annotation.StringRes\n\ndata class ButtonFooter(\n\t@StringRes val textResId: Int,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ButtonFooter && textResId == other.textResId\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyHint.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\ndata class EmptyHint(\n\t@DrawableRes val icon: Int,\n\t@StringRes val textPrimary: Int,\n\t@StringRes val textSecondary: Int,\n\t@StringRes val actionStringRes: Int,\n) : ListModel {\n\n\tfun toState() = EmptyState(icon, textPrimary, textSecondary, actionStringRes)\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is EmptyHint && textPrimary == other.textPrimary\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/EmptyState.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\ndata class EmptyState(\n\t@DrawableRes val icon: Int,\n\t@StringRes val textPrimary: Int,\n\t@StringRes val textSecondary: Int,\n\t@StringRes val actionStringRes: Int,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is EmptyState\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorFooter.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\ndata class ErrorFooter(\n\tval exception: Throwable,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ErrorFooter && exception == other.exception\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ErrorState.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\ndata class ErrorState(\n\tval exception: Throwable,\n\t@DrawableRes val icon: Int,\n\tval canRetry: Boolean,\n\t@StringRes val buttonText: Int,\n\t@StringRes val secondaryButtonText: Int,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel) = other is ErrorState\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/InfoModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\ndata class InfoModel(\n\tval key: String,\n\t@StringRes val title: Int,\n\t@StringRes val text: Int,\n\t@DrawableRes val icon: Int,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is InfoModel && other.key == key\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListHeader.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport android.content.Context\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.core.model.getLocalizedTitle\nimport org.koitharu.kotatsu.core.ui.model.DateTimeAgo\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\n\ndata class ListHeader private constructor(\n\tprivate val textRaw: Any,\n\t@StringRes val buttonTextRes: Int,\n\tval payload: Any?,\n\tval badge: String?,\n) : ListModel {\n\n\tconstructor(\n\t\ttext: CharSequence,\n\t\t@StringRes buttonTextRes: Int = 0,\n\t\tpayload: Any? = null,\n\t\tbadge: String? = null,\n\t) : this(textRaw = text, buttonTextRes, payload, badge)\n\n\tconstructor(\n\t\t@StringRes textRes: Int,\n\t\t@StringRes buttonTextRes: Int = 0,\n\t\tpayload: Any? = null,\n\t\tbadge: String? = null,\n\t) : this(textRaw = textRes, buttonTextRes, payload, badge)\n\n\tconstructor(\n\t\tchapter: MangaChapter,\n\t\t@StringRes buttonTextRes: Int = 0,\n\t\tpayload: Any? = null,\n\t\tbadge: String? = null,\n\t) : this(textRaw = chapter, buttonTextRes, payload, badge)\n\n\tconstructor(\n\t\tdateTimeAgo: DateTimeAgo,\n\t\t@StringRes buttonTextRes: Int = 0,\n\t\tpayload: Any? = null,\n\t\tbadge: String? = null,\n\t) : this(textRaw = dateTimeAgo, buttonTextRes, payload, badge)\n\n\tfun getText(context: Context): CharSequence? = when (textRaw) {\n\t\tis CharSequence -> textRaw\n\t\tis Int -> if (textRaw != 0) context.getString(textRaw) else null\n\t\tis DateTimeAgo -> textRaw.format(context)\n\t\tis MangaChapter -> textRaw.getLocalizedTitle(context.resources)\n\t\telse -> null\n\t}\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ListHeader && textRaw == other.textRaw\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\ninterface ListModel {\n\n\toverride fun equals(other: Any?): Boolean\n\n\tfun areItemsTheSame(other: ListModel): Boolean\n\n\tfun getChangePayload(previousState: ListModel): Any? = null\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelExt.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.util.ext.getDisplayIcon\nimport org.koitharu.kotatsu.parsers.util.ifZero\n\nfun Throwable.toErrorState(canRetry: Boolean = true, @StringRes secondaryAction: Int = 0) = ErrorState(\n\texception = this,\n\ticon = getDisplayIcon(),\n\tcanRetry = canRetry,\n\tbuttonText = ExceptionResolver.getResolveStringId(this).ifZero { R.string.try_again },\n\tsecondaryButtonText = secondaryAction,\n)\n\nfun Throwable.toErrorFooter() = ErrorFooter(\n\texception = this,\n)\n\noperator fun ListModel.plus(list: List<ListModel>): List<ListModel> {\n\tval result = ArrayList<ListModel>(list.size + 1)\n\tresult.add(this)\n\tresult.addAll(list)\n\treturn result\n}\n\noperator fun ListModel.plus(other: ListModel): List<ListModel> = listOf(this, other)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingFooter.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\ndata class LoadingFooter @JvmOverloads constructor(\n\tval key: Int = 0,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is LoadingFooter && key == other.key\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/LoadingState.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nobject LoadingState : ListModel {\n\n\toverride fun equals(other: Any?): Boolean = other === LoadingState\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is LoadingState\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaCompactListModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.parsers.model.Manga\n\ndata class MangaCompactListModel(\n\toverride val manga: Manga,\n\toverride val override: MangaOverride?,\n\tval subtitle: String,\n\toverride val counter: Int,\n) : MangaListModel()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaDetailedListModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_ANYTHING_CHANGED\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED\nimport org.koitharu.kotatsu.parsers.model.Manga\n\ndata class MangaDetailedListModel(\n\toverride val manga: Manga,\n\toverride val override: MangaOverride?,\n\tval subtitle: String?,\n\toverride val counter: Int,\n\tval progress: ReadingProgress?,\n\tval isFavorite: Boolean,\n\tval isSaved: Boolean,\n\tval tags: List<ChipsView.ChipModel>,\n) : MangaListModel() {\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is MangaDetailedListModel || previousState.manga != manga -> null\n\n\t\tpreviousState.progress != progress -> PAYLOAD_PROGRESS_CHANGED\n\t\tpreviousState.isFavorite != isFavorite ||\n\t\t\tpreviousState.isSaved != isSaved -> PAYLOAD_ANYTHING_CHANGED\n\n\t\telse -> super.getChangePayload(previousState)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_ANYTHING_CHANGED\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED\nimport org.koitharu.kotatsu.parsers.model.Manga\n\ndata class MangaGridModel(\n\toverride val manga: Manga,\n\toverride val override: MangaOverride?,\n\toverride val counter: Int,\n\tval progress: ReadingProgress?,\n\tval isFavorite: Boolean,\n\tval isSaved: Boolean,\n) : MangaListModel() {\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is MangaGridModel || previousState.manga != manga -> null\n\n\t\tpreviousState.progress != progress -> PAYLOAD_PROGRESS_CHANGED\n\t\tpreviousState.isFavorite != isFavorite ||\n\t\t\tpreviousState.isSaved != isSaved -> PAYLOAD_ANYTHING_CHANGED\n\n\t\telse -> super.getChangePayload(previousState)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport android.content.Context\nimport androidx.core.text.bold\nimport androidx.core.text.buildSpannedString\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.withOverride\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_ANYTHING_CHANGED\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\n\nsealed class MangaListModel : ListModel {\n\n\tabstract val override: MangaOverride?\n\tabstract val manga: Manga\n\tabstract val counter: Int\n\n\tval id: Long\n\t\tget() = manga.id\n\n\tval title: String\n\t\tget() = override?.title.ifNullOrEmpty { manga.title }\n\n\tval coverUrl: String?\n\t\tget() = override?.coverUrl.ifNullOrEmpty { manga.coverUrl }\n\n\tval source: MangaSource\n\t\tget() = manga.source\n\n\tfun toMangaWithOverride() = manga.withOverride(override)\n\n\topen fun getSummary(context: Context): CharSequence = buildSpannedString {\n\t\tbold {\n\t\t\tappend(manga.title)\n\t\t}\n\t\tappendLine()\n\t\tif (manga.tags.isNotEmpty()) {\n\t\t\tmanga.tags.joinTo(this) { it.title }\n\t\t\tappendLine()\n\t\t}\n\t\tappend(manga.source.getTitle(context))\n\t}\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is MangaListModel && other.javaClass == javaClass && id == other.id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is MangaListModel || previousState.manga != manga -> null\n\t\tpreviousState.counter != counter -> PAYLOAD_ANYTHING_CHANGED\n\t\telse -> null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/QuickFilter.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\n\ndata class QuickFilter(\n\tval items: List<ChipsView.ChipModel>,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean = other is QuickFilter\n\n\toverride fun getChangePayload(previousState: ListModel) = ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/TipModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\ndata class TipModel(\n\tval key: String,\n\t@StringRes val title: Int,\n\t@StringRes val text: Int,\n\t@DrawableRes val icon: Int,\n\t@StringRes val primaryButtonText: Int,\n\t@StringRes val secondaryButtonText: Int,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is TipModel && other.key == key\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt",
    "content": "package org.koitharu.kotatsu.list.ui.preview\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.chip.Chip\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.FragmentPreviewBinding\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.search.ui.MangaListActivity\n\n@AndroidEntryPoint\nclass PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickListener, ChipsView.OnChipClickListener {\n\n\tprivate val viewModel: PreviewViewModel by viewModels()\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPreviewBinding {\n\t\treturn FragmentPreviewBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: FragmentPreviewBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.buttonClose.isVisible = activity is MangaListActivity\n\t\tbinding.buttonClose.setOnClickListener(this)\n\t\tbinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()\n\t\tbinding.chipsTags.onChipClickListener = this\n\t\tbinding.textViewAuthor.setOnClickListener(this)\n\t\tbinding.imageViewCover.setOnClickListener(this)\n\t\tbinding.buttonOpen.setOnClickListener(this)\n\t\tbinding.buttonRead.setOnClickListener(this)\n\n\t\tviewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated)\n\t\tviewModel.footer.observe(viewLifecycleOwner, ::onFooterUpdated)\n\t\tviewModel.tagsChips.observe(viewLifecycleOwner, ::onTagsChipsChanged)\n\t\tviewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat = insets\n\n\toverride fun onClick(v: View) {\n\t\tval manga = viewModel.manga.value\n\t\twhen (v.id) {\n\t\t\tR.id.button_close -> closeSelf()\n\t\t\tR.id.button_open -> router.openDetails(manga)\n\t\t\tR.id.button_read -> router.openReader(manga)\n\n\t\t\tR.id.textView_author -> router.showAuthorDialog(\n\t\t\t\tauthor = manga.authors.firstOrNull() ?: return,\n\t\t\t\tsource = manga.source,\n\t\t\t)\n\n\t\t\tR.id.imageView_cover -> router.openImage(\n\t\t\t\turl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } ?: return,\n\t\t\t\tsource = manga.source,\n\t\t\t\tanchor = v,\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun onChipClick(chip: Chip, data: Any?) {\n\t\tval tag = data as? MangaTag ?: return\n\t\tval filter = FilterCoordinator.find(this)\n\t\tif (filter == null) {\n\t\t\trouter.openList(tag)\n\t\t} else {\n\t\t\tfilter.toggleTag(tag, true)\n\t\t\tcloseSelf()\n\t\t}\n\t}\n\n\tprivate fun onMangaUpdated(manga: Manga) {\n\t\twith(requireViewBinding()) {\n\t\t\t// Main\n\t\t\tloadCover(manga)\n\t\t\ttextViewTitle.text = manga.title\n\t\t\ttextViewSubtitle.textAndVisible = manga.altTitles.firstOrNull()\n\t\t\ttextViewAuthor.textAndVisible = manga.authors.firstOrNull()\n\t\t\tif (manga.hasRating) {\n\t\t\t\tratingBar.rating = manga.rating * ratingBar.numStars\n\t\t\t\tratingBar.isVisible = true\n\t\t\t} else {\n\t\t\t\tratingBar.isVisible = false\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onFooterUpdated(footer: PreviewViewModel.FooterInfo?) {\n\t\twith(requireViewBinding()) {\n\t\t\tbuttonRead.isEnabled = footer != null\n\t\t\tbuttonRead.setText(\n\t\t\t\twhen {\n\t\t\t\t\tfooter == null -> R.string.loading_\n\t\t\t\t\tfooter.isIncognito -> R.string.incognito\n\t\t\t\t\tfooter.isInProgress() -> R.string._continue\n\t\t\t\t\telse -> R.string.read\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun onDescriptionChanged(description: CharSequence?) {\n\t\tval tv = viewBinding?.textViewDescription ?: return\n\t\twhen {\n\t\t\tdescription == null -> tv.setText(R.string.loading_)\n\t\t\tdescription.isBlank() -> tv.setText(R.string.no_description)\n\t\t\telse -> tv.setText(description, TextView.BufferType.NORMAL)\n\t\t}\n\t}\n\n\tprivate fun loadCover(manga: Manga) {\n\t\tval imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }\n\t\trequireViewBinding().imageViewCover.setImageAsync(imageUrl, manga)\n\t}\n\n\tprivate fun onTagsChipsChanged(chips: List<ChipsView.ChipModel>) {\n\t\trequireViewBinding().chipsTags.setChips(chips)\n\t}\n\n\tprivate fun closeSelf() {\n\t\t((activity as? MangaListActivity)?.hidePreview())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewViewModel.kt",
    "content": "package org.koitharu.kotatsu.list.ui.preview\n\nimport android.text.Html\nimport android.text.SpannableString\nimport android.text.Spanned\nimport android.text.style.ForegroundColorSpan\nimport androidx.core.text.getSpans\nimport androidx.core.text.parseAsHtml\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.distinctUntilChangedBy\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.transformLatest\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.model.getPreferredBranch\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.core.util.ext.sanitize\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PreviewViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val repositoryFactory: MangaRepository.Factory,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val imageGetter: Html.ImageGetter,\n) : BaseViewModel() {\n\n\tval manga = MutableStateFlow(\n\t\tsavedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga,\n\t)\n\n\tval footer = combine(\n\t\tmanga,\n\t\thistoryRepository.observeOne(manga.value.id),\n\t\tmanga.flatMapLatest { historyRepository.observeShouldSkip(it) }.distinctUntilChanged(),\n\t) { m, history, incognito ->\n\t\tif (m.chapters == null) {\n\t\t\treturn@combine null\n\t\t}\n\t\tval b = m.getPreferredBranch(history)\n\t\tval chapters = m.getChapters(b)\n\t\tFooterInfo(\n\t\t\tpercent = history?.percent ?: PROGRESS_NONE,\n\t\t\tcurrentChapter = history?.chapterId?.let {\n\t\t\t\tchapters.indexOfFirst { x -> x.id == it }\n\t\t\t} ?: -1,\n\t\t\ttotalChapters = chapters.size,\n\t\t\tisIncognito = incognito,\n\t\t)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, null)\n\n\tval description = manga\n\t\t.distinctUntilChangedBy { it.description.orEmpty() }\n\t\t.transformLatest {\n\t\t\tval description = it.description\n\t\t\tif (description.isNullOrEmpty()) {\n\t\t\t\temit(null)\n\t\t\t} else {\n\t\t\t\temit(description.parseAsHtml().filterSpans().sanitize())\n\t\t\t\temit(description.parseAsHtml(imageGetter = imageGetter).filterSpans())\n\t\t\t}\n\t\t}.combine(isLoading) { desc, loading ->\n\t\t\tif (loading) null else desc ?: \"\"\n\t\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), null)\n\n\tval tagsChips = manga.map {\n\t\tmangaListMapper.mapTags(it.tags)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tinit {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval repo = repositoryFactory.create(manga.value.source)\n\t\t\tmanga.value = repo.getDetails(manga.value)\n\t\t}\n\t}\n\n\tprivate fun Spanned.filterSpans(): CharSequence {\n\t\tval spannable = SpannableString.valueOf(this)\n\t\tval spans = spannable.getSpans<ForegroundColorSpan>()\n\t\tfor (span in spans) {\n\t\t\tspannable.removeSpan(span)\n\t\t}\n\t\treturn spannable.trim()\n\t}\n\n\tdata class FooterInfo(\n\t\tval currentChapter: Int,\n\t\tval totalChapters: Int,\n\t\tval isIncognito: Boolean,\n\t\tval percent: Float,\n\t) {\n\n\t\tfun isInProgress() = currentChapter >= 0\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/DynamicItemSizeResolver.kt",
    "content": "package org.koitharu.kotatsu.list.ui.size\n\nimport android.content.SharedPreferences\nimport android.content.res.Resources\nimport android.view.View\nimport android.widget.TextView\nimport androidx.annotation.StyleRes\nimport androidx.core.widget.TextViewCompat\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.history.ui.util.ReadingProgressView\nimport kotlin.math.roundToInt\n\nclass DynamicItemSizeResolver(\n\tresources: Resources,\n\tprivate val lifecycleOwner: LifecycleOwner,\n\tprivate val settings: AppSettings,\n\tprivate val adjustWidth: Boolean,\n) : ItemSizeResolver {\n\n\tprivate val gridWidth = resources.getDimension(R.dimen.preferred_grid_width)\n\tprivate val scaleFactor: Float\n\t\tget() = settings.gridSize / 100f\n\n\toverride val cellWidth: Int\n\t\tget() = (gridWidth * scaleFactor).roundToInt()\n\n\toverride fun attachToView(\n\t\tview: View,\n\t\ttextView: TextView?,\n\t\tprogressView: ReadingProgressView?\n\t) {\n\t\tval observer = SizeObserver(view, textView, progressView)\n\t\tview.addOnAttachStateChangeListener(observer)\n\t\tlifecycleOwner.lifecycle.addObserver(observer)\n\t\tif (view.isAttachedToWindow) {\n\t\t\tobserver.update()\n\t\t}\n\t}\n\n\tprivate inner class SizeObserver(\n\t\tprivate val view: View,\n\t\tprivate val textView: TextView?,\n\t\tprivate val progressView: ReadingProgressView?,\n\t) : DefaultLifecycleObserver, SharedPreferences.OnSharedPreferenceChangeListener, View.OnAttachStateChangeListener {\n\n\t\tprivate val widthThreshold = view.resources.getDimensionPixelSize(R.dimen.small_grid_width)\n\n\t\t@StyleRes\n\t\tprivate var prevTextAppearance = 0\n\n\t\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\t\tif (key == AppSettings.KEY_GRID_SIZE) {\n\t\t\t\tupdate()\n\t\t\t}\n\t\t}\n\n\t\toverride fun onViewAttachedToWindow(v: View) {\n\t\t\tsettings.subscribe(this)\n\t\t\tupdate()\n\t\t}\n\n\t\toverride fun onViewDetachedFromWindow(v: View) {\n\t\t\tsettings.unsubscribe(this)\n\t\t}\n\n\t\toverride fun onDestroy(owner: LifecycleOwner) {\n\t\t\tsuper.onDestroy(owner)\n\t\t\tsettings.unsubscribe(this)\n\t\t\tview.removeOnAttachStateChangeListener(this)\n\t\t}\n\n\t\tfun update() {\n\t\t\tval newWidth = cellWidth\n\t\t\ttextView?.adjustTextAppearance(newWidth)\n\t\t\tif (adjustWidth) {\n\t\t\t\tval lp = view.layoutParams\n\t\t\t\tif (lp.width != newWidth) {\n\t\t\t\t\tlp.width = newWidth\n\t\t\t\t\tview.layoutParams = lp\n\t\t\t\t}\n\t\t\t}\n\t\t\tprogressView?.adjustSize(newWidth)\n\t\t}\n\n\t\tprivate fun ReadingProgressView.adjustSize(width: Int) {\n\t\t\tval lp = layoutParams\n\t\t\tval size = resources.getDimensionPixelSize(\n\t\t\t\tif (width < widthThreshold) {\n\t\t\t\t\tR.dimen.card_indicator_size_small\n\t\t\t\t} else {\n\t\t\t\t\tR.dimen.card_indicator_size\n\t\t\t\t},\n\t\t\t)\n\t\t\tif (lp.width != size || lp.height != size) {\n\t\t\t\tlp.width = size\n\t\t\t\tlp.height = size\n\t\t\t\tlayoutParams = lp\n\t\t\t}\n\t\t}\n\n\t\tprivate fun TextView.adjustTextAppearance(width: Int) {\n\t\t\tval textAppearanceResId = if (width < widthThreshold) {\n\t\t\t\tR.style.TextAppearance_Kotatsu_GridTitle_Small\n\t\t\t} else {\n\t\t\t\tR.style.TextAppearance_Kotatsu_GridTitle\n\t\t\t}\n\t\t\tif (textAppearanceResId != prevTextAppearance) {\n\t\t\t\tprevTextAppearance = textAppearanceResId\n\t\t\t\tTextViewCompat.setTextAppearance(this, textAppearanceResId)\n\t\t\t\trequestLayout()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/ItemSizeResolver.kt",
    "content": "package org.koitharu.kotatsu.list.ui.size\n\nimport android.view.View\nimport android.widget.TextView\nimport org.koitharu.kotatsu.history.ui.util.ReadingProgressView\n\ninterface ItemSizeResolver {\n\n\tval cellWidth: Int\n\n\tfun attachToView(\n\t\tview: View,\n\t\ttextView: TextView?,\n\t\tprogressView: ReadingProgressView?,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/StaticItemSizeResolver.kt",
    "content": "package org.koitharu.kotatsu.list.ui.size\n\nimport android.view.View\nimport android.widget.TextView\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.widget.TextViewCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.history.ui.util.ReadingProgressView\n\nclass StaticItemSizeResolver(\n\toverride val cellWidth: Int,\n) : ItemSizeResolver {\n\n\tprivate var widthThreshold: Int = -1\n\tprivate var textAppearanceResId = R.style.TextAppearance_Kotatsu_GridTitle\n\n\toverride fun attachToView(\n\t\tview: View,\n\t\ttextView: TextView?,\n\t\tprogressView: ReadingProgressView?\n\t) {\n\t\tif (widthThreshold == -1) {\n\t\t\twidthThreshold = view.resources.getDimensionPixelSize(R.dimen.small_grid_width)\n\t\t\ttextAppearanceResId = if (cellWidth < widthThreshold) {\n\t\t\t\tR.style.TextAppearance_Kotatsu_GridTitle_Small\n\t\t\t} else {\n\t\t\t\tR.style.TextAppearance_Kotatsu_GridTitle\n\t\t\t}\n\t\t}\n\t\tif (textView != null) {\n\t\t\tTextViewCompat.setTextAppearance(textView, textAppearanceResId)\n\t\t}\n\t\tview.updateLayoutParams {\n\t\t\twidth = cellWidth\n\t\t}\n\t\tprogressView?.adjustSize()\n\t}\n\n\tprivate fun ReadingProgressView.adjustSize() {\n\t\tval lp = layoutParams\n\t\tval size = resources.getDimensionPixelSize(\n\t\t\tif (cellWidth < widthThreshold) {\n\t\t\t\tR.dimen.card_indicator_size_small\n\t\t\t} else {\n\t\t\t\tR.dimen.card_indicator_size\n\t\t\t},\n\t\t)\n\t\tif (lp.width != size || lp.height != size) {\n\t\t\tlp.width = size\n\t\t\tlp.height = size\n\t\t\tlayoutParams = lp\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/CacheDir.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nenum class CacheDir(val dir: String) {\n\n\tTHUMBS(\"image_cache\"),\n\tFAVICONS(\"favicons\"),\n\tPAGES(\"pages\");\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/Caches.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport javax.inject.Qualifier\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class PageCache\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class FaviconCache\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/CbzFilter.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport java.io.File\n\nprivate fun isZipExtension(ext: String?): Boolean {\n\treturn ext.equals(\"cbz\", ignoreCase = true) || ext.equals(\"zip\", ignoreCase = true)\n}\n\nfun hasZipExtension(string: String): Boolean {\n\tval ext = string.substringAfterLast('.', \"\")\n\treturn isZipExtension(ext)\n}\n\nval File.isZipArchive: Boolean\n\tget() = isFile && isZipExtension(extension)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/LocalMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.firstOrNull\nimport kotlinx.coroutines.flow.toCollection\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runInterruptible\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.AlphanumComparator\nimport org.koitharu.kotatsu.core.util.ext.deleteAwait\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.takeIfWriteable\nimport org.koitharu.kotatsu.core.util.ext.withChildren\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndex\nimport org.koitharu.kotatsu.local.data.input.LocalMangaParser\nimport org.koitharu.kotatsu.local.data.output.LocalMangaOutput\nimport org.koitharu.kotatsu.local.data.output.LocalMangaUtil\nimport org.koitharu.kotatsu.local.domain.MangaLock\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities\nimport org.koitharu.kotatsu.parsers.model.MangaListFilterOptions\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.levenshteinDistance\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\nimport java.util.EnumSet\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\nprivate const val MAX_PARALLELISM = 4\nprivate const val FILENAME_SKIP = \".notamanga\"\n\n@Singleton\nclass LocalMangaRepository @Inject constructor(\n\tprivate val storageManager: LocalStorageManager,\n\tprivate val localMangaIndex: LocalMangaIndex,\n\t@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,\n\tprivate val settings: AppSettings,\n\tprivate val lock: MangaLock,\n) : MangaRepository {\n\n\toverride val source = LocalMangaSource\n\n\toverride val filterCapabilities: MangaListFilterCapabilities\n\t\tget() = MangaListFilterCapabilities(\n\t\t\tisMultipleTagsSupported = true,\n\t\t\tisTagsExclusionSupported = true,\n\t\t\tisSearchSupported = true,\n\t\t\tisSearchWithFiltersSupported = true,\n\t\t)\n\n\toverride val sortOrders: Set<SortOrder> = EnumSet.of(\n\t\tSortOrder.ALPHABETICAL,\n\t\tSortOrder.RATING,\n\t\tSortOrder.NEWEST,\n\t\tSortOrder.RELEVANCE,\n\t)\n\n\toverride var defaultSortOrder: SortOrder\n\t\tget() = settings.localListOrder\n\t\tset(value) {\n\t\t\tsettings.localListOrder = value\n\t\t}\n\n\toverride suspend fun getFilterOptions() = MangaListFilterOptions(\n\t\tavailableTags = localMangaIndex.getAvailableTags(\n\t\t\tskipNsfw = settings.isNsfwContentDisabled,\n\t\t).mapToSet { MangaTag(title = it, key = it, source = source) },\n\t\tavailableContentRating = if (!settings.isNsfwContentDisabled) {\n\t\t\tEnumSet.of(ContentRating.SAFE, ContentRating.ADULT)\n\t\t} else {\n\t\t\temptySet()\n\t\t},\n\t)\n\n\toverride suspend fun getList(offset: Int, order: SortOrder?, filter: MangaListFilter?): List<Manga> {\n\t\tif (offset > 0) {\n\t\t\treturn emptyList()\n\t\t}\n\t\tval list = getRawList()\n\t\tif (settings.isNsfwContentDisabled) {\n\t\t\tlist.removeAll { it.manga.isNsfw() }\n\t\t}\n\t\tif (filter != null) {\n\t\t\tval query = filter.query\n\t\t\tif (!query.isNullOrEmpty()) {\n\t\t\t\tlist.retainAll { x -> x.isMatchesQuery(query) }\n\t\t\t}\n\t\t\tif (filter.tags.isNotEmpty()) {\n\t\t\t\tlist.retainAll { x -> x.containsTags(filter.tags.mapToSet { it.title }) }\n\t\t\t}\n\t\t\tif (filter.tagsExclude.isNotEmpty()) {\n\t\t\t\tlist.removeAll { x -> x.containsAnyTag(filter.tagsExclude.mapToSet { it.title }) }\n\t\t\t}\n\t\t\tfilter.contentRating.singleOrNull()?.let { contentRating ->\n\t\t\t\tval isNsfw = contentRating == ContentRating.ADULT\n\t\t\t\tlist.retainAll { it.manga.isNsfw() == isNsfw }\n\t\t\t}\n\t\t\tif (!query.isNullOrEmpty() && order == SortOrder.RELEVANCE) {\n\t\t\t\tlist.sortBy { it.manga.title.levenshteinDistance(query) }\n\t\t\t}\n\t\t}\n\t\twhen (order) {\n\t\t\tSortOrder.ALPHABETICAL -> list.sortWith(compareBy(AlphanumComparator()) { x -> x.manga.title })\n\t\t\tSortOrder.RATING -> list.sortByDescending { it.manga.rating }\n\t\t\tSortOrder.NEWEST,\n\t\t\tSortOrder.UPDATED -> list.sortWith(compareBy({ -it.createdAt }, { it.manga.id }))\n\n\t\t\telse -> Unit\n\t\t}\n\t\treturn list.unwrap()\n\t}\n\n\toverride suspend fun getDetails(manga: Manga): Manga = when {\n\t\t!manga.isLocal -> requireNotNull(findSavedManga(manga, withDetails = true)?.manga) {\n\t\t\t\"Manga is not local or saved\"\n\t\t}\n\n\t\telse -> LocalMangaParser(manga.url.toUri()).getManga(withDetails = true).manga\n\t}\n\n\toverride suspend fun getPages(chapter: MangaChapter): List<MangaPage> {\n\t\treturn LocalMangaParser(chapter.url.toUri()).getPages(chapter)\n\t}\n\n\tsuspend fun delete(manga: Manga): Boolean {\n\t\tval file = manga.url.toUri().toFile()\n\t\tval result = file.deleteAwait()\n\t\tif (result) {\n\t\t\tlocalMangaIndex.delete(manga.id)\n\t\t\tlocalStorageChanges.emit(null)\n\t\t}\n\t\treturn result\n\t}\n\n\tsuspend fun deleteChapters(manga: Manga, ids: Set<Long>) = lock.withLock(manga) {\n\t\tval subject = if (manga.isLocal) manga else checkNotNull(findSavedManga(manga, withDetails = false)) {\n\t\t\t\"Manga is not stored on local storage\"\n\t\t}.manga\n\t\tLocalMangaUtil(subject).deleteChapters(ids)\n\t\tval updated = getDetails(subject)\n\t\tlocalStorageChanges.emit(LocalManga(updated))\n\t}\n\n\tsuspend fun getRemoteManga(localManga: Manga): Manga? {\n\t\treturn runCatchingCancellable {\n\t\t\tLocalMangaParser(localManga.url.toUri()).getMangaInfo()?.takeUnless { it.isLocal }\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}.getOrNull()\n\t}\n\n\tsuspend fun findSavedManga(remoteManga: Manga, withDetails: Boolean = true): LocalManga? = runCatchingCancellable {\n\t\t// very fast path\n\t\tlocalMangaIndex.get(remoteManga.id, withDetails)?.let { cached ->\n\t\t\treturn@runCatchingCancellable cached\n\t\t}\n\t\t// fast path\n\t\tLocalMangaParser.find(storageManager.getReadableDirs(), remoteManga)?.let {\n\t\t\treturn it.getManga(withDetails)\n\t\t}\n\t\t// slow path\n\t\tval files = getAllFiles()\n\t\treturn channelFlow {\n\t\t\tfor (file in files) {\n\t\t\t\tlaunch {\n\t\t\t\t\tval mangaInput = LocalMangaParser.getOrNull(file)\n\t\t\t\t\trunCatchingCancellable {\n\t\t\t\t\t\tval mangaInfo = mangaInput?.getMangaInfo()\n\t\t\t\t\t\tif (mangaInfo != null && mangaInfo.id == remoteManga.id) {\n\t\t\t\t\t\t\tsend(mangaInput)\n\t\t\t\t\t\t}\n\t\t\t\t\t}.onFailure {\n\t\t\t\t\t\tit.printStackTraceDebug()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}.firstOrNull()?.getManga(withDetails)\n\t}.onSuccess { x: LocalManga? ->\n\t\tif (x != null) {\n\t\t\tlocalMangaIndex.put(x)\n\t\t}\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n\n\toverride suspend fun getPageUrl(page: MangaPage) = page.url\n\n\toverride suspend fun getRelated(seed: Manga): List<Manga> = emptyList()\n\n\tsuspend fun getOutputDir(manga: Manga, fallback: File?): File? {\n\t\tval defaultDir = fallback?.takeIfWriteable() ?: storageManager.getDefaultWriteableDir()\n\t\tif (defaultDir != null && LocalMangaOutput.get(defaultDir, manga) != null) {\n\t\t\treturn defaultDir\n\t\t}\n\t\treturn storageManager.getWriteableDirs()\n\t\t\t.firstOrNull {\n\t\t\t\tLocalMangaOutput.get(it, manga) != null\n\t\t\t} ?: defaultDir\n\t}\n\n\tsuspend fun cleanup(): Boolean {\n\t\tif (lock.isNotEmpty()) {\n\t\t\treturn false\n\t\t}\n\t\tval dirs = storageManager.getWriteableDirs()\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\tval filter = TempFileFilter()\n\t\t\tdirs.forEach { dir ->\n\t\t\t\tdir.withChildren { children ->\n\t\t\t\t\tchildren.forEach { child ->\n\t\t\t\t\t\tif (filter.accept(child)) {\n\t\t\t\t\t\t\tchild.deleteRecursively()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tfun getRawListAsFlow(): Flow<LocalManga> = channelFlow {\n\t\tval files = getAllFiles()\n\t\tval dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)\n\t\tfor (file in files) {\n\t\t\tlaunch(dispatcher) {\n\t\t\t\trunCatchingCancellable {\n\t\t\t\t\tLocalMangaParser.getOrNull(file)?.getManga(withDetails = false)\n\t\t\t\t}.onFailure { e ->\n\t\t\t\t\te.printStackTraceDebug()\n\t\t\t\t}.onSuccess { m ->\n\t\t\t\t\tif (m != null) send(m)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun getRawList(): ArrayList<LocalManga> = getRawListAsFlow().toCollection(ArrayList())\n\n\tprivate suspend fun getAllFiles() = storageManager.getReadableDirs()\n\t\t.asSequence()\n\t\t.flatMap { dir ->\n\t\t\tdir.withChildren { children -> children.filterNot { it.isHidden || it.shouldSkip() }.toList() }\n\t\t}\n\n\tprivate fun Collection<LocalManga>.unwrap(): List<Manga> = map { it.manga }\n\n\tprivate fun File.shouldSkip(): Boolean = isDirectory && File(this, FILENAME_SKIP).exists()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/LocalStorageCache.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.os.StatFs\nimport android.webkit.MimeTypeMap\nimport com.tomclaw.cache.DiskLruCache\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.withContext\nimport okio.Source\nimport okio.buffer\nimport okio.sink\nimport okio.use\nimport org.koitharu.kotatsu.core.exceptions.NoDataReceivedException\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.MimeType\nimport org.koitharu.kotatsu.core.util.ext.compressToPNG\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.subdir\nimport org.koitharu.kotatsu.core.util.ext.takeIfReadable\nimport org.koitharu.kotatsu.core.util.ext.takeIfWriteable\nimport org.koitharu.kotatsu.core.util.ext.writeAllCancellable\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport java.io.File\nimport java.util.UUID\n\nclass LocalStorageCache(\n\tcontext: Context,\n\tprivate val dir: CacheDir,\n\tprivate val defaultSize: Long,\n\tprivate val minSize: Long,\n) {\n\n\tprivate val cacheDir = suspendLazy {\n\t\tval dirs = context.externalCacheDirs + context.cacheDir\n\t\tdirs.firstNotNullOf {\n\t\t\tit?.subdir(dir.dir)?.takeIfWriteable()\n\t\t}\n\t}\n\tprivate val lruCache = suspendLazy {\n\t\tval dir = cacheDir.get()\n\t\tval availableSize = (getAvailableSize() * 0.8).toLong()\n\t\tval size = defaultSize.coerceAtMost(availableSize).coerceAtLeast(minSize)\n\t\trunCatchingCancellable {\n\t\t\tDiskLruCache.create(dir, size)\n\t\t}.recoverCatching { error ->\n\t\t\terror.printStackTraceDebug()\n\t\t\tdir.deleteRecursively()\n\t\t\tdir.mkdir()\n\t\t\tDiskLruCache.create(dir, size)\n\t\t}.getOrThrow()\n\t}\n\n\tsuspend operator fun get(url: String): File? = withContext(Dispatchers.IO) {\n\t\tval cache = lruCache.get()\n\t\trunInterruptible {\n\t\t\tcache.get(url)?.takeIfReadable()\n\t\t}\n\t}\n\n\tsuspend operator fun set(url: String, source: Source, mimeType: MimeType?): File = withContext(Dispatchers.IO) {\n\t\tval file = createBufferFile(url, mimeType)\n\t\ttry {\n\t\t\tval bytes = file.sink(append = false).buffer().use {\n\t\t\t\tit.writeAllCancellable(source)\n\t\t\t}\n\t\t\tif (bytes == 0L) {\n\t\t\t\tthrow NoDataReceivedException(url)\n\t\t\t}\n\t\t\tval cache = lruCache.get()\n\t\t\trunInterruptible {\n\t\t\t\tcache.put(url, file)\n\t\t\t}\n\t\t} finally {\n\t\t\tfile.delete()\n\t\t}\n\t}\n\n\tsuspend operator fun set(url: String, bitmap: Bitmap): File = withContext(Dispatchers.IO) {\n\t\tval file = createBufferFile(url, MimeType(\"image/png\"))\n\t\ttry {\n\t\t\tbitmap.compressToPNG(file)\n\t\t\tval cache = lruCache.get()\n\t\t\trunInterruptible {\n\t\t\t\tcache.put(url, file)\n\t\t\t}\n\t\t} finally {\n\t\t\tfile.delete()\n\t\t}\n\t}\n\n\tsuspend fun clear() {\n\t\tval cache = lruCache.get()\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\tcache.clearCache()\n\t\t}\n\t}\n\n\tprivate suspend fun getAvailableSize(): Long = runCatchingCancellable {\n\t\tval dir = cacheDir.get()\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\tval statFs = StatFs(dir.absolutePath)\n\t\t\tstatFs.availableBytes\n\t\t}\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrDefault(defaultSize)\n\n\tprivate suspend fun createBufferFile(url: String, mimeType: MimeType?): File {\n\t\tval ext = MimeTypes.getExtension(mimeType) ?: MimeTypeMap.getFileExtensionFromUrl(url).ifNullOrEmpty { \"dat\" }\n\t\tval cacheDir = cacheDir.get()\n\t\tval rootDir = checkNotNull(cacheDir.parentFile) { \"Cannot get parent for ${cacheDir.absolutePath}\" }\n\t\tval name = UUID.randomUUID().toString() + \".\" + ext\n\t\treturn File(rootDir, name)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/LocalStorageManager.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport android.Manifest\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.os.StatFs\nimport androidx.annotation.WorkerThread\nimport androidx.core.content.ContextCompat\nimport androidx.core.net.toFile\nimport dagger.Reusable\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.withContext\nimport okhttp3.Cache\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.exceptions.NonFileUriException\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.computeSize\nimport org.koitharu.kotatsu.core.util.ext.getStorageName\nimport org.koitharu.kotatsu.core.util.ext.isFileUri\nimport org.koitharu.kotatsu.core.util.ext.isReadable\nimport org.koitharu.kotatsu.core.util.ext.isWriteable\nimport org.koitharu.kotatsu.core.util.ext.resolveFile\nimport org.koitharu.kotatsu.core.util.ext.takeIfWriteable\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport java.io.File\nimport javax.inject.Inject\n\nprivate const val DIR_NAME = \"manga\"\nprivate const val NOMEDIA = \".nomedia\"\nprivate const val CACHE_DISK_PERCENTAGE = 0.02\nprivate const val CACHE_SIZE_MIN: Long = 10 * 1024 * 1024 // 10MB\nprivate const val CACHE_SIZE_MAX: Long = 250 * 1024 * 1024 // 250MB\n\n@Reusable\nclass LocalStorageManager @Inject constructor(\n    @LocalizedAppContext private val context: Context,\n    private val settings: AppSettings,\n) {\n\n\tval contentResolver: ContentResolver\n\t\tget() = context.contentResolver\n\n\t@WorkerThread\n\tfun createHttpCache(): Cache {\n\t\tval directory = File(context.externalCacheDir ?: context.cacheDir, \"http\")\n\t\tdirectory.mkdirs()\n\t\tval maxSize = calculateDiskCacheSize(directory)\n\t\treturn Cache(directory, maxSize)\n\t}\n\n\tsuspend fun computeCacheSize(cache: CacheDir) = withContext(Dispatchers.IO) {\n\t\tgetCacheDirs(cache.dir).sumOf { it.computeSize() }\n\t}\n\n\tsuspend fun computeCacheSize() = withContext(Dispatchers.IO) {\n\t\tgetCacheDirs().sumOf { it.computeSize() }\n\t}\n\n\tsuspend fun computeStorageSize() = withContext(Dispatchers.IO) {\n\t\tgetConfiguredStorageDirs().sumOf { it.computeSize() }\n\t}\n\n\tsuspend fun computeAvailableSize() = runInterruptible(Dispatchers.IO) {\n\t\tgetConfiguredStorageDirs().mapToSet { it.freeSpace }.sum()\n\t}\n\n\tsuspend fun clearCache(cache: CacheDir) = runInterruptible(Dispatchers.IO) {\n\t\tgetCacheDirs(cache.dir).forEach { it.deleteRecursively() }\n\t}\n\n\tsuspend fun getReadableDirs(): List<File> = runInterruptible(Dispatchers.IO) {\n\t\tgetConfiguredStorageDirs()\n\t\t\t.filter { it.isReadable() }\n\t}\n\n\tsuspend fun getWriteableDirs(): List<File> = runInterruptible(Dispatchers.IO) {\n\t\tgetConfiguredStorageDirs()\n\t\t\t.filter { it.isWriteable() }\n\t}\n\n\tsuspend fun getDefaultWriteableDir(): File? = runInterruptible(Dispatchers.IO) {\n\t\tval preferredDir = settings.mangaStorageDir?.takeIfWriteable()\n\t\tpreferredDir ?: getFallbackStorageDir()?.takeIfWriteable()\n\t}\n\n\tsuspend fun getApplicationStorageDirs(): Set<File> = runInterruptible(Dispatchers.IO) {\n\t\tgetAvailableStorageDirs()\n\t}\n\n\tsuspend fun resolveUri(uri: Uri): File = runInterruptible(Dispatchers.IO) {\n\t\tif (uri.isFileUri()) {\n\t\t\turi.toFile()\n\t\t} else {\n\t\t\turi.resolveFile(context) ?: throw NonFileUriException(uri)\n\t\t}\n\t}\n\n\tsuspend fun setDirIsNoMedia(dir: File) = runInterruptible(Dispatchers.IO) {\n\t\tFile(dir, NOMEDIA).createNewFile()\n\t}\n\n\tfun takePermissions(uri: Uri) {\n\t\tval flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION\n\t\tcontentResolver.takePersistableUriPermission(uri, flags)\n\t}\n\n\tfun isOnExternalStorage(file: File): Boolean {\n\t\treturn !file.absolutePath.contains(context.packageName)\n\t}\n\n\tfun hasExternalStoragePermission(isReadOnly: Boolean): Boolean {\n\t\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n\t\t\tEnvironment.isExternalStorageManager()\n\t\t} else {\n\t\t\tval permission = if (isReadOnly) {\n\t\t\t\tManifest.permission.READ_EXTERNAL_STORAGE\n\t\t\t} else {\n\t\t\t\tManifest.permission.WRITE_EXTERNAL_STORAGE\n\t\t\t}\n\t\t\tContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED\n\t\t}\n\t}\n\n\tsuspend fun getDirectoryDisplayName(dir: File, isFullPath: Boolean): String = runInterruptible(Dispatchers.IO) {\n\t\tval packageName = context.packageName\n\t\tif (dir.absolutePath.contains(packageName)) {\n\t\t\tdir.getStorageName(context)\n\t\t} else if (isFullPath) {\n\t\t\tdir.path\n\t\t} else {\n\t\t\tdir.name\n\t\t}\n\t}\n\n\t@WorkerThread\n\tprivate fun getConfiguredStorageDirs(): MutableSet<File> {\n\t\tval set = getAvailableStorageDirs()\n\t\tset.addAll(settings.userSpecifiedMangaDirectories)\n\t\treturn set\n\t}\n\n\t@WorkerThread\n\tprivate fun getAvailableStorageDirs(): MutableSet<File> {\n\t\tval result = LinkedHashSet<File>()\n\t\tresult += File(context.filesDir, DIR_NAME)\n\t\tcontext.getExternalFilesDirs(DIR_NAME).filterNotNullTo(result)\n\t\tresult.retainAll { it.exists() || it.mkdirs() }\n\t\treturn result\n\t}\n\n\t@WorkerThread\n\tprivate fun getFallbackStorageDir(): File? {\n\t\treturn context.getExternalFilesDir(DIR_NAME) ?: File(context.filesDir, DIR_NAME).takeIf {\n\t\t\tit.exists() || it.mkdirs()\n\t\t}\n\t}\n\n\t@WorkerThread\n\tprivate fun getCacheDirs(subDir: String): MutableSet<File> {\n\t\tval result = LinkedHashSet<File>()\n\t\tresult += File(context.cacheDir, subDir)\n\t\tcontext.externalCacheDirs.mapNotNullTo(result) {\n\t\t\tFile(it ?: return@mapNotNullTo null, subDir)\n\t\t}\n\t\treturn result\n\t}\n\n\t@WorkerThread\n\tprivate fun getCacheDirs(): MutableSet<File> {\n\t\tval result = LinkedHashSet<File>()\n\t\tresult += context.cacheDir\n\t\tcontext.externalCacheDirs.filterNotNullTo(result)\n\t\treturn result\n\t}\n\n\tprivate fun calculateDiskCacheSize(cacheDirectory: File): Long {\n\t\treturn try {\n\t\t\tval cacheDir = StatFs(cacheDirectory.absolutePath)\n\t\t\tval size = CACHE_DISK_PERCENTAGE * cacheDir.blockCountLong * cacheDir.blockSizeLong\n\t\t\treturn size.toLong().coerceIn(CACHE_SIZE_MIN, CACHE_SIZE_MAX)\n\t\t} catch (_: Exception) {\n\t\t\tCACHE_SIZE_MIN\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/MangaIndex.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport androidx.annotation.WorkerThread\nimport okio.FileSystem\nimport okio.Path\nimport okio.Path.Companion.toOkioPath\nimport okio.buffer\nimport org.jetbrains.annotations.Blocking\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaState\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN\nimport org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault\nimport org.koitharu.kotatsu.parsers.util.json.getEnumValueOrNull\nimport org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault\nimport org.koitharu.kotatsu.parsers.util.json.getIntOrDefault\nimport org.koitharu.kotatsu.parsers.util.json.getLongOrDefault\nimport org.koitharu.kotatsu.parsers.util.json.getStringOrNull\nimport org.koitharu.kotatsu.parsers.util.json.mapJSONToSet\nimport org.koitharu.kotatsu.parsers.util.json.toStringSet\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.toTitleCase\nimport java.io.File\n\nclass MangaIndex(source: String?) {\n\n\tprivate val json: JSONObject = source?.let(::JSONObject) ?: JSONObject()\n\n\tfun setMangaInfo(manga: Manga) {\n\t\trequire(!manga.isLocal) { \"Local manga information cannot be stored\" }\n\t\tjson.put(KEY_ID, manga.id)\n\t\tjson.put(KEY_TITLE, manga.title)\n\t\tjson.put(KEY_TITLE_ALT, manga.altTitle) // for backward compatibility\n\t\tjson.put(KEY_ALT_TITLES, JSONArray(manga.altTitles))\n\t\tjson.put(KEY_URL, manga.url)\n\t\tjson.put(KEY_PUBLIC_URL, manga.publicUrl)\n\t\tjson.put(KEY_AUTHOR, manga.author) // for backward compatibility\n\t\tjson.put(KEY_AUTHORS, JSONArray(manga.authors))\n\t\tjson.put(KEY_COVER, manga.coverUrl)\n\t\tjson.put(KEY_DESCRIPTION, manga.description)\n\t\tjson.put(KEY_RATING, manga.rating)\n\t\tjson.put(KEY_CONTENT_RATING, manga.contentRating)\n\t\tjson.put(KEY_NSFW, manga.isNsfw) // for backward compatibility\n\t\tjson.put(KEY_STATE, manga.state?.name)\n\t\tjson.put(KEY_SOURCE, manga.source.name)\n\t\tjson.put(KEY_COVER_LARGE, manga.largeCoverUrl)\n\t\tjson.put(\n\t\t\tKEY_TAGS,\n\t\t\tJSONArray().also { a ->\n\t\t\t\tfor (tag in manga.tags) {\n\t\t\t\t\tval jo = JSONObject()\n\t\t\t\t\tjo.put(KEY_KEY, tag.key)\n\t\t\t\t\tjo.put(KEY_TITLE, tag.title)\n\t\t\t\t\ta.put(jo)\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t\tif (!json.has(KEY_CHAPTERS)) {\n\t\t\tjson.put(KEY_CHAPTERS, JSONObject())\n\t\t}\n\t\tjson.put(KEY_APP_ID, BuildConfig.APPLICATION_ID)\n\t\tjson.put(KEY_APP_VERSION, BuildConfig.VERSION_CODE)\n\t}\n\n\tfun getMangaInfo(): Manga? = if (json.length() == 0) null else runCatching {\n\t\tval source = MangaSource(json.getString(KEY_SOURCE))\n\t\tManga(\n\t\t\tid = json.getLong(KEY_ID),\n\t\t\ttitle = json.getString(KEY_TITLE),\n\t\t\taltTitles = json.optJSONArray(KEY_ALT_TITLES)?.toStringSet()\n\t\t\t\t?: setOfNotNull(json.getStringOrNull(KEY_TITLE_ALT)),\n\t\t\turl = json.getString(KEY_URL),\n\t\t\tpublicUrl = json.getStringOrNull(KEY_PUBLIC_URL).orEmpty(),\n\t\t\tauthors = json.optJSONArray(KEY_AUTHORS)?.toStringSet()\n\t\t\t\t?: setOfNotNull(json.getStringOrNull(KEY_AUTHOR)),\n\t\t\tlargeCoverUrl = json.getStringOrNull(KEY_COVER_LARGE),\n\t\t\tsource = source,\n\t\t\trating = json.getFloatOrDefault(KEY_RATING, RATING_UNKNOWN),\n\t\t\tcontentRating = json.getEnumValueOrNull(KEY_CONTENT_RATING, ContentRating::class.java)\n\t\t\t\t?: if (json.getBooleanOrDefault(KEY_NSFW, false)) ContentRating.ADULT else null,\n\t\t\tcoverUrl = json.getStringOrNull(KEY_COVER),\n\t\t\tstate = json.getEnumValueOrNull(KEY_STATE, MangaState::class.java),\n\t\t\tdescription = json.getStringOrNull(KEY_DESCRIPTION),\n\t\t\ttags = json.getJSONArray(KEY_TAGS).mapJSONToSet { x ->\n\t\t\t\tMangaTag(\n\t\t\t\t\ttitle = x.getString(KEY_TITLE).toTitleCase(),\n\t\t\t\t\tkey = x.getString(KEY_KEY),\n\t\t\t\t\tsource = source,\n\t\t\t\t)\n\t\t\t},\n\t\t\tchapters = getChapters(json.getJSONObject(KEY_CHAPTERS), source),\n\t\t)\n\t}.getOrNull()\n\n\tfun getCoverEntry(): String? = json.getStringOrNull(KEY_COVER_ENTRY)\n\n\tfun addChapter(chapter: IndexedValue<MangaChapter>, filename: String?) {\n\t\tval chapters = json.getJSONObject(KEY_CHAPTERS)\n\t\tif (!chapters.has(chapter.value.id.toString())) {\n\t\t\tval jo = JSONObject()\n\t\t\tjo.put(KEY_NUMBER, chapter.value.number)\n\t\t\tjo.put(KEY_VOLUME, chapter.value.volume)\n\t\t\tjo.put(KEY_URL, chapter.value.url)\n\t\t\tjo.put(KEY_NAME, chapter.value.title.orEmpty())\n\t\t\tjo.put(KEY_UPLOAD_DATE, chapter.value.uploadDate)\n\t\t\tjo.put(KEY_SCANLATOR, chapter.value.scanlator)\n\t\t\tjo.put(KEY_BRANCH, chapter.value.branch)\n\t\t\tjo.put(KEY_ENTRIES, \"%08d_%04d\\\\d{4}\".format(chapter.value.branch.hashCode(), chapter.index + 1))\n\t\t\tjo.put(KEY_FILE, filename)\n\t\t\tchapters.put(chapter.value.id.toString(), jo)\n\t\t}\n\t}\n\n\tfun removeChapter(id: Long): Boolean {\n\t\treturn json.has(KEY_CHAPTERS) && json.getJSONObject(KEY_CHAPTERS).remove(id.toString()) != null\n\t}\n\n\tfun getChapterFileName(chapterId: Long): String? {\n\t\treturn json.optJSONObject(KEY_CHAPTERS)?.optJSONObject(chapterId.toString())?.getStringOrNull(KEY_FILE)\n\t}\n\n\tfun setCoverEntry(name: String) {\n\t\tjson.put(KEY_COVER_ENTRY, name)\n\t}\n\n\tfun getChapterNamesPattern(chapter: MangaChapter) = Regex(\n\t\tjson.getJSONObject(KEY_CHAPTERS)\n\t\t\t.getJSONObject(chapter.id.toString())\n\t\t\t.getString(KEY_ENTRIES),\n\t)\n\n\tfun sortChaptersByName() {\n\t\tval jo = json.getJSONObject(KEY_CHAPTERS)\n\t\tval list = ArrayList<JSONObject>(jo.length())\n\t\tjo.keys().forEach { id ->\n\t\t\tval item = jo.getJSONObject(id)\n\t\t\titem.put(KEY_ID, id)\n\t\t\tlist.add(item)\n\t\t}\n\t\tval comparator = org.koitharu.kotatsu.core.util.AlphanumComparator()\n\t\tlist.sortWith(compareBy(comparator) { it.getString(KEY_NAME) })\n\t\tval newJo = JSONObject()\n\t\tlist.forEachIndexed { i, obj ->\n\t\t\tobj.put(KEY_NUMBER, i + 1)\n\t\t\tval id = obj.remove(KEY_ID) as String\n\t\t\tnewJo.put(id, obj)\n\t\t}\n\t\tjson.put(KEY_CHAPTERS, newJo)\n\t}\n\n\tfun clear() {\n\t\tval keys = json.keys()\n\t\twhile (keys.hasNext()) {\n\t\t\tjson.remove(keys.next())\n\t\t}\n\t}\n\n\tfun setFrom(other: MangaIndex) {\n\t\tclear()\n\t\tother.json.keys().forEach { key ->\n\t\t\tjson.putOpt(key, other.json.opt(key))\n\t\t}\n\t}\n\n\tprivate fun getChapters(json: JSONObject, source: MangaSource): List<MangaChapter> {\n\t\tval chapters = ArrayList<MangaChapter>(json.length())\n\t\tfor (k in json.keys()) {\n\t\t\tval v = json.getJSONObject(k)\n\t\t\tchapters.add(\n\t\t\t\tMangaChapter(\n\t\t\t\t\tid = k.toLong(),\n\t\t\t\t\ttitle = v.getStringOrNull(KEY_NAME),\n\t\t\t\t\turl = v.getString(KEY_URL),\n\t\t\t\t\tnumber = v.getFloatOrDefault(KEY_NUMBER, 0f),\n\t\t\t\t\tvolume = v.getIntOrDefault(KEY_VOLUME, 0),\n\t\t\t\t\tuploadDate = v.getLongOrDefault(KEY_UPLOAD_DATE, 0L),\n\t\t\t\t\tscanlator = v.getStringOrNull(KEY_SCANLATOR),\n\t\t\t\t\tbranch = v.getStringOrNull(KEY_BRANCH),\n\t\t\t\t\tsource = source,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\treturn chapters.sortedBy { it.number }\n\t}\n\n\toverride fun toString(): String = if (BuildConfig.DEBUG) {\n\t\tjson.toString(4)\n\t} else {\n\t\tjson.toString()\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val KEY_ID = \"id\"\n\t\tprivate const val KEY_TITLE = \"title\"\n\t\tprivate const val KEY_TITLE_ALT = \"title_alt\"\n\t\tprivate const val KEY_ALT_TITLES = \"alt_titles\"\n\t\tprivate const val KEY_URL = \"url\"\n\t\tprivate const val KEY_PUBLIC_URL = \"public_url\"\n\t\tprivate const val KEY_AUTHOR = \"author\"\n\t\tprivate const val KEY_AUTHORS = \"authors\"\n\t\tprivate const val KEY_COVER = \"cover\"\n\t\tprivate const val KEY_DESCRIPTION = \"description\"\n\t\tprivate const val KEY_RATING = \"rating\"\n\t\tprivate const val KEY_CONTENT_RATING = \"content_rating\"\n\t\tprivate const val KEY_NSFW = \"nsfw\"\n\t\tprivate const val KEY_STATE = \"state\"\n\t\tprivate const val KEY_SOURCE = \"source\"\n\t\tprivate const val KEY_COVER_LARGE = \"cover_large\"\n\t\tprivate const val KEY_TAGS = \"tags\"\n\t\tprivate const val KEY_CHAPTERS = \"chapters\"\n\t\tprivate const val KEY_NUMBER = \"number\"\n\t\tprivate const val KEY_VOLUME = \"volume\"\n\t\tprivate const val KEY_NAME = \"name\"\n\t\tprivate const val KEY_UPLOAD_DATE = \"uploadDate\"\n\t\tprivate const val KEY_SCANLATOR = \"scanlator\"\n\t\tprivate const val KEY_BRANCH = \"branch\"\n\t\tprivate const val KEY_ENTRIES = \"entries\"\n\t\tprivate const val KEY_FILE = \"file\"\n\t\tprivate const val KEY_COVER_ENTRY = \"cover_entry\"\n\t\tprivate const val KEY_KEY = \"key\"\n\t\tprivate const val KEY_APP_ID = \"app_id\"\n\t\tprivate const val KEY_APP_VERSION = \"app_version\"\n\n\t\t@Blocking\n\t\t@WorkerThread\n\t\tfun read(fileSystem: FileSystem, path: Path): MangaIndex? = runCatchingCancellable {\n\t\t\tif (!fileSystem.exists(path)) {\n\t\t\t\treturn@runCatchingCancellable null\n\t\t\t}\n\t\t\tval text = fileSystem.source(path).use {\n\t\t\t\tit.buffer().use { buffer ->\n\t\t\t\t\tbuffer.readUtf8()\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (text.length > 2) {\n\t\t\t\tMangaIndex(text)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}.onFailure { e ->\n\t\t\te.printStackTraceDebug()\n\t\t}.getOrNull()\n\n\t\t@Blocking\n\t\t@WorkerThread\n\t\tfun read(file: File): MangaIndex? = read(FileSystem.SYSTEM, file.toOkioPath())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/Qualifiers.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport javax.inject.Qualifier\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class LocalStorageChanges\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/TempFileFilter.kt",
    "content": "package org.koitharu.kotatsu.local.data\n\nimport java.io.File\nimport java.io.FileFilter\n\nclass TempFileFilter : FileFilter {\n\n\toverride fun accept(file: File): Boolean {\n\t\treturn file.name.endsWith(\".tmp\", ignoreCase = true)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/importer/SingleMangaImporter.kt",
    "content": "package org.koitharu.kotatsu.local.data.importer\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.documentfile.provider.DocumentFile\nimport dagger.Reusable\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.withContext\nimport okio.buffer\nimport okio.sink\nimport org.koitharu.kotatsu.core.exceptions.UnsupportedFileException\nimport org.koitharu.kotatsu.core.util.ext.openSource\nimport org.koitharu.kotatsu.core.util.ext.resolveName\nimport org.koitharu.kotatsu.core.util.ext.writeAllCancellable\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport org.koitharu.kotatsu.local.data.hasZipExtension\nimport org.koitharu.kotatsu.local.data.input.LocalMangaParser\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport java.io.File\nimport java.io.IOException\nimport javax.inject.Inject\n\n@Reusable\nclass SingleMangaImporter @Inject constructor(\n\t@ApplicationContext private val context: Context,\n\tprivate val storageManager: LocalStorageManager,\n\t@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,\n) {\n\n\tprivate val contentResolver = context.contentResolver\n\n\tsuspend fun import(uri: Uri): LocalManga {\n\t\tval result = if (isDirectory(uri)) {\n\t\t\timportDirectory(uri)\n\t\t} else {\n\t\t\timportFile(uri)\n\t\t}\n\t\tlocalStorageChanges.emit(result)\n\t\treturn result\n\t}\n\n\tprivate suspend fun importFile(uri: Uri): LocalManga = withContext(Dispatchers.IO) {\n\t\tval contentResolver = storageManager.contentResolver\n\t\tval name = contentResolver.resolveName(uri) ?: throw IOException(\"Cannot fetch name from uri: $uri\")\n\t\tif (!hasZipExtension(name)) {\n\t\t\tthrow UnsupportedFileException(\"Unsupported file $name on $uri\")\n\t\t}\n\t\tval dest = File(getOutputDir(), name)\n\t\trunInterruptible {\n\t\t\tcontentResolver.openSource(uri)\n\t\t}.use { source ->\n\t\t\tdest.sink().buffer().use { output ->\n\t\t\t\toutput.writeAllCancellable(source)\n\t\t\t}\n\t\t}\n\t\tLocalMangaParser(dest).getManga(withDetails = false)\n\t}\n\n\tprivate suspend fun importDirectory(uri: Uri): LocalManga {\n\t\tval root = requireNotNull(DocumentFile.fromTreeUri(context, uri)) {\n\t\t\t\"Provided uri $uri is not a tree\"\n\t\t}\n\t\tval dest = File(getOutputDir(), root.requireName())\n\t\tdest.mkdir()\n\t\tfor (docFile in root.listFiles()) {\n\t\t\tdocFile.copyTo(dest)\n\t\t}\n\t\treturn LocalMangaParser(dest).getManga(withDetails = false)\n\t}\n\n\tprivate suspend fun DocumentFile.copyTo(destDir: File) {\n\t\tif (isDirectory) {\n\t\t\tval subDir = File(destDir, requireName())\n\t\t\tsubDir.mkdir()\n\t\t\tfor (docFile in listFiles()) {\n\t\t\t\tdocFile.copyTo(subDir)\n\t\t\t}\n\t\t} else {\n\t\t\tsource().use { input ->\n\t\t\t\tFile(destDir, requireName()).sink().buffer().use { output ->\n\t\t\t\t\toutput.writeAllCancellable(input)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun getOutputDir(): File {\n\t\treturn storageManager.getDefaultWriteableDir() ?: throw IOException(\"External files dir unavailable\")\n\t}\n\n\tprivate suspend fun DocumentFile.source() = runInterruptible(Dispatchers.IO) {\n\t\tcontentResolver.openSource(uri)\n\t}\n\n\tprivate fun DocumentFile.requireName(): String {\n\t\treturn name ?: throw IOException(\"Cannot fetch name from uri: $uri\")\n\t}\n\n\tprivate fun isDirectory(uri: Uri): Boolean {\n\t\treturn runCatching {\n\t\t\tDocumentFile.fromTreeUri(context, uri)\n\t\t}.isSuccess\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/index/LocalMangaIndex.kt",
    "content": "package org.koitharu.kotatsu.local.data.index\n\nimport android.content.Context\nimport androidx.core.content.edit\nimport androidx.room.withTransaction\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.data.input.LocalMangaParser\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport javax.inject.Singleton\n\n@Singleton\nclass LocalMangaIndex @Inject constructor(\n\tprivate val mangaDataRepository: MangaDataRepository,\n\tprivate val db: MangaDatabase,\n\t@ApplicationContext context: Context,\n\tprivate val localMangaRepositoryProvider: Provider<LocalMangaRepository>,\n) : FlowCollector<LocalManga?> {\n\n\tprivate val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)\n\tprivate val mutex = Mutex()\n\n\tprivate var currentVersion: Int\n\t\tget() = prefs.getInt(KEY_VERSION, 0)\n\t\tset(value) = prefs.edit { putInt(KEY_VERSION, value) }\n\n\toverride suspend fun emit(value: LocalManga?) {\n\t\tif (value != null) {\n\t\t\tput(value)\n\t\t}\n\t}\n\n\tsuspend fun update() = mutex.withLock {\n\t\tdb.withTransaction {\n\t\t\tval dao = db.getLocalMangaIndexDao()\n\t\t\tdao.clear()\n\t\t\tlocalMangaRepositoryProvider.get()\n\t\t\t\t.getRawListAsFlow()\n\t\t\t\t.collect { upsert(it) }\n\t\t}\n\t\tcurrentVersion = VERSION\n\t}\n\n\tsuspend fun updateIfRequired() {\n\t\tif (isUpdateRequired()) {\n\t\t\tupdate()\n\t\t}\n\t}\n\n\tsuspend fun get(mangaId: Long, withDetails: Boolean): LocalManga? {\n\t\tupdateIfRequired()\n\t\tvar path = db.getLocalMangaIndexDao().findPath(mangaId)\n\t\tif (path == null && mutex.isLocked) { // wait for updating complete\n\t\t\tpath = mutex.withLock { db.getLocalMangaIndexDao().findPath(mangaId) }\n\t\t}\n\t\tif (path == null) {\n\t\t\treturn null\n\t\t}\n\t\treturn runCatchingCancellable {\n\t\t\tLocalMangaParser(File(path)).getManga(withDetails)\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}.getOrNull()\n\t}\n\n\tsuspend operator fun contains(mangaId: Long): Boolean {\n\t\treturn db.getLocalMangaIndexDao().findPath(mangaId) != null\n\t}\n\n\tsuspend fun put(manga: LocalManga) = mutex.withLock {\n\t\tdb.withTransaction {\n\t\t\tupsert(manga)\n\t\t}\n\t}\n\n\tsuspend fun delete(mangaId: Long) {\n\t\tdb.getLocalMangaIndexDao().delete(mangaId)\n\t}\n\n\tsuspend fun getAvailableTags(skipNsfw: Boolean): List<String> {\n\t\tval dao = db.getLocalMangaIndexDao()\n\t\treturn if (skipNsfw) {\n\t\t\tdao.findTags(isNsfw = false)\n\t\t} else {\n\t\t\tdao.findTags()\n\t\t}\n\t}\n\n\tprivate suspend fun upsert(manga: LocalManga) {\n\t\tmangaDataRepository.storeManga(manga.manga, replaceExisting = true)\n\t\tdb.getLocalMangaIndexDao().upsert(manga.toEntity())\n\t}\n\n\tprivate fun LocalManga.toEntity() = LocalMangaIndexEntity(\n\t\tmangaId = manga.id,\n\t\tpath = file.path,\n\t)\n\n\tprivate fun isUpdateRequired() = currentVersion < VERSION\n\n\tcompanion object {\n\n\t\tprivate const val PREF_NAME = \"_local_index\"\n\t\tprivate const val KEY_VERSION = \"ver\"\n\t\tprivate const val VERSION = 1\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/index/LocalMangaIndexDao.kt",
    "content": "package org.koitharu.kotatsu.local.data.index\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.Upsert\n\n@Dao\ninterface LocalMangaIndexDao {\n\n\t@Query(\"SELECT path FROM local_index WHERE manga_id = :mangaId\")\n\tsuspend fun findPath(mangaId: Long): String?\n\n\t@Query(\"SELECT title FROM local_index LEFT JOIN manga_tags ON manga_tags.manga_id = local_index.manga_id LEFT JOIN tags ON tags.tag_id = manga_tags.tag_id WHERE title IS NOT NULL GROUP BY title\")\n\tsuspend fun findTags(): List<String>\n\n\t@Query(\"SELECT title FROM local_index LEFT JOIN manga_tags ON manga_tags.manga_id = local_index.manga_id LEFT JOIN tags ON tags.tag_id = manga_tags.tag_id WHERE (SELECT nsfw FROM manga WHERE manga.manga_id = local_index.manga_id) = :isNsfw AND title IS NOT NULL GROUP BY title\")\n\tsuspend fun findTags(isNsfw: Boolean): List<String>\n\n\t@Upsert\n\tsuspend fun upsert(entity: LocalMangaIndexEntity)\n\n\t@Query(\"DELETE FROM local_index WHERE manga_id = :mangaId\")\n\tsuspend fun delete(mangaId: Long)\n\n\t@Query(\"DELETE FROM local_index\")\n\tsuspend fun clear()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/index/LocalMangaIndexEntity.kt",
    "content": "package org.koitharu.kotatsu.local.data.index\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\n@Entity(\n\ttableName = \"local_index\",\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t],\n)\nclass LocalMangaIndexEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"manga_id\") val mangaId: Long,\n\t@ColumnInfo(name = \"path\") val path: String,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt",
    "content": "package org.koitharu.kotatsu.local.data.input\n\nimport android.net.Uri\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.firstOrNull\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runInterruptible\nimport okio.FileSystem\nimport okio.Path\nimport okio.Path.Companion.toOkioPath\nimport okio.Path.Companion.toPath\nimport okio.openZip\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.util.AlphanumComparator\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP\nimport org.koitharu.kotatsu.core.util.ext.isDirectory\nimport org.koitharu.kotatsu.core.util.ext.isFileUri\nimport org.koitharu.kotatsu.core.util.ext.isImage\nimport org.koitharu.kotatsu.core.util.ext.isRegularFile\nimport org.koitharu.kotatsu.core.util.ext.isZipUri\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toFileNameSafe\nimport org.koitharu.kotatsu.core.util.ext.toListSorted\nimport org.koitharu.kotatsu.local.data.MangaIndex\nimport org.koitharu.kotatsu.local.data.hasZipExtension\nimport org.koitharu.kotatsu.local.data.isZipArchive\nimport org.koitharu.kotatsu.local.data.output.LocalMangaOutput.Companion.ENTRY_NAME_INDEX\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.util.longHashCode\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.toTitleCase\nimport java.io.File\n\n/**\n * Manga root {dir or zip file}\n * |--- index.json (optional)\n * |--- Page 1.png\n * |--- Page 2.png\n * |---Chapter 1/(dir or zip, optional)\n * |------Page 1.1.png\n * :\n * L--- Page x.png\n */\nclass LocalMangaParser(private val uri: Uri) {\n\n\tconstructor(file: File) : this(file.toUri())\n\n\tprivate val rootFile: File = File(uri.schemeSpecificPart)\n\n\tsuspend fun getManga(withDetails: Boolean): LocalManga = runInterruptible(Dispatchers.IO) {\n\t\t(uri.resolveFsAndPath()).use { (fileSystem, rootPath) ->\n\t\t\tval index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)\n\t\t\tval mangaInfo = index?.getMangaInfo()\n\t\t\tif (mangaInfo != null) {\n\t\t\t\tval coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it }?.takeIf {\n\t\t\t\t\tfileSystem.exists(it)\n\t\t\t\t}\n\t\t\t\tmangaInfo.copy(\n\t\t\t\t\tsource = LocalMangaSource,\n\t\t\t\t\turl = rootFile.toUri().toString(),\n\t\t\t\t\tcoverUrl = coverEntry?.let {\n\t\t\t\t\t\turi.child(it, resolve = true).toString()\n\t\t\t\t\t} ?: fileSystem.findFirstImageUri(rootPath)?.toString(),\n\t\t\t\t\tlargeCoverUrl = null,\n\t\t\t\t\tchapters = if (withDetails) {\n\t\t\t\t\t\tmangaInfo.chapters?.mapNotNull { c ->\n\t\t\t\t\t\t\tval path = index.getChapterFileName(c.id)?.toPath()\n\t\t\t\t\t\t\tif (path != null && !fileSystem.exists(rootPath / path)) {\n\t\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tc.copy(\n\t\t\t\t\t\t\t\t\turl = path?.let {\n\t\t\t\t\t\t\t\t\t\turi.child(it, resolve = false).toString()\n\t\t\t\t\t\t\t\t\t} ?: uri.toString(),\n\t\t\t\t\t\t\t\t\tsource = LocalMangaSource,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnull\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tval title = rootFile.name.fileNameToTitle()\n\t\t\t\tManga(\n\t\t\t\t\tid = rootFile.absolutePath.longHashCode(),\n\t\t\t\t\ttitle = title,\n\t\t\t\t\turl = rootFile.toUri().toString(),\n\t\t\t\t\tpublicUrl = rootFile.toUri().toString(),\n\t\t\t\t\tsource = LocalMangaSource,\n\t\t\t\t\tcoverUrl = fileSystem.findFirstImageUri(rootPath)?.toString(),\n\t\t\t\t\tchapters = if (withDetails) {\n\t\t\t\t\t\tval chapters = fileSystem.listRecursively(rootPath)\n\t\t\t\t\t\t\t.mapNotNullTo(HashSet()) { path ->\n\t\t\t\t\t\t\t\twhen {\n\t\t\t\t\t\t\t\t\t!fileSystem.isRegularFile(path) -> null\n\t\t\t\t\t\t\t\t\tpath.isImage() -> path.parent\n\t\t\t\t\t\t\t\t\thasZipExtension(path.name) -> path\n\t\t\t\t\t\t\t\t\telse -> null\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}.sortedWith(compareBy(AlphanumComparator()) { x -> x.toString() })\n\t\t\t\t\t\tchapters.mapIndexed { i, p ->\n\t\t\t\t\t\t\tval s = if (p.root == rootPath.root) {\n\t\t\t\t\t\t\t\tp.relativeTo(rootPath).toString()\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tp\n\t\t\t\t\t\t\t}.toString().removePrefix(Path.DIRECTORY_SEPARATOR)\n\t\t\t\t\t\t\tMangaChapter(\n\t\t\t\t\t\t\t\tid = \"$i$s\".longHashCode(),\n\t\t\t\t\t\t\t\ttitle = p.userFriendlyName(),\n\t\t\t\t\t\t\t\tnumber = 0f,\n\t\t\t\t\t\t\t\tvolume = 0,\n\t\t\t\t\t\t\t\tsource = LocalMangaSource,\n\t\t\t\t\t\t\t\tuploadDate = 0L,\n\t\t\t\t\t\t\t\turl = uri.child(p.relativeTo(rootPath), resolve = false).toString(),\n\t\t\t\t\t\t\t\tscanlator = null,\n\t\t\t\t\t\t\t\tbranch = null,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnull\n\t\t\t\t\t},\n\t\t\t\t\taltTitles = emptySet(),\n\t\t\t\t\trating = -1f,\n\t\t\t\t\tcontentRating = null,\n\t\t\t\t\ttags = emptySet(),\n\t\t\t\t\tstate = null,\n\t\t\t\t\tauthors = emptySet(),\n\t\t\t\t\tlargeCoverUrl = null,\n\t\t\t\t\tdescription = null,\n\t\t\t\t)\n\t\t\t}.let { LocalManga(it, rootFile) }\n\t\t}\n\t}\n\n\tsuspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) {\n\t\turi.resolveFsAndPath().use { (fileSystem, rootPath) ->\n\t\t\tval index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)\n\t\t\tindex?.getMangaInfo()\n\t\t}\n\t}\n\n\tsuspend fun getPages(chapter: MangaChapter): List<MangaPage> = runInterruptible(Dispatchers.IO) {\n\t\tval chapterUri = chapter.url.toUri().resolve()\n\t\tchapterUri.resolveFsAndPath().use { (fileSystem, rootPath) ->\n\t\t\tval index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)\n\t\t\tval entries = fileSystem.listRecursively(rootPath)\n\t\t\t\t.filter { fileSystem.isRegularFile(it) }\n\t\t\tif (index != null) {\n\t\t\t\tval pattern = index.getChapterNamesPattern(chapter)\n\t\t\t\tentries.filter { x -> x.name.substringBefore('.').matches(pattern) }\n\t\t\t} else {\n\t\t\t\tentries.filter { x -> x.isImage() && x.parent == rootPath }\n\t\t\t}.toListSorted(compareBy(AlphanumComparator()) { x -> x.toString() })\n\t\t\t\t.map { x ->\n\t\t\t\t\tval entryUri = chapterUri.child(x, resolve = true).toString()\n\t\t\t\t\tMangaPage(\n\t\t\t\t\t\tid = entryUri.longHashCode(),\n\t\t\t\t\t\turl = entryUri,\n\t\t\t\t\t\tpreview = null,\n\t\t\t\t\t\tsource = LocalMangaSource,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun Uri.child(path: Path, resolve: Boolean): Uri {\n\t\tval file = fileFromPath()\n\t\tval builder = buildUpon()\n\t\tval isZip = isZipUri() || file.isZipArchive\n\t\tif (isZip) {\n\t\t\tbuilder.scheme(URI_SCHEME_ZIP)\n\t\t}\n\t\tif (isZip || !resolve) {\n\t\t\tbuilder.fragment(path.toString().removePrefix(Path.DIRECTORY_SEPARATOR))\n\t\t} else {\n\t\t\tbuilder.appendEncodedPath(path.relativeTo(file.toOkioPath()).toString())\n\t\t}\n\t\treturn builder.build()\n\t}\n\n\tprivate fun FileSystem.findFirstImageUri(\n\t\trootPath: Path,\n\t\trecursive: Boolean = false\n\t): Uri? = runCatchingCancellable {\n\t\tval list = list(rootPath)\n\t\tfor (file in list.sortedWith(compareBy(AlphanumComparator()) { x -> x.name })) {\n\t\t\tif (isRegularFile(file)) {\n\t\t\t\tif (file.isImage()) {\n\t\t\t\t\treturn@runCatchingCancellable uri.child(file, resolve = true)\n\t\t\t\t}\n\t\t\t\tif (recursive && file.isZip()) {\n\t\t\t\t\topenZip(file).use { zipFs ->\n\t\t\t\t\t\tzipFs.findFirstImageUri(Path.DIRECTORY_SEPARATOR.toPath())?.let { subUri ->\n\t\t\t\t\t\t\tval subPath = subUri.path.orEmpty().removePrefix(uri.path.orEmpty())\n\t\t\t\t\t\t\t\t.replace(REGEX_PARENT_PATH_PREFIX, \"\")\n\t\t\t\t\t\t\treturn@runCatchingCancellable uri.child(file, resolve = true)\n\t\t\t\t\t\t\t\t.child(subPath.toPath(), resolve = false)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (recursive && isDirectory(file)) {\n\t\t\t\tfindFirstImageUri(file, true)?.let {\n\t\t\t\t\treturn@runCatchingCancellable it\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (recursive) {\n\t\t\tnull\n\t\t} else {\n\t\t\tfindFirstImageUri(rootPath, recursive = true)\n\t\t}\n\t}.onFailure { e ->\n\t\te.printStackTraceDebug()\n\t}.getOrNull()\n\n\tprivate fun Path.userFriendlyName(): String = name.substringBeforeLast('.')\n\t\t.replace('_', ' ')\n\t\t.toTitleCase()\n\n\tprivate class FsAndPath(\n\t\tval fileSystem: FileSystem,\n\t\tval path: Path,\n\t\tprivate val isCloseable: Boolean,\n\t) : AutoCloseable {\n\n\t\toverride fun close() {\n\t\t\tif (isCloseable) {\n\t\t\t\tfileSystem.close()\n\t\t\t}\n\t\t}\n\n\t\toperator fun component1() = fileSystem\n\n\t\toperator fun component2() = path\n\t}\n\n\tcompanion object {\n\n\t\tprivate val REGEX_PARENT_PATH_PREFIX = Regex(\"^(/\\\\.\\\\.)+\")\n\n\t\t@Blocking\n\t\tfun getOrNull(file: File): LocalMangaParser? = if ((file.isDirectory || file.isZipArchive) && file.canRead()) {\n\t\t\tLocalMangaParser(file)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\n\t\tsuspend fun find(roots: Iterable<File>, manga: Manga): LocalMangaParser? = channelFlow {\n\t\t\tval fileName = manga.title.toFileNameSafe()\n\t\t\tfor (root in roots) {\n\t\t\t\tlaunch {\n\t\t\t\t\tval parser = getOrNull(File(root, fileName)) ?: getOrNull(File(root, \"$fileName.cbz\"))\n\t\t\t\t\tval info = runCatchingCancellable { parser?.getMangaInfo() }.getOrNull()\n\t\t\t\t\tif (info?.id == manga.id) {\n\t\t\t\t\t\tsend(parser)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}.flowOn(Dispatchers.Default).firstOrNull()\n\n\t\tprivate fun Path.isImage(): Boolean = MimeTypes.getMimeTypeFromExtension(name)?.isImage == true\n\n\t\tprivate fun Path.isZip(): Boolean = hasZipExtension(name)\n\n\t\tprivate fun Uri.resolve(): Uri = if (isFileUri()) {\n\t\t\tval file = toFile()\n\t\t\tif (file.isZipArchive) {\n\t\t\t\tthis\n\t\t\t} else if (file.isDirectory) {\n\t\t\t\tfile.resolve(fragment.orEmpty()).toUri()\n\t\t\t} else {\n\t\t\t\tthis\n\t\t\t}\n\t\t} else {\n\t\t\tthis\n\t\t}\n\n\t\tprivate fun Uri.fileFromPath(): File = File(requireNotNull(path) { \"Uri path is null: $this\" })\n\n\t\t@Blocking\n\t\tprivate fun Uri.resolveFsAndPath(): FsAndPath {\n\t\t\tval resolved = resolve()\n\t\t\treturn when {\n\t\t\t\tresolved.isZipUri() -> FsAndPath(\n\t\t\t\t\tFileSystem.SYSTEM.openZip(resolved.schemeSpecificPart.toPath()),\n\t\t\t\t\tresolved.fragment.orEmpty().toRootedPath(),\n\t\t\t\t\tisCloseable = true,\n\t\t\t\t)\n\n\t\t\t\tisFileUri() -> {\n\t\t\t\t\tval file = toFile()\n\t\t\t\t\tif (file.isZipArchive) {\n\t\t\t\t\t\tFsAndPath(\n\t\t\t\t\t\t\tFileSystem.SYSTEM.openZip(schemeSpecificPart.toPath()),\n\t\t\t\t\t\t\tfragment.orEmpty().toRootedPath(),\n\t\t\t\t\t\t\tisCloseable = true,\n\t\t\t\t\t\t)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tFsAndPath(FileSystem.SYSTEM, file.toOkioPath(), isCloseable = false)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\telse -> throw IllegalArgumentException(\"Unsupported uri $resolved\")\n\t\t\t}\n\t\t}\n\n\t\tprivate fun String.toRootedPath(): Path = if (startsWith(Path.DIRECTORY_SEPARATOR)) {\n\t\t\tthis\n\t\t} else {\n\t\t\tPath.DIRECTORY_SEPARATOR + this\n\t\t}.toPath()\n\n\t\tprivate fun String.fileNameToTitle() = substringBeforeLast('.')\n\t\t\t.replace('_', ' ')\n\t\t\t.replaceFirstChar { it.uppercase() }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/output/LocalMangaDirOutput.kt",
    "content": "package org.koitharu.kotatsu.local.data.output\n\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport okhttp3.internal.closeQuietly\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.MimeType\nimport org.koitharu.kotatsu.core.util.ext.deleteAwait\nimport org.koitharu.kotatsu.core.util.ext.takeIfReadable\nimport org.koitharu.kotatsu.core.util.ext.toFileNameSafe\nimport org.koitharu.kotatsu.core.zip.ZipOutput\nimport org.koitharu.kotatsu.local.data.MangaIndex\nimport org.koitharu.kotatsu.local.data.input.LocalMangaParser\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport java.io.File\n\nclass LocalMangaDirOutput(\n\trootFile: File,\n\tmanga: Manga,\n) : LocalMangaOutput(rootFile) {\n\n\tprivate val chaptersOutput = HashMap<MangaChapter, ZipOutput>()\n\tprivate val index = MangaIndex(File(rootFile, ENTRY_NAME_INDEX).takeIfReadable()?.readText())\n\tprivate val mutex = Mutex()\n\n\tinit {\n\t\tif (!manga.isLocal) {\n\t\t\tindex.setMangaInfo(manga)\n\t\t}\n\t}\n\n\toverride suspend fun mergeWithExisting() = Unit\n\n\toverride suspend fun addCover(file: File, type: MimeType?) = mutex.withLock {\n\t\tval name = buildString {\n\t\t\tappend(\"cover\")\n\t\t\tMimeTypes.getExtension(type)?.let { ext ->\n\t\t\t\tappend('.')\n\t\t\t\tappend(ext)\n\t\t\t}\n\t\t}\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\tfile.copyTo(File(rootFile, name), overwrite = true)\n\t\t}\n\t\tindex.setCoverEntry(name)\n\t\tflushIndex()\n\t}\n\n\toverride suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, type: MimeType?) =\n\t\tmutex.withLock {\n\t\t\tval output = chaptersOutput.getOrPut(chapter.value) {\n\t\t\t\tZipOutput(File(rootFile, chapterFileName(chapter) + SUFFIX_TMP))\n\t\t\t}\n\t\t\tval name = buildString {\n\t\t\t\tappend(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber))\n\t\t\t\tMimeTypes.getExtension(type)?.let { ext ->\n\t\t\t\t\tappend('.')\n\t\t\t\t\tappend(ext)\n\t\t\t\t}\n\t\t\t}\n\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\toutput.put(name, file)\n\t\t\t}\n\t\t\tindex.addChapter(chapter, chapterFileName(chapter))\n\t\t}\n\n\toverride suspend fun flushChapter(chapter: MangaChapter): Boolean = mutex.withLock {\n\t\tval output = chaptersOutput.remove(chapter) ?: return@withLock false\n\t\toutput.flushAndFinish()\n\t\tflushIndex()\n\t\ttrue\n\t}\n\n\toverride suspend fun finish() = mutex.withLock {\n\t\tflushIndex()\n\t\tfor (output in chaptersOutput.values) {\n\t\t\toutput.flushAndFinish()\n\t\t}\n\t\tchaptersOutput.clear()\n\t}\n\n\toverride suspend fun cleanup() = mutex.withLock {\n\t\tfor (output in chaptersOutput.values) {\n\t\t\toutput.file.deleteAwait()\n\t\t}\n\t}\n\n\toverride fun close() {\n\t\tfor (output in chaptersOutput.values) {\n\t\t\toutput.closeQuietly()\n\t\t}\n\t}\n\n\tsuspend fun deleteChapters(ids: Set<Long>) = mutex.withLock {\n\t\tval chapters = checkNotNull(\n\t\t\t(index.getMangaInfo() ?: LocalMangaParser(rootFile).getManga(withDetails = true).manga).chapters,\n\t\t) {\n\t\t\t\"No chapters found\"\n\t\t}.withIndex()\n\t\tval victimsIds = ids.toMutableSet()\n\t\tfor (chapter in chapters) {\n\t\t\tif (!victimsIds.remove(chapter.value.id)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tval chapterFile = index.getChapterFileName(chapter.value.id)?.let {\n\t\t\t\tFile(rootFile, it)\n\t\t\t} ?: chapter.value.url.toUri().toFile()\n\t\t\tchapterFile.deleteAwait()\n\t\t\tindex.removeChapter(chapter.value.id)\n\t\t}\n\t\tcheck(victimsIds.isEmpty()) {\n\t\t\t\"${victimsIds.size} of ${ids.size} chapters was not removed: not found\"\n\t\t}\n\t}\n\n\tfun setIndex(newIndex: MangaIndex) {\n\t\tindex.setFrom(newIndex)\n\t}\n\n\tprivate suspend fun ZipOutput.flushAndFinish() = runInterruptible(Dispatchers.IO) {\n\t\tval e: Throwable? = try {\n\t\t\tfinish()\n\t\t\tnull\n\t\t} catch (e: Throwable) {\n\t\t\te\n\t\t} finally {\n\t\t\tclose()\n\t\t}\n\t\tif (e == null) {\n\t\t\tval resFile = File(file.absolutePath.removeSuffix(SUFFIX_TMP))\n\t\t\tfile.renameTo(resFile)\n\t\t} else {\n\t\t\tfile.delete()\n\t\t\tthrow e\n\t\t}\n\t}\n\n\tprivate fun chapterFileName(chapter: IndexedValue<MangaChapter>): String {\n\t\tindex.getChapterFileName(chapter.value.id)?.let {\n\t\t\treturn it\n\t\t}\n\t\tval baseName = buildString {\n\t\t\tappend(chapter.index)\n\t\t\tchapter.value.title?.nullIfEmpty()?.let {\n\t\t\t\tappend('_')\n\t\t\t\tappend(it.toFileNameSafe())\n\t\t\t}\n\t\t\tif (length > 32) {\n\t\t\t\tdeleteRange(31, lastIndex)\n\t\t\t}\n\t\t}\n\t\tvar i = 0\n\t\twhile (true) {\n\t\t\tval name = (if (i == 0) baseName else baseName + \"_$i\") + \".cbz\"\n\t\t\tif (!File(rootFile, name).exists()) {\n\t\t\t\treturn name\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t}\n\n\tprivate suspend fun flushIndex() = runInterruptible(Dispatchers.IO) {\n\t\tFile(rootFile, ENTRY_NAME_INDEX).writeText(index.toString())\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val FILENAME_PATTERN = \"%08d_%04d%04d\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/output/LocalMangaOutput.kt",
    "content": "package org.koitharu.kotatsu.local.data.output\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport okio.Closeable\nimport org.koitharu.kotatsu.core.prefs.DownloadFormat\nimport org.koitharu.kotatsu.core.util.ext.MimeType\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toFileNameSafe\nimport org.koitharu.kotatsu.local.data.input.LocalMangaParser\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.File\n\nsealed class LocalMangaOutput(\n\tval rootFile: File,\n) : Closeable {\n\n\tabstract suspend fun mergeWithExisting()\n\n\tabstract suspend fun addCover(file: File, type: MimeType?)\n\n\tabstract suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, type: MimeType?)\n\n\tabstract suspend fun flushChapter(chapter: MangaChapter): Boolean\n\n\tabstract suspend fun finish()\n\n\tabstract suspend fun cleanup()\n\n\tcompanion object {\n\n\t\tconst val ENTRY_NAME_INDEX = \"index.json\"\n\t\tconst val SUFFIX_TMP = \".tmp\"\n\t\tprivate val mutex = Mutex()\n\n\t\tsuspend fun getOrCreate(\n\t\t\troot: File,\n\t\t\tmanga: Manga,\n\t\t\tformat: DownloadFormat,\n\t\t): LocalMangaOutput = withContext(Dispatchers.IO) {\n\t\t\tval targetFormat = if (format == DownloadFormat.AUTOMATIC) {\n\t\t\t\tif (manga.chapters.let { it != null && it.size <= 3 }) {\n\t\t\t\t\tDownloadFormat.SINGLE_CBZ\n\t\t\t\t} else {\n\t\t\t\t\tDownloadFormat.MULTIPLE_CBZ\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tformat\n\t\t\t}\n\t\t\tcheckNotNull(getImpl(root, manga, onlyIfExists = false, format = targetFormat))\n\t\t}\n\n\t\tsuspend fun get(root: File, manga: Manga): LocalMangaOutput? = withContext(Dispatchers.IO) {\n\t\t\tgetImpl(root, manga, onlyIfExists = true, format = DownloadFormat.AUTOMATIC)\n\t\t}\n\n\t\tprivate suspend fun getImpl(\n\t\t\troot: File,\n\t\t\tmanga: Manga,\n\t\t\tonlyIfExists: Boolean,\n\t\t\tformat: DownloadFormat,\n\t\t): LocalMangaOutput? {\n\t\t\tmutex.withLock {\n\t\t\t\tvar i = 0\n\t\t\t\tval baseName = manga.title.toFileNameSafe()\n\t\t\t\twhile (true) {\n\t\t\t\t\tval fileName = if (i == 0) baseName else baseName + \"_$i\"\n\t\t\t\t\tval dir = File(root, fileName)\n\t\t\t\t\tval zip = File(root, \"$fileName.cbz\")\n\t\t\t\t\ti++\n\t\t\t\t\treturn when {\n\t\t\t\t\t\tdir.isDirectory -> {\n\t\t\t\t\t\t\tif (canWriteTo(dir, manga)) {\n\t\t\t\t\t\t\t\tLocalMangaDirOutput(dir, manga)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tzip.isFile -> if (canWriteTo(zip, manga)) {\n\t\t\t\t\t\t\tLocalMangaZipOutput(zip, manga)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t!onlyIfExists -> when (format) {\n\t\t\t\t\t\t\tDownloadFormat.AUTOMATIC -> null\n\t\t\t\t\t\t\tDownloadFormat.SINGLE_CBZ -> LocalMangaZipOutput(zip, manga)\n\t\t\t\t\t\t\tDownloadFormat.MULTIPLE_CBZ -> LocalMangaDirOutput(dir, manga)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\telse -> null\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate suspend fun canWriteTo(file: File, manga: Manga): Boolean {\n\t\t\tval info = runCatchingCancellable {\n\t\t\t\tLocalMangaParser(file).getMangaInfo()\n\t\t\t}.onFailure {\n\t\t\t\tit.printStackTraceDebug()\n\t\t\t}.getOrNull() ?: return false\n\t\t\treturn info.id == manga.id\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/output/LocalMangaUtil.kt",
    "content": "package org.koitharu.kotatsu.local.data.output\n\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nclass LocalMangaUtil(\n\tprivate val manga: Manga,\n) {\n\n\tinit {\n\t\trequire(manga.isLocal) { \"Expected LOCAL source but ${manga.source} found\" }\n\t}\n\n\tsuspend fun deleteChapters(ids: Set<Long>) {\n\t\tval file = manga.url.toUri().toFile()\n\t\tif (file.isDirectory) {\n\t\t\tLocalMangaDirOutput(file, manga).use { output ->\n\t\t\t\toutput.deleteChapters(ids)\n\t\t\t\toutput.finish()\n\t\t\t}\n\t\t} else {\n\t\t\tLocalMangaZipOutput.filterChapters(file, manga, ids)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/data/output/LocalMangaZipOutput.kt",
    "content": "package org.koitharu.kotatsu.local.data.output\n\nimport androidx.annotation.WorkerThread\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport okhttp3.internal.closeQuietly\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.MimeType\nimport org.koitharu.kotatsu.core.util.ext.deleteAwait\nimport org.koitharu.kotatsu.core.util.ext.readText\nimport org.koitharu.kotatsu.core.zip.ZipOutput\nimport org.koitharu.kotatsu.local.data.MangaIndex\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport java.io.File\nimport java.util.zip.ZipFile\n\nclass LocalMangaZipOutput(\n\trootFile: File,\n\tmanga: Manga,\n) : LocalMangaOutput(rootFile) {\n\n\tprivate val output = ZipOutput(File(rootFile.path + \".tmp\"))\n\tprivate val index = MangaIndex(null)\n\tprivate val mutex = Mutex()\n\n\tinit {\n\t\tif (!manga.isLocal) {\n\t\t\tindex.setMangaInfo(manga)\n\t\t}\n\t}\n\n\toverride suspend fun mergeWithExisting() = mutex.withLock {\n\t\tif (rootFile.exists()) {\n\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\tmergeWith(rootFile)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride suspend fun addCover(file: File, type: MimeType?) = mutex.withLock {\n\t\tval name = buildString {\n\t\t\tappend(FILENAME_PATTERN.format(0, 0, 0))\n\t\t\tMimeTypes.getExtension(type)?.let { ext ->\n\t\t\t\tappend('.')\n\t\t\t\tappend(ext)\n\t\t\t}\n\t\t}\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\toutput.put(name, file)\n\t\t}\n\t\tindex.setCoverEntry(name)\n\t}\n\n\toverride suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, type: MimeType?) =\n\t\tmutex.withLock {\n\t\t\tval name = buildString {\n\t\t\t\tappend(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber))\n\t\t\t\tMimeTypes.getExtension(type)?.let { ext ->\n\t\t\t\t\tappend('.')\n\t\t\t\t\tappend(ext)\n\t\t\t\t}\n\t\t\t}\n\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\toutput.put(name, file)\n\t\t\t}\n\t\t\tindex.addChapter(chapter, null)\n\t\t}\n\n\toverride suspend fun flushChapter(chapter: MangaChapter): Boolean = false\n\n\toverride suspend fun finish() = mutex.withLock {\n\t\trunInterruptible(Dispatchers.IO) {\n\t\t\toutput.use { output ->\n\t\t\t\toutput.put(ENTRY_NAME_INDEX, index.toString())\n\t\t\t\toutput.finish()\n\t\t\t}\n\t\t}\n\t\trootFile.deleteAwait()\n\t\toutput.file.renameTo(rootFile)\n\t\tUnit\n\t}\n\n\toverride suspend fun cleanup() = mutex.withLock {\n\t\toutput.file.deleteAwait()\n\t\tUnit\n\t}\n\n\toverride fun close() {\n\t\toutput.close()\n\t}\n\n\t@WorkerThread\n\tprivate fun mergeWith(other: File) {\n\t\tvar otherIndex: MangaIndex? = null\n\t\tZipFile(other).use { zip ->\n\t\t\tfor (entry in zip.entries()) {\n\t\t\t\tif (entry.name == ENTRY_NAME_INDEX) {\n\t\t\t\t\totherIndex = MangaIndex(\n\t\t\t\t\t\tzip.getInputStream(entry).use {\n\t\t\t\t\t\t\tit.reader().readText()\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\toutput.copyEntryFrom(zip, entry)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\totherIndex?.getMangaInfo()?.chapters?.withIndex()?.let { chapters ->\n\t\t\tfor (chapter in chapters) {\n\t\t\t\tindex.addChapter(chapter, null)\n\t\t\t}\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val FILENAME_PATTERN = \"%08d_%04d%04d\"\n\n\t\tsuspend fun filterChapters(file: File, manga: Manga, idsToRemove: Set<Long>) =\n\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\tval subject = LocalMangaZipOutput(file, manga)\n\t\t\t\ttry {\n\t\t\t\t\tZipFile(subject.rootFile).use { zip ->\n\t\t\t\t\t\tval index = MangaIndex(zip.readText(zip.getEntry(ENTRY_NAME_INDEX)))\n\t\t\t\t\t\tidsToRemove.forEach { id -> index.removeChapter(id) }\n\t\t\t\t\t\tval patterns = requireNotNull(index.getMangaInfo()?.chapters).map {\n\t\t\t\t\t\t\tindex.getChapterNamesPattern(it)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tval coverEntryName = index.getCoverEntry()\n\t\t\t\t\t\tfor (entry in zip.entries()) {\n\t\t\t\t\t\t\twhen {\n\t\t\t\t\t\t\t\tentry.name == ENTRY_NAME_INDEX -> {\n\t\t\t\t\t\t\t\t\tsubject.output.put(ENTRY_NAME_INDEX, index.toString())\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tentry.isDirectory -> {\n\t\t\t\t\t\t\t\t\tsubject.output.addDirectory(entry.name)\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tentry.name == coverEntryName -> {\n\t\t\t\t\t\t\t\t\tsubject.output.copyEntryFrom(zip, entry)\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\telse -> {\n\t\t\t\t\t\t\t\t\tval name = entry.name.substringBefore('.')\n\t\t\t\t\t\t\t\t\tif (patterns.any { it.matches(name) }) {\n\t\t\t\t\t\t\t\t\t\tsubject.output.copyEntryFrom(zip, entry)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsubject.output.finish()\n\t\t\t\t\t\tsubject.output.close()\n\t\t\t\t\t\tsubject.rootFile.delete()\n\t\t\t\t\t\tsubject.output.file.renameTo(subject.rootFile)\n\t\t\t\t\t}\n\t\t\t\t} catch (e: Throwable) {\n\t\t\t\t\tsubject.closeQuietly()\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsubject.output.file.delete()\n\t\t\t\t\t} catch (e2: Throwable) {\n\t\t\t\t\t\te.addSuppressed(e2)\n\t\t\t\t\t}\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/domain/DeleteLocalMangaUseCase.kt",
    "content": "package org.koitharu.kotatsu.local.domain\n\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.io.IOException\nimport javax.inject.Inject\n\nclass DeleteLocalMangaUseCase @Inject constructor(\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val historyRepository: HistoryRepository,\n) {\n\n\tsuspend operator fun invoke(manga: Manga) {\n\t\tval victim = if (manga.isLocal) manga else localMangaRepository.findSavedManga(manga)?.manga\n\t\tcheckNotNull(victim) { \"Cannot find saved manga for ${manga.title}\" }\n\t\tval original = if (manga.isLocal) localMangaRepository.getRemoteManga(manga) else manga\n\t\tlocalMangaRepository.delete(victim) || throw IOException(\"Unable to delete file\")\n\t\trunCatchingCancellable {\n\t\t\thistoryRepository.deleteOrSwap(victim, original)\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}\n\t}\n\n\tsuspend operator fun invoke(ids: Set<Long>) {\n\t\tval list = localMangaRepository.getList(0, null, null)\n\t\tvar removed = 0\n\t\tfor (manga in list) {\n\t\t\tif (manga.id in ids) {\n\t\t\t\tinvoke(manga)\n\t\t\t\tremoved++\n\t\t\t}\n\t\t}\n\t\tcheck(removed == ids.size) {\n\t\t\t\"Removed $removed files but ${ids.size} requested\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/domain/DeleteReadChaptersUseCase.kt",
    "content": "package org.koitharu.kotatsu.local.domain\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.buffer\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.fold\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.core.model.ids\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.recoverCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\nclass DeleteReadChaptersUseCase @Inject constructor(\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\n\tsuspend operator fun invoke(manga: Manga): Int {\n\t\tval localManga = if (manga.isLocal) {\n\t\t\tLocalManga(manga)\n\t\t} else {\n\t\t\tcheckNotNull(localMangaRepository.findSavedManga(manga)) { \"Cannot find local manga\" }\n\t\t}\n\t\tval task = getDeletionTask(localManga) ?: return 0\n\t\tlocalMangaRepository.deleteChapters(task.manga.manga, task.chaptersIds)\n\t\treturn task.chaptersIds.size\n\t}\n\n\tsuspend operator fun invoke(): Int {\n\t\tval list = localMangaRepository.getList(0, null, null)\n\t\tif (list.isEmpty()) {\n\t\t\treturn 0\n\t\t}\n\t\treturn channelFlow {\n\t\t\tfor (manga in list) {\n\t\t\t\tlaunch(Dispatchers.Default) {\n\t\t\t\t\tval task = runCatchingCancellable {\n\t\t\t\t\t\tgetDeletionTask(LocalManga(manga))\n\t\t\t\t\t}.onFailure {\n\t\t\t\t\t\tit.printStackTraceDebug()\n\t\t\t\t\t}.getOrNull()\n\t\t\t\t\tif (task != null) {\n\t\t\t\t\t\tsend(task)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}.buffer().map {\n\t\t\trunCatchingCancellable {\n\t\t\t\tlocalMangaRepository.deleteChapters(it.manga.manga, it.chaptersIds)\n\t\t\t\tit.chaptersIds.size\n\t\t\t}.onFailure {\n\t\t\t\tit.printStackTraceDebug()\n\t\t\t}.getOrDefault(0)\n\t\t}.fold(0) { acc, x -> acc + x }\n\t}\n\n\tprivate suspend fun getDeletionTask(manga: LocalManga): DeletionTask? {\n\t\tval history = historyRepository.getOne(manga.manga) ?: return null\n\t\tval chapters = getAllChapters(manga)\n\t\tif (chapters.isEmpty()) {\n\t\t\treturn null\n\t\t}\n\t\tval branch = (chapters.findById(history.chapterId) ?: return null).branch\n\t\tval filteredChapters = chapters.filter { x -> x.branch == branch }.takeWhile { it.id != history.chapterId }\n\t\treturn if (filteredChapters.isEmpty()) {\n\t\t\tnull\n\t\t} else {\n\t\t\tDeletionTask(\n\t\t\t\tmanga = manga,\n\t\t\t\tchaptersIds = filteredChapters.ids(),\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate suspend fun getAllChapters(manga: LocalManga): List<MangaChapter> = runCatchingCancellable {\n\t\tval remoteManga = checkNotNull(localMangaRepository.getRemoteManga(manga.manga))\n\t\tcheckNotNull(mangaRepositoryFactory.create(remoteManga.source).getDetails(remoteManga).chapters)\n\t}.recoverCatchingCancellable {\n\t\tcheckNotNull(\n\t\t\tmanga.manga.chapters.let {\n\t\t\t\tif (it.isNullOrEmpty()) {\n\t\t\t\t\tlocalMangaRepository.getDetails(manga.manga).chapters\n\t\t\t\t} else {\n\t\t\t\t\tit\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t}.getOrDefault(manga.manga.chapters.orEmpty())\n\n\tprivate class DeletionTask(\n\t\tval manga: LocalManga,\n\t\tval chaptersIds: Set<Long>,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/domain/LocalObserveMapper.kt",
    "content": "package org.koitharu.kotatsu.local.domain\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.mapLatest\nimport kotlinx.coroutines.flow.onStart\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndex\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nabstract class LocalObserveMapper<E : Any, R : Any>(\n\tprivate val localMangaIndex: LocalMangaIndex,\n) {\n\n\tprotected fun Flow<Collection<E>>.mapToLocal() = onStart {\n\t\tlocalMangaIndex.updateIfRequired()\n\t}.mapLatest {\n\t\tit.mapToLocal()\n\t}\n\n\tprivate suspend fun Collection<E>.mapToLocal(): List<R> = coroutineScope {\n\t\tval dispatcher = Dispatchers.IO.limitedParallelism(6)\n\t\tmap { item ->\n\t\t\tval m = toManga(item)\n\t\t\tasync(dispatcher) {\n\t\t\t\tval mapped = if (m.isLocal) {\n\t\t\t\t\tm\n\t\t\t\t} else {\n\t\t\t\t\tlocalMangaIndex.get(m.id, withDetails = false)?.manga\n\t\t\t\t}\n\t\t\t\tmapped?.let { mm -> toResult(item, mm) }\n\t\t\t}\n\t\t}.awaitAll().filterNotNull()\n\t}\n\n\tprotected abstract fun toManga(e: E): Manga\n\n\tprotected abstract fun toResult(e: E, manga: Manga): R\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/domain/MangaLock.kt",
    "content": "package org.koitharu.kotatsu.local.domain\n\nimport org.koitharu.kotatsu.core.util.MultiMutex\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass MangaLock @Inject constructor() : MultiMutex<Manga>()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/domain/model/LocalManga.kt",
    "content": "package org.koitharu.kotatsu.local.domain.model\n\nimport android.net.Uri\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.core.util.ext.contains\nimport org.koitharu.kotatsu.core.util.ext.creationTime\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport java.io.File\n\ndata class LocalManga(\n\tval manga: Manga,\n\tval file: File = manga.url.toUri().toFile(),\n) {\n\n\tvar createdAt: Long = -1L\n\t\tprivate set\n\t\tget() {\n\t\t\tif (field == -1L) {\n\t\t\t\tfield = file.creationTime\n\t\t\t}\n\t\t\treturn field\n\t\t}\n\n\tfun toUri(): Uri = manga.url.toUri()\n\n\tfun isMatchesQuery(query: String): Boolean {\n\t\treturn manga.title.contains(query, ignoreCase = true) ||\n\t\t\tmanga.altTitles.contains(query, ignoreCase = true) ||\n\t\t\tmanga.authors.contains(query, ignoreCase = true)\n\t}\n\n\tfun containsTags(tags: Collection<String>): Boolean {\n\t\treturn tags.all { tag -> tag in manga.tags }\n\t}\n\n\tfun containsAnyTag(tags: Collection<String>): Boolean {\n\t\treturn tags.any { tag -> tag in manga.tags }\n\t}\n\n\tprivate operator fun Collection<MangaTag>.contains(title: String): Boolean {\n\t\treturn any { it.title.equals(title, ignoreCase = true) }\n\t}\n\n\toverride fun toString(): String {\n\t\treturn \"LocalManga(${file.path}: ${manga.title})\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportDialogFragment.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.os.OpenDocumentTreeHelper\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.databinding.DialogImportBinding\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.OnClickListener {\n\n\t@Inject\n\tlateinit var storageManager: LocalStorageManager\n\n\tprivate val importFileCall = registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) {\n\t\tstartImport(it)\n\t}\n\tprivate val importDirCall = OpenDocumentTreeHelper(this) {\n\t\tstartImport(listOfNotNull(it))\n\t}\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogImportBinding {\n\t\treturn DialogImportBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder)\n\t\t\t.setTitle(R.string._import)\n\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t\t\t.setCancelable(true)\n\t}\n\n\toverride fun onViewBindingCreated(binding: DialogImportBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.buttonDir.setOnClickListener(this)\n\t\tbinding.buttonFile.setOnClickListener(this)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\tval res = when (v.id) {\n\t\t\tR.id.button_file -> importFileCall.tryLaunch(arrayOf(\"*/*\"))\n\t\t\tR.id.button_dir -> importDirCall.tryLaunch(null)\n\t\t\telse -> true\n\t\t}\n\t\tif (!res) {\n\t\t\tToast.makeText(v.context, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()\n\t\t}\n\t}\n\n\tprivate fun startImport(uris: Collection<Uri>) {\n\t\tif (uris.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\turis.forEach {\n\t\t\tstorageManager.takePermissions(it)\n\t\t}\n\t\tval ctx = requireContext()\n\t\tval msg = if (ImportService.start(ctx, uris)) {\n\t\t\tR.string.import_will_start_soon\n\t\t} else {\n\t\t\tR.string.error_occurred\n\t\t}\n\t\tToast.makeText(ctx, msg, Toast.LENGTH_LONG).show()\n\t\tdismiss()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/ImportService.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport android.net.Uri\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.content.ContextCompat\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ErrorReporterReceiver\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.powerManager\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toBitmapOrNull\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.core.util.ext.withPartialWakeLock\nimport org.koitharu.kotatsu.local.data.importer.SingleMangaImporter\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ImportService : CoroutineIntentService() {\n\n\t@Inject\n\tlateinit var importer: SingleMangaImporter\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate lateinit var notificationManager: NotificationManagerCompat\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tnotificationManager = NotificationManagerCompat.from(applicationContext)\n\t}\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tval uri = requireNotNull(intent.getStringExtra(DATA_URI)?.toUriOrNull()) { \"No input uri\" }\n\t\tstartForeground(this)\n\t\tpowerManager.withPartialWakeLock(TAG) {\n\t\t\tval result = runCatchingCancellable {\n\t\t\t\timporter.import(uri).manga\n\t\t\t}\n\t\t\tif (applicationContext.checkNotificationPermission(CHANNEL_ID)) {\n\t\t\t\tval notification = buildNotification(startId, result)\n\t\t\t\tnotificationManager.notify(TAG, startId, notification)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) {\n\t\tif (applicationContext.checkNotificationPermission(CHANNEL_ID)) {\n\t\t\tval notification = runBlocking { buildNotification(startId, Result.failure(error)) }\n\t\t\tnotificationManager.notify(TAG, startId, notification)\n\t\t}\n\t}\n\n\t@SuppressLint(\"InlinedApi\")\n\tprivate fun startForeground(jobContext: IntentJobContext) {\n\t\tval title = applicationContext.getString(R.string.importing_manga)\n\t\tval channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)\n\t\t\t.setName(title)\n\t\t\t.setShowBadge(false)\n\t\t\t.setVibrationEnabled(false)\n\t\t\t.setSound(null, null)\n\t\t\t.setLightsEnabled(false)\n\t\t\t.build()\n\t\tnotificationManager.createNotificationChannel(channel)\n\n\t\tval notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\t\t.setContentTitle(title)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_MIN)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setOngoing(true)\n\t\t\t.setProgress(0, 0, true)\n\t\t\t.setSmallIcon(android.R.drawable.stat_sys_download)\n\t\t\t.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)\n\t\t\t.setCategory(NotificationCompat.CATEGORY_PROGRESS)\n\t\t\t.build()\n\n\t\tjobContext.setForeground(\n\t\t\tFOREGROUND_NOTIFICATION_ID,\n\t\t\tnotification,\n\t\t\tServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,\n\t\t)\n\t}\n\n\tprivate suspend fun buildNotification(startId: Int, result: Result<Manga>): Notification {\n\t\tval notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_DEFAULT)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setAutoCancel(true)\n\t\tresult.onSuccess { manga ->\n\t\t\tnotification.setLargeIcon(\n\t\t\t\tcoil.execute(\n\t\t\t\t\tImageRequest.Builder(applicationContext)\n\t\t\t\t\t\t.data(manga.coverUrl)\n\t\t\t\t\t\t.mangaSourceExtra(manga.source)\n\t\t\t\t\t\t.build(),\n\t\t\t\t).toBitmapOrNull(),\n\t\t\t)\n\t\t\tnotification.setSubText(manga.title)\n\t\t\tval intent = AppRouter.detailsIntent(applicationContext, manga)\n\t\t\tnotification.setContentIntent(\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\tmanga.id.toInt(),\n\t\t\t\t\tintent,\n\t\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t).setVisibility(\n\t\t\t\tif (manga.isNsfw()) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,\n\t\t\t)\n\t\t\tnotification.setContentTitle(applicationContext.getString(R.string.import_completed))\n\t\t\t\t.setContentText(applicationContext.getString(R.string.import_completed_hint))\n\t\t\t\t.setSmallIcon(R.drawable.ic_stat_done)\n\t\t\tNotificationCompat.BigTextStyle(notification)\n\t\t\t\t.bigText(applicationContext.getString(R.string.import_completed_hint))\n\t\t}.onFailure { error ->\n\t\t\tnotification.setContentTitle(applicationContext.getString(R.string.error_occurred))\n\t\t\t\t.setContentText(error.getDisplayMessage(applicationContext.resources))\n\t\t\t\t.setSmallIcon(android.R.drawable.stat_notify_error)\n\t\t\tErrorReporterReceiver.getNotificationAction(\n\t\t\t\tcontext = applicationContext,\n\t\t\t\te = error,\n\t\t\t\tnotificationId = startId,\n\t\t\t\tnotificationTag = TAG,\n\t\t\t)?.let { action ->\n\t\t\t\tnotification.addAction(action)\n\t\t\t}\n\t\t}\n\t\treturn notification.build()\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val DATA_URI = \"uri\"\n\t\tprivate const val TAG = \"import\"\n\t\tprivate const val CHANNEL_ID = \"importing\"\n\t\tprivate const val FOREGROUND_NOTIFICATION_ID = 37\n\n\t\tfun start(context: Context, uris: Collection<Uri>): Boolean = try {\n\t\t\trequire(uris.isNotEmpty())\n\t\t\tfor (uri in uris) {\n\t\t\t\tval intent = Intent(context, ImportService::class.java)\n\t\t\t\tintent.putExtra(DATA_URI, uri.toString())\n\t\t\t\tContextCompat.startForegroundService(context, intent)\n\t\t\t}\n\t\t\ttrue\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTraceDebug()\n\t\t\tfalse\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalChaptersRemoveService.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.annotation.SuppressLint\nimport android.app.NotificationManager\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.content.ContextCompat\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ErrorReporterReceiver\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.powerManager\nimport org.koitharu.kotatsu.core.util.ext.withPartialWakeLock\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass LocalChaptersRemoveService : CoroutineIntentService() {\n\n\t@Inject\n\tlateinit var localMangaRepository: LocalMangaRepository\n\n\t@Inject\n\t@LocalStorageChanges\n\tlateinit var localStorageChanges: MutableSharedFlow<LocalManga?>\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tisRunning = true\n\t}\n\n\toverride fun onDestroy() {\n\t\tisRunning = false\n\t\tsuper.onDestroy()\n\t}\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tstartForeground(this)\n\t\tval manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return\n\t\tval chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return\n\t\tpowerManager.withPartialWakeLock(TAG) {\n\t\t\tval mangaWithChapters = localMangaRepository.getDetails(manga)\n\t\t\tlocalMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)\n\t\t\tlocalStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))\n\t\t}\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) {\n\t\tval notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\t\t.setContentTitle(getString(R.string.error_occurred))\n\t\t\t.setPriority(NotificationCompat.PRIORITY_DEFAULT)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setContentText(error.getDisplayMessage(resources))\n\t\t\t.setSmallIcon(android.R.drawable.stat_notify_error)\n\t\t\t.setAutoCancel(true)\n\t\t\t.setContentIntent(ErrorReporterReceiver.getPendingIntent(applicationContext, error))\n\t\t\t.build()\n\t\tval nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager\n\t\tnm.notify(NOTIFICATION_ID + startId, notification)\n\t}\n\n\t@SuppressLint(\"InlinedApi\")\n\tprivate fun startForeground(jobContext: IntentJobContext) {\n\t\tval title = getString(R.string.local_manga_processing)\n\t\tval manager = NotificationManagerCompat.from(this)\n\t\tval channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)\n\t\t\t.setName(title)\n\t\t\t.setShowBadge(false)\n\t\t\t.setVibrationEnabled(false)\n\t\t\t.setSound(null, null)\n\t\t\t.setLightsEnabled(false)\n\t\t\t.build()\n\t\tmanager.createNotificationChannel(channel)\n\n\t\tval notification = NotificationCompat.Builder(this, CHANNEL_ID)\n\t\t\t.setContentTitle(title)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_MIN)\n\t\t\t.setDefaults(0)\n\t\t\t.setSilent(true)\n\t\t\t.setProgress(0, 0, true)\n\t\t\t.setSmallIcon(android.R.drawable.stat_notify_sync)\n\t\t\t.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)\n\t\t\t.setOngoing(false)\n\t\t\t.build()\n\t\tjobContext.setForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)\n\t}\n\n\tcompanion object {\n\n\t\tvar isRunning: Boolean = false\n\t\t\tprivate set\n\n\t\tprivate const val CHANNEL_ID = \"local_processing\"\n\t\tprivate const val NOTIFICATION_ID = 21\n\n\t\tprivate const val EXTRA_MANGA = \"manga\"\n\t\tprivate const val EXTRA_CHAPTERS_IDS = \"chapters_ids\"\n\n\t\tprivate const val TAG = CHANNEL_ID\n\n\t\tfun start(context: Context, manga: Manga, chaptersIds: Collection<Long>) {\n\t\t\tif (chaptersIds.isEmpty()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval intent = Intent(context, LocalChaptersRemoveService::class.java)\n\t\t\tintent.putExtra(EXTRA_MANGA, ParcelableManga(manga))\n\t\t\tintent.putExtra(EXTRA_CHAPTERS_IDS, chaptersIds.toLongArray())\n\t\t\tContextCompat.startForegroundService(context, intent)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalIndexUpdateService.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.content.Intent\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.ui.CoroutineIntentService\nimport org.koitharu.kotatsu.local.data.index.LocalMangaIndex\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass LocalIndexUpdateService : CoroutineIntentService() {\n\n\t@Inject\n\tlateinit var localMangaIndex: LocalMangaIndex\n\n\toverride suspend fun IntentJobContext.processIntent(intent: Intent) {\n\t\tlocalMangaIndex.update()\n\t}\n\n\toverride fun IntentJobContext.onError(error: Throwable) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.Manifest\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.widgets.TipView\nimport org.koitharu.kotatsu.core.util.ShareHelper\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.databinding.FragmentListBinding\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\nimport org.koitharu.kotatsu.remotelist.ui.MangaSearchMenuProvider\nimport org.koitharu.kotatsu.remotelist.ui.RemoteListFragment\nimport org.koitharu.kotatsu.settings.storage.RequestStorageManagerPermissionContract\n\nclass LocalListFragment : MangaListFragment(), FilterCoordinator.Owner {\n\n\tprivate val permissionRequestLauncher = registerForActivityResult(\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n\t\t\tRequestStorageManagerPermissionContract()\n\t\t} else {\n\t\t\tActivityResultContracts.RequestPermission()\n\t\t},\n\t) {\n\t\tif (it) {\n\t\t\tviewModel.onRefresh()\n\t\t}\n\t}\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tval args = arguments ?: Bundle(1)\n\t\targs.putString(\n\t\t\tRemoteListFragment.ARG_SOURCE,\n\t\t\tLocalMangaSource.name,\n\t\t) // required by FilterCoordinator\n\t\targuments = args\n\t}\n\n\toverride val viewModel by viewModels<LocalListViewModel>()\n\n\toverride val filterCoordinator: FilterCoordinator\n\t\tget() = viewModel.filterCoordinator\n\n\toverride fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\taddMenuProvider(LocalListMenuProvider(this, this::onEmptyActionClick))\n\t\taddMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel))\n\t\tviewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() }\n\t}\n\n\toverride fun onEmptyActionClick() {\n\t\trouter.showImportDialog()\n\t}\n\n\toverride fun onFilterClick(view: View?) {\n\t\trouter.showFilterSheet()\n\t}\n\n\toverride fun onPrimaryButtonClick(tipView: TipView) {\n\t\tif (!permissionRequestLauncher.tryLaunch(Manifest.permission.READ_EXTERNAL_STORAGE)) {\n\t\t\tSnackbar.make(tipView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\t}\n\t}\n\n\toverride fun onSecondaryButtonClick(tipView: TipView) {\n\t\trouter.openDirectoriesSettings()\n\t}\n\n\toverride fun onScrolledToEnd() = viewModel.loadNextPage()\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu,\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_local, menu)\n\t\treturn super.onCreateActionMode(controller, menuInflater, menu)\n\t}\n\n\toverride fun onActionItemClicked(\n\t\tcontroller: ListSelectionController,\n\t\tmode: ActionMode?,\n\t\titem: MenuItem,\n\t): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_remove -> {\n\t\t\t\tshowDeletionConfirm(selectedItemsIds, mode)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_share -> {\n\t\t\t\tval files = selectedItems.map { it.url.toUri().toFile() }\n\t\t\t\tShareHelper(requireContext()).shareCbz(files)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onActionItemClicked(controller, mode, item)\n\t\t}\n\t}\n\n\tprivate fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode?) {\n\t\tMaterialAlertDialogBuilder(context ?: return)\n\t\t\t.setTitle(R.string.delete_manga)\n\t\t\t.setMessage(getString(R.string.text_delete_local_manga_batch))\n\t\t\t.setPositiveButton(R.string.delete) { _, _ ->\n\t\t\t\tviewModel.delete(ids)\n\t\t\t\tmode?.finish()\n\t\t\t}\n\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t\t\t.show()\n\t}\n\n\tprivate fun onItemRemoved() {\n\t\tSnackbar.make(\n\t\t\trequireViewBinding().recyclerView,\n\t\t\tR.string.removal_completed,\n\t\t\tSnackbar.LENGTH_SHORT,\n\t\t).show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.Fragment\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\n\nclass LocalListMenuProvider(\n\tprivate val fragment: Fragment,\n\tprivate val onImportClick: Function0<Unit>,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_local, menu)\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tmenu.findItem(R.id.action_filter)?.isVisible = fragment.router.isFilterSupported()\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\treturn when (menuItem.itemId) {\n\t\t\tR.id.action_import -> {\n\t\t\t\tonImportClick()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_directories -> {\n\t\t\t\tfragment.router.openDirectoriesSettings()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_filter -> {\n\t\t\t\tfragment.router.showFilterSheet()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.content.SharedPreferences\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharedFlow\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.toChipModel\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.toFileOrNull\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.explore.domain.ExploreRepository\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.domain.QuickFilterListener\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.model.QuickFilter\nimport org.koitharu.kotatsu.list.ui.model.TipModel\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel\nimport javax.inject.Inject\n\n@HiltViewModel\nclass LocalListViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n\tfilterCoordinator: FilterCoordinator,\n\tprivate val settings: AppSettings,\n\tmangaListMapper: MangaListMapper,\n\tprivate val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,\n\texploreRepository: ExploreRepository,\n\t@param:LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,\n\tprivate val localStorageManager: LocalStorageManager,\n\tsourcesRepository: MangaSourcesRepository,\n\tmangaDataRepository: MangaDataRepository,\n) : RemoteListViewModel(\n\tsavedStateHandle = savedStateHandle,\n\tmangaRepositoryFactory = mangaRepositoryFactory,\n\tfilterCoordinator = filterCoordinator,\n\tsettings = settings,\n\tmangaListMapper = mangaListMapper,\n\texploreRepository = exploreRepository,\n\tsourcesRepository = sourcesRepository,\n\tmangaDataRepository = mangaDataRepository,\n\tlocalStorageChanges = localStorageChanges,\n), SharedPreferences.OnSharedPreferenceChangeListener, QuickFilterListener {\n\n\tval onMangaRemoved = MutableEventFlow<Unit>()\n\tprivate val showInlineFilter: Boolean = savedStateHandle[AppRouter.KEY_IS_BOTTOMTAB] ?: false\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tlocalStorageChanges\n\t\t\t\t.collect {\n\t\t\t\t\tloadList(filterCoordinator.snapshot(), append = false).join()\n\t\t\t\t}\n\t\t}\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride suspend fun onBuildList(list: MutableList<ListModel>) {\n\t\tsuper.onBuildList(list)\n\t\tif (showInlineFilter) {\n\t\t\tcreateFilterHeader(maxCount = 16)?.let {\n\t\t\t\tlist.add(0, it)\n\t\t\t}\n\t\t}\n\t\tif (!localStorageManager.hasExternalStoragePermission(isReadOnly = true)) {\n\t\t\tfor (item in list) {\n\t\t\t\tif (item !is MangaListModel) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tval file = item.manga.url.toUriOrNull()?.toFileOrNull() ?: continue\n\t\t\t\tif (localStorageManager.isOnExternalStorage(file)) {\n\t\t\t\t\tval tip = TipModel(\n\t\t\t\t\t\tkey = \"permission\",\n\t\t\t\t\t\ttitle = R.string.external_storage,\n\t\t\t\t\t\ttext = R.string.missing_storage_permission,\n\t\t\t\t\t\ticon = R.drawable.ic_storage,\n\t\t\t\t\t\tprimaryButtonText = R.string.fix,\n\t\t\t\t\t\tsecondaryButtonText = R.string.settings,\n\t\t\t\t\t)\n\t\t\t\t\tlist.add(0, tip)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun setFilterOption(option: ListFilterOption, isApplied: Boolean) {\n\t\tif (option is ListFilterOption.Tag) {\n\t\t\tfilterCoordinator.toggleTag(option.tag, isApplied)\n\t\t}\n\t}\n\n\toverride fun toggleFilterOption(option: ListFilterOption) {\n\t\tif (option is ListFilterOption.Tag) {\n\t\t\tval tag = option.tag\n\t\t\tval isSelected = tag in filterCoordinator.snapshot().listFilter.tags\n\t\t\tfilterCoordinator.toggleTag(option.tag, !isSelected)\n\t\t}\n\t}\n\n\toverride fun clearFilter() = filterCoordinator.reset()\n\n\toverride fun onCleared() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onCleared()\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\tif (key == AppSettings.KEY_LOCAL_MANGA_DIRS) {\n\t\t\tonRefresh()\n\t\t}\n\t}\n\n\tfun delete(ids: Set<Long>) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tdeleteLocalMangaUseCase(ids)\n\t\t\tonMangaRemoved.call(Unit)\n\t\t}\n\t}\n\n\toverride suspend fun mapMangaList(\n\t\tdestination: MutableCollection<in ListModel>,\n\t\tmanga: Collection<Manga>,\n\t\tmode: ListMode\n\t) = mangaListMapper.toListModelList(destination, manga, mode, MangaListMapper.NO_SAVED)\n\n\toverride fun createEmptyState(canResetFilter: Boolean): EmptyState = if (canResetFilter) {\n\t\tsuper.createEmptyState(true)\n\t} else {\n\t\tEmptyState(\n\t\t\ticon = R.drawable.ic_empty_local,\n\t\t\ttextPrimary = R.string.text_local_holder_primary,\n\t\t\ttextSecondary = R.string.text_local_holder_secondary,\n\t\t\tactionStringRes = R.string._import,\n\t\t)\n\t}\n\n\tprivate suspend fun createFilterHeader(maxCount: Int): QuickFilter? {\n\t\tval appliedTags = filterCoordinator.snapshot().listFilter.tags\n\t\tval availableTags = repository.getFilterOptions().availableTags\n\t\tif (appliedTags.isEmpty() && availableTags.size < 3) {\n\t\t\treturn null\n\t\t}\n\t\tval result = ArrayList<ChipsView.ChipModel>(minOf(availableTags.size, maxCount))\n\t\tappliedTags.mapTo(result) { tag ->\n\t\t\tListFilterOption.Tag(tag).toChipModel(isChecked = true)\n\t\t}\n\t\tfor (tag in availableTags) {\n\t\t\tif (result.size >= maxCount) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif (tag in appliedTags) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult.add(ListFilterOption.Tag(tag).toChipModel(isChecked = false))\n\t\t}\n\t\treturn QuickFilter(result)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalStorageCleanupWorker.kt",
    "content": "package org.koitharu.kotatsu.local.ui\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport android.os.Build\nimport android.provider.Settings\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.hilt.work.HiltWorker\nimport androidx.work.BackoffPolicy\nimport androidx.work.CoroutineWorker\nimport androidx.work.ExistingWorkPolicy\nimport androidx.work.ForegroundInfo\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.OutOfQuotaPolicy\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport androidx.work.await\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedInject\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.domain.DeleteReadChaptersUseCase\nimport java.util.concurrent.TimeUnit\n\n@HiltWorker\nclass LocalStorageCleanupWorker @AssistedInject constructor(\n\t@Assisted appContext: Context,\n\t@Assisted params: WorkerParameters,\n\tprivate val settings: AppSettings,\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val dataRepository: MangaDataRepository,\n\tprivate val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,\n) : CoroutineWorker(appContext, params) {\n\n\toverride suspend fun doWork(): Result {\n\t\tif (settings.isAutoLocalChaptersCleanupEnabled) {\n\t\t\tdeleteReadChaptersUseCase.invoke()\n\t\t}\n\t\treturn if (localMangaRepository.cleanup()) {\n\t\t\tdataRepository.cleanupLocalManga()\n\t\t\tResult.success()\n\t\t} else {\n\t\t\tResult.retry()\n\t\t}\n\t}\n\n\toverride suspend fun getForegroundInfo(): ForegroundInfo {\n\t\tval title = applicationContext.getString(R.string.local_storage_cleanup)\n\t\tval channel = NotificationChannelCompat.Builder(WORKER_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)\n\t\t\t.setName(title)\n\t\t\t.setShowBadge(true)\n\t\t\t.setVibrationEnabled(false)\n\t\t\t.setSound(null, null)\n\t\t\t.setLightsEnabled(true)\n\t\t\t.build()\n\t\tNotificationManagerCompat.from(applicationContext).createNotificationChannel(channel)\n\n\t\tval notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)\n\t\t\t.setContentTitle(title)\n\t\t\t.setContentIntent(\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\t0,\n\t\t\t\t\tAppRouter.suggestionsSettingsIntent(applicationContext),\n\t\t\t\t\t0,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_MIN)\n\t\t\t.setCategory(NotificationCompat.CATEGORY_SERVICE)\n\t\t\t.setDefaults(0)\n\t\t\t.setOngoing(false)\n\t\t\t.setSilent(true)\n\t\t\t.setProgress(0, 0, true)\n\t\t\t.setSmallIcon(android.R.drawable.stat_notify_sync)\n\t\t\t.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\tval actionIntent = PendingIntentCompat.getActivity(\n\t\t\t\tapplicationContext, SETTINGS_ACTION_CODE,\n\t\t\t\tIntent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)\n\t\t\t\t\t.putExtra(Settings.EXTRA_APP_PACKAGE, applicationContext.packageName)\n\t\t\t\t\t.putExtra(Settings.EXTRA_CHANNEL_ID, WORKER_CHANNEL_ID),\n\t\t\t\t0, false,\n\t\t\t)\n\t\t\tnotification.addAction(\n\t\t\t\tR.drawable.ic_settings,\n\t\t\t\tapplicationContext.getString(R.string.notifications_settings),\n\t\t\t\tactionIntent,\n\t\t\t)\n\t\t}\n\t\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\t\tForegroundInfo(WORKER_NOTIFICATION_ID, notification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)\n\t\t} else {\n\t\t\tForegroundInfo(WORKER_NOTIFICATION_ID, notification.build())\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val TAG = \"cleanup\"\n\t\tprivate const val WORKER_CHANNEL_ID = \"storage_cleanup\"\n\t\tprivate const val WORKER_NOTIFICATION_ID = 32\n\t\tprivate const val SETTINGS_ACTION_CODE = 6\n\n\t\tsuspend fun enqueue(context: Context) {\n\t\t\tval request = OneTimeWorkRequestBuilder<LocalStorageCleanupWorker>()\n\t\t\t\t.addTag(TAG)\n\t\t\t\t.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)\n\t\t\t\t.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)\n\t\t\t\t.build()\n\t\t\tWorkManager.getInstance(context).enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request).await()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/info/LocalInfoDialog.kt",
    "content": "package org.koitharu.kotatsu.local.ui.info\n\nimport android.content.res.ColorStateList\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.core.widget.TextViewCompat\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.combine\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.KotatsuColors\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.setProgressIcon\nimport org.koitharu.kotatsu.databinding.DialogLocalInfoBinding\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\nclass LocalInfoDialog : AlertDialogFragment<DialogLocalInfoBinding>(), View.OnClickListener {\n\n\tprivate val viewModel: LocalInfoViewModel by viewModels()\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder).setTitle(R.string.saved_manga).setNegativeButton(R.string.close, null)\n\t}\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogLocalInfoBinding {\n\t\treturn DialogLocalInfoBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: DialogLocalInfoBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tviewModel.path.observe(this) {\n\t\t\tbinding.textViewPath.text = it\n\t\t}\n\t\tbinding.chipCleanup.setOnClickListener(this)\n\t\tcombine(viewModel.size, viewModel.availableSize, ::Pair).observe(viewLifecycleOwner) {\n\t\t\tif (it.first >= 0 && it.second >= 0) {\n\t\t\t\tsetSegments(it.first, it.second)\n\t\t\t} else {\n\t\t\t\tbinding.barView.animateSegments(emptyList())\n\t\t\t}\n\t\t}\n\t\tviewModel.onCleanedUp.observeEvent(viewLifecycleOwner, ::onCleanedUp)\n\t\tviewModel.isCleaningUp.observe(viewLifecycleOwner) { loading ->\n\t\t\tbinding.chipCleanup.isClickable = !loading\n\t\t\tdialog?.setCancelable(!loading)\n\t\t\tif (loading) {\n\t\t\t\tbinding.chipCleanup.setProgressIcon()\n\t\t\t} else {\n\t\t\t\tbinding.chipCleanup.setChipIconResource(R.drawable.ic_delete)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.chip_cleanup -> viewModel.cleanup()\n\t\t}\n\t}\n\n\tprivate fun onCleanedUp(result: Pair<Int, Long>) {\n\t\tval c = context ?: return\n\t\tval text = if (result.first == 0 && result.second == 0L) {\n\t\t\tc.getString(R.string.no_chapters_deleted)\n\t\t} else {\n\t\t\tc.getString(\n\t\t\t\tR.string.chapters_deleted_pattern,\n\t\t\t\tc.resources.getQuantityStringSafe(R.plurals.chapters, result.first, result.first),\n\t\t\t\tFileSize.BYTES.format(c, result.second),\n\t\t\t)\n\t\t}\n\t\tToast.makeText(c, text, Toast.LENGTH_SHORT).show()\n\t}\n\n\tprivate fun setSegments(size: Long, available: Long) {\n\t\tval view = viewBinding?.barView ?: return\n\t\tval total = size + available\n\t\tval segment = SegmentedBarView.Segment(\n\t\t\tpercent = (size.toDouble() / total.toDouble()).toFloat(),\n\t\t\tcolor = KotatsuColors.segmentColor(view.context, appcompatR.attr.colorPrimary),\n\t\t)\n\t\trequireViewBinding().labelUsed.text = view.context.getString(\n\t\t\tR.string.memory_usage_pattern,\n\t\t\tgetString(R.string.this_manga),\n\t\t\tFileSize.BYTES.format(view.context, size),\n\t\t)\n\t\trequireViewBinding().labelAvailable.text = view.context.getString(\n\t\t\tR.string.memory_usage_pattern,\n\t\t\tgetString(R.string.available),\n\t\t\tFileSize.BYTES.format(view.context, available),\n\t\t)\n\t\tTextViewCompat.setCompoundDrawableTintList(\n\t\t\trequireViewBinding().labelUsed,\n\t\t\tColorStateList.valueOf(segment.color),\n\t\t)\n\t\tview.animateSegments(listOf(segment))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/local/ui/info/LocalInfoViewModel.kt",
    "content": "package org.koitharu.kotatsu.local.ui.info\n\nimport androidx.core.net.toUri\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.computeSize\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.core.util.ext.toFileOrNull\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport org.koitharu.kotatsu.local.domain.DeleteReadChaptersUseCase\nimport javax.inject.Inject\n\n@HiltViewModel\nclass LocalInfoViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val localMangaRepository: LocalMangaRepository,\n\tprivate val storageManager: LocalStorageManager,\n\tprivate val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,\n) : BaseViewModel() {\n\n\tprivate val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga\n\n\tval isCleaningUp = MutableStateFlow(false)\n\tval onCleanedUp = MutableEventFlow<Pair<Int, Long>>()\n\n\tval path = MutableStateFlow<String?>(null)\n\tval size = MutableStateFlow(-1L)\n\tval availableSize = MutableStateFlow(-1L)\n\n\tinit {\n\t\tcomputeSize()\n\t}\n\n\tfun cleanup() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\ttry {\n\t\t\t\tisCleaningUp.value = true\n\t\t\t\tval oldSize = size.value\n\t\t\t\tval chaptersCount = deleteReadChaptersUseCase.invoke(manga)\n\t\t\t\tcomputeSize().join()\n\t\t\t\tval newSize = size.value\n\t\t\t\tonCleanedUp.call(chaptersCount to oldSize - newSize)\n\t\t\t} finally {\n\t\t\t\tisCleaningUp.value = false\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun computeSize() = launchLoadingJob(Dispatchers.Default) {\n\t\tval file = manga.url.toUri().toFileOrNull() ?: localMangaRepository.findSavedManga(manga)?.file\n\t\trequireNotNull(file)\n\t\tpath.value = file.path\n\t\tsize.value = file.computeSize()\n\t\tavailableSize.value = storageManager.computeAvailableSize()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/domain/CoverRestoreInterceptor.kt",
    "content": "package org.koitharu.kotatsu.main.domain\n\nimport androidx.collection.ArraySet\nimport coil3.intercept.Interceptor\nimport coil3.request.ErrorResult\nimport coil3.request.ImageResult\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.ext.bookmarkKey\nimport org.koitharu.kotatsu.core.util.ext.mangaKey\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport java.util.Collections\nimport javax.inject.Inject\n\nclass CoverRestoreInterceptor @Inject constructor(\n\tprivate val dataRepository: MangaDataRepository,\n\tprivate val bookmarksRepository: BookmarksRepository,\n\tprivate val repositoryFactory: MangaRepository.Factory,\n) : Interceptor {\n\n\tprivate val blacklist = Collections.synchronizedSet(ArraySet<String>())\n\n\toverride suspend fun intercept(chain: Interceptor.Chain): ImageResult {\n\t\tval request = chain.request\n\t\tval result = chain.proceed()\n\t\tif (result is ErrorResult && result.throwable.shouldRestore()) {\n\t\t\trequest.extras[bookmarkKey]?.let {\n\t\t\t\treturn if (restoreBookmark(it)) {\n\t\t\t\t\tchain.withRequest(request.newBuilder().build()).proceed()\n\t\t\t\t} else {\n\t\t\t\t\tresult\n\t\t\t\t}\n\t\t\t}\n\t\t\trequest.extras[mangaKey]?.let {\n\t\t\t\treturn if (restoreManga(it)) {\n\t\t\t\t\tchain.withRequest(request.newBuilder().build()).proceed()\n\t\t\t\t} else {\n\t\t\t\t\tresult\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate suspend fun restoreManga(manga: Manga): Boolean {\n\t\tval key = manga.publicUrl\n\t\tif (!blacklist.add(key)) {\n\t\t\treturn false\n\t\t}\n\t\tval restored = runCatchingCancellable {\n\t\t\trestoreMangaImpl(manga)\n\t\t}.onFailure { e ->\n\t\t\te.printStackTraceDebug()\n\t\t}.getOrDefault(false)\n\t\tif (restored) {\n\t\t\tblacklist.remove(key)\n\t\t}\n\t\treturn restored\n\t}\n\n\tprivate suspend fun restoreMangaImpl(manga: Manga): Boolean {\n\t\tif (dataRepository.findMangaById(manga.id, withChapters = false) == null || manga.isLocal) {\n\t\t\treturn false\n\t\t}\n\t\tval repo = repositoryFactory.create(manga.source)\n\t\tval fixed = repo.find(manga) ?: return false\n\t\treturn if (fixed != manga) {\n\t\t\tdataRepository.storeManga(fixed, replaceExisting = true)\n\t\t\tfixed.coverUrl != manga.coverUrl\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tprivate suspend fun restoreBookmark(bookmark: Bookmark): Boolean {\n\t\tval key = bookmark.imageUrl\n\t\tif (!blacklist.add(key)) {\n\t\t\treturn false\n\t\t}\n\t\tval restored = runCatchingCancellable {\n\t\t\trestoreBookmarkImpl(bookmark)\n\t\t}.onFailure { e ->\n\t\t\te.printStackTraceDebug()\n\t\t}.getOrDefault(false)\n\t\tif (restored) {\n\t\t\tblacklist.remove(key)\n\t\t}\n\t\treturn restored\n\t}\n\n\tprivate suspend fun restoreBookmarkImpl(bookmark: Bookmark): Boolean {\n\t\tif (bookmark.manga.isLocal) {\n\t\t\treturn false\n\t\t}\n\t\tval repo = repositoryFactory.create(bookmark.manga.source)\n\t\tval chapter = repo.getDetails(bookmark.manga).chapters?.findById(bookmark.chapterId) ?: return false\n\t\tval page = repo.getPages(chapter)[bookmark.page]\n\t\tval imageUrl = page.preview.ifNullOrEmpty { page.url }\n\t\treturn if (imageUrl != bookmark.imageUrl) {\n\t\t\tbookmarksRepository.updateBookmark(bookmark, imageUrl)\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tprivate fun Throwable.shouldRestore(): Boolean {\n\t\treturn this is Exception // any Exception but not Error\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/domain/ReadingResumeEnabledUseCase.kt",
    "content": "package org.koitharu.kotatsu.main.domain\n\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport javax.inject.Inject\n\nclass ReadingResumeEnabledUseCase @Inject constructor(\n\tprivate val networkState: NetworkState,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val settings: AppSettings,\n) {\n\n\toperator fun invoke(): Flow<Boolean> = settings.observe(\n\t\tAppSettings.KEY_MAIN_FAB,\n\t\tAppSettings.KEY_INCOGNITO_MODE,\n\t).map {\n\t\tsettings.isMainFabEnabled && !settings.isIncognitoModeEnabled\n\t}.distinctUntilChanged()\n\t\t.flatMapLatest { isFabEnabled ->\n\t\t\tif (isFabEnabled) {\n\t\t\t\tobserveCanResume()\n\t\t\t} else {\n\t\t\t\tflowOf(false)\n\t\t\t}\n\t\t}\n\n\tprivate fun observeCanResume() = combine(networkState, historyRepository.observeLast()) { isOnline, last ->\n\t\tlast != null && (isOnline || last.isLocal)\n\t}.distinctUntilChanged()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/ExitCallback.kt",
    "content": "package org.koitharu.kotatsu.main.ui\n\nimport android.view.View\nimport androidx.activity.OnBackPressedCallback\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.search.SearchView\nimport com.google.android.material.snackbar.Snackbar\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.main.ui.owners.BottomNavOwner\n\nclass ExitCallback(\n\tprivate val activity: MainActivity,\n\tprivate val snackbarHost: View,\n) : OnBackPressedCallback(false), SearchView.TransitionListener {\n\n\tprivate var job: Job? = null\n\tprivate val isSearchOpen = MutableStateFlow(activity.viewBinding.searchView.isShowing)\n\tprivate val isDisabledByTimeout = MutableStateFlow(false)\n\n\tinit {\n\t\tactivity.lifecycleScope.launch {\n\t\t\tcombine(\n\t\t\t\tobserveSettings(),\n\t\t\t\tisSearchOpen,\n\t\t\t\tisDisabledByTimeout,\n\t\t\t) { enabledInSettings, searchOpen, disabledTemporary ->\n\t\t\t\tenabledInSettings && !searchOpen && !disabledTemporary\n\t\t\t}.collect {\n\t\t\t\tisEnabled = it\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun handleOnBackPressed() {\n\t\tjob?.cancel()\n\t\tjob = activity.lifecycleScope.launch {\n\t\t\tresetExitConfirmation()\n\t\t}\n\t}\n\n\toverride fun onStateChanged(\n\t\tsearchView: SearchView,\n\t\tpreviousState: SearchView.TransitionState,\n\t\tnewState: SearchView.TransitionState\n\t) {\n\t\tisSearchOpen.value = newState >= SearchView.TransitionState.SHOWING\n\t}\n\n\tprivate suspend fun resetExitConfirmation() {\n\t\tisDisabledByTimeout.value = true\n\t\tval snackbar = Snackbar.make(snackbarHost, R.string.confirm_exit, Snackbar.LENGTH_INDEFINITE)\n\t\tsnackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav\n\t\tsnackbar.show()\n\t\tdelay(2000)\n\t\tsnackbar.dismiss()\n\t\tisDisabledByTimeout.value = false\n\t}\n\n\tprivate fun observeSettings(): Flow<Boolean> = activity.settings\n\t\t.observeAsFlow(AppSettings.KEY_EXIT_CONFIRM) { isExitConfirmationEnabled }\n\t\t.flowOn(Dispatchers.Default)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActionButtonBehavior.kt",
    "content": "package org.koitharu.kotatsu.main.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.ViewCompat\nimport com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton\nimport org.koitharu.kotatsu.core.ui.util.ShrinkOnScrollBehavior\nimport org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView\n\nclass MainActionButtonBehavior : ShrinkOnScrollBehavior {\n\n\tconstructor() : super()\n\tconstructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)\n\n\toverride fun layoutDependsOn(\n\t\tparent: CoordinatorLayout,\n\t\tchild: ExtendedFloatingActionButton,\n\t\tdependency: View\n\t): Boolean {\n\t\treturn dependency is SlidingBottomNavigationView || super.layoutDependsOn(parent, child, dependency)\n\t}\n\n\toverride fun onDependentViewChanged(\n\t\tparent: CoordinatorLayout,\n\t\tchild: ExtendedFloatingActionButton,\n\t\tdependency: View\n\t): Boolean {\n\t\tval bottom = child.bottom\n\t\tval bottomLine = parent.height\n\t\treturn if (bottom > bottomLine) {\n\t\t\tViewCompat.offsetTopAndBottom(child, bottomLine - bottom)\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt",
    "content": "package org.koitharu.kotatsu.main.ui\n\nimport android.Manifest\nimport android.app.BackgroundServiceStartNotAllowedException\nimport android.app.ServiceStartNotAllowedException\nimport android.content.Intent\nimport android.content.pm.PackageManager.PERMISSION_GRANTED\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport androidx.activity.viewModels\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.children\nimport androidx.core.view.inputmethod.EditorInfoCompat\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.withResumed\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport com.google.android.material.appbar.AppBarLayout\nimport com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS\nimport com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL\nimport com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL\nimport com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP\nimport com.google.android.material.search.SearchView\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.channels.trySendBlocking\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.emptyFlow\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupService\nimport org.koitharu.kotatsu.browser.AdListUpdateService\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.os.VoiceInputContract\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.NavItem\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.util.FadingAppbarMediator\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.databinding.ActivityMainBinding\nimport org.koitharu.kotatsu.details.service.MangaPrefetchService\nimport org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment\nimport org.koitharu.kotatsu.history.ui.HistoryListFragment\nimport org.koitharu.kotatsu.local.ui.LocalIndexUpdateService\nimport org.koitharu.kotatsu.local.ui.LocalStorageCleanupWorker\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.main.ui.owners.BottomNavOwner\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.remotelist.ui.MangaSearchMenuProvider\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionItemCallback\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListenerImpl\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionMenuProvider\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel\nimport org.koitharu.kotatsu.search.ui.suggestion.adapter.SearchSuggestionAdapter\nimport javax.inject.Inject\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nclass MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner,\n\tView.OnClickListener,\n\tSearchSuggestionItemCallback.SuggestionItemListener,\n\tMainNavigationDelegate.OnFragmentChangedListener,\n\tView.OnLayoutChangeListener,\n\tSearchView.TransitionListener {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate val viewModel by viewModels<MainViewModel>()\n\tprivate val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()\n\tprivate val voiceInputLauncher = registerForActivityResult(VoiceInputContract()) { result ->\n\t\tif (result != null) {\n\t\t\tviewBinding.searchView.setText(result)\n\t\t}\n\t}\n\tprivate lateinit var navigationDelegate: MainNavigationDelegate\n\tprivate lateinit var fadingAppbarMediator: FadingAppbarMediator\n\n\toverride val appBar: AppBarLayout\n\t\tget() = viewBinding.appbar\n\n\toverride val bottomNav: SlidingBottomNavigationView?\n\t\tget() = viewBinding.bottomNav\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityMainBinding.inflate(layoutInflater))\n\t\tsetSupportActionBar(viewBinding.searchBar)\n\n\t\tviewBinding.fab?.setOnClickListener(this)\n\t\tviewBinding.navRail?.headerView?.findViewById<View>(R.id.railFab)?.setOnClickListener(this)\n\t\tfadingAppbarMediator =\n\t\t\tFadingAppbarMediator(viewBinding.appbar, viewBinding.layoutSearch ?: viewBinding.searchBar)\n\n\t\tnavigationDelegate = MainNavigationDelegate(\n\t\t\tnavBar = checkNotNull(bottomNav ?: viewBinding.navRail),\n\t\t\tfragmentManager = supportFragmentManager,\n\t\t\tsettings = settings,\n\t\t)\n\t\tnavigationDelegate.addOnFragmentChangedListener(this)\n\t\tnavigationDelegate.onCreate(this, savedInstanceState)\n\t\tviewBinding.textViewTitle?.let { tv ->\n\t\t\tnavigationDelegate.observeTitle().observe(this) { tv.text = it }\n\t\t}\n\n\t\taddMenuProvider(MainMenuProvider(router, viewModel))\n\n\t\tval exitCallback = ExitCallback(this, viewBinding.container)\n\t\tonBackPressedDispatcher.addCallback(exitCallback)\n\t\tonBackPressedDispatcher.addCallback(navigationDelegate)\n\n\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || !resources.getBoolean(R.bool.is_predictive_back_enabled)) {\n\t\t\tval legacySearchCallback = SearchViewLegacyBackCallback(viewBinding.searchView)\n\t\t\tviewBinding.searchView.addTransitionListener(legacySearchCallback)\n\t\t\tonBackPressedDispatcher.addCallback(legacySearchCallback)\n\t\t}\n\n\t\tif (savedInstanceState == null) {\n\t\t\tonFirstStart()\n\t\t}\n\n\t\tviewModel.onOpenReader.observeEvent(this, this::onOpenReader)\n\t\tviewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.container, null))\n\t\tviewModel.isLoading.observe(this, this::onLoadingStateChanged)\n\t\tviewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)\n\t\tviewModel.feedCounter.observe(this, ::onFeedCounterChanged)\n\t\tviewModel.appUpdate.observe(this, MenuInvalidator(this))\n\t\tviewModel.onFirstStart.observeEvent(this) { router.showWelcomeSheet() }\n\t\tviewModel.isBottomNavPinned.observe(this, ::setNavbarPinned)\n\t\tsearchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)\n\t\tviewBinding.bottomNav?.addOnLayoutChangeListener(this)\n\t\tviewBinding.searchView.addTransitionListener(this)\n\t\tviewBinding.searchView.addTransitionListener(exitCallback)\n\t\tinitSearch()\n\t}\n\n\toverride fun onRestoreInstanceState(savedInstanceState: Bundle) {\n\t\tsuper.onRestoreInstanceState(savedInstanceState)\n\t\tadjustSearchUI(viewBinding.searchView.isShowing)\n\t\tnavigationDelegate.syncSelectedItem()\n\t}\n\n\toverride fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {\n\t\tadjustFabVisibility(topFragment = fragment)\n\t\tadjustAppbar(topFragment = fragment)\n\t\tif (fromUser) {\n\t\t\tactionModeDelegate.finishActionMode()\n\t\t\tviewBinding.appbar.setExpanded(true)\n\t\t}\n\t}\n\n\toverride fun addMenuProvider(provider: MenuProvider, owner: LifecycleOwner, state: Lifecycle.State) {\n\t\tif (provider !is MangaSearchMenuProvider) { // do not duplicate search menu item\n\t\t\tsuper.addMenuProvider(provider, owner, state)\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.fab, R.id.railFab -> viewModel.openLastReader()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tval searchBarDefaultMargin = resources.getDimensionPixelOffset(materialR.dimen.m3_searchbar_margin_horizontal)\n\t\tviewBinding.searchBar.updateLayoutParams<MarginLayoutParams> {\n\t\t\tmarginEnd = searchBarDefaultMargin + barsInsets.end(v)\n\t\t\tmarginStart = if (viewBinding.navRail != null) {\n\t\t\t\tsearchBarDefaultMargin\n\t\t\t} else {\n\t\t\t\tsearchBarDefaultMargin + barsInsets.start(v)\n\t\t\t}\n\t\t}\n\t\tviewBinding.bottomNav?.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\tviewBinding.navRail?.updateLayoutParams<MarginLayoutParams> {\n\t\t\tmarginStart = barsInsets.start(v)\n\t\t\ttopMargin = barsInsets.top\n\t\t\tbottomMargin = barsInsets.bottom\n\t\t}\n\t\tupdateContainerBottomMargin()\n\t\treturn insets.consume(v, typeMask, start = viewBinding.navRail != null).also {\n\t\t\thandleSearchSuggestionsInsets(it)\n\t\t}\n\t}\n\n\toverride fun onLayoutChange(\n\t\tv: View?,\n\t\tleft: Int,\n\t\ttop: Int,\n\t\tright: Int,\n\t\tbottom: Int,\n\t\toldLeft: Int,\n\t\toldTop: Int,\n\t\toldRight: Int,\n\t\toldBottom: Int\n\t) {\n\t\tif (top != oldTop || bottom != oldBottom) {\n\t\t\tupdateContainerBottomMargin()\n\t\t}\n\t}\n\n\toverride fun onStateChanged(\n\t\tsearchView: SearchView,\n\t\tpreviousState: SearchView.TransitionState,\n\t\tnewState: SearchView.TransitionState,\n\t) {\n\t\tval wasOpened = previousState >= SearchView.TransitionState.SHOWING\n\t\tval isOpened = newState >= SearchView.TransitionState.SHOWING\n\t\tif (isOpened != wasOpened) {\n\t\t\tadjustSearchUI(isOpened)\n\t\t}\n\t}\n\n\toverride fun onRemoveQuery(query: String) {\n\t\tsearchSuggestionViewModel.deleteQuery(query)\n\t}\n\n\toverride fun onSupportActionModeStarted(mode: ActionMode) {\n\t\tsuper.onSupportActionModeStarted(mode)\n\t\tadjustFabVisibility()\n\t\tbottomNav?.hide()\n\t\t(viewBinding.layoutSearch ?: viewBinding.searchBar).isInvisible = true\n\t\tupdateContainerBottomMargin()\n\t}\n\n\toverride fun onSupportActionModeFinished(mode: ActionMode) {\n\t\tsuper.onSupportActionModeFinished(mode)\n\t\tadjustFabVisibility()\n\t\tbottomNav?.show()\n\t\t(viewBinding.layoutSearch ?: viewBinding.searchBar).isInvisible = false\n\t\tupdateContainerBottomMargin()\n\t}\n\n\tprivate fun onOpenReader(manga: Manga) {\n\t\tval fab = viewBinding.fab ?: viewBinding.navRail?.headerView\n\t\trouter.openReader(manga, fab)\n\t}\n\n\tprivate fun onFeedCounterChanged(counter: Int) {\n\t\tnavigationDelegate.setCounter(NavItem.FEED, counter)\n\t}\n\n\tprivate fun onIncognitoModeChanged(isIncognito: Boolean) {\n\t\tvar options = viewBinding.searchView.getEditText().imeOptions\n\t\toptions = if (isIncognito) {\n\t\t\toptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING\n\t\t} else {\n\t\t\toptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()\n\t\t}\n\t\tviewBinding.searchView.getEditText().imeOptions = options\n\t\tinvalidateOptionsMenu()\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tval fab = viewBinding.fab ?: viewBinding.navRail?.headerView ?: return\n\t\tfab.isEnabled = !isLoading\n\t}\n\n\tprivate fun onResumeEnabledChanged(isEnabled: Boolean) {\n\t\tadjustFabVisibility(isResumeEnabled = isEnabled)\n\t}\n\n\tprivate fun onFirstStart() = try {\n\t\tlifecycleScope.launch(Dispatchers.Main) { // not a default `Main.immediate` dispatcher\n\t\t\twithContext(Dispatchers.Default) {\n\t\t\t\tLocalStorageCleanupWorker.enqueue(applicationContext)\n\t\t\t}\n\t\t\twithResumed {\n\t\t\t\tMangaPrefetchService.prefetchLast(this@MainActivity)\n\t\t\t\trequestNotificationsPermission()\n\t\t\t\tstartService(Intent(this@MainActivity, LocalIndexUpdateService::class.java))\n\t\t\t\tstartService(Intent(this@MainActivity, PeriodicalBackupService::class.java))\n\t\t\t\tif (settings.isAdBlockEnabled) {\n\t\t\t\t\tstartService(Intent(this@MainActivity, AdListUpdateService::class.java))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (e: IllegalStateException) {\n\t\te.printStackTraceDebug()\n\t}\n\n\tprivate fun adjustAppbar(topFragment: Fragment) {\n\t\tif (topFragment is FavouritesContainerFragment) {\n\t\t\tviewBinding.appbar.fitsSystemWindows = true\n\t\t\tfadingAppbarMediator.bind()\n\t\t} else {\n\t\t\tviewBinding.appbar.fitsSystemWindows = false\n\t\t\tfadingAppbarMediator.unbind()\n\t\t}\n\t}\n\n\tprivate fun adjustFabVisibility(\n\t\tisResumeEnabled: Boolean = viewModel.isResumeEnabled.value,\n\t\ttopFragment: Fragment? = navigationDelegate.primaryFragment,\n\t\tisSearchOpened: Boolean = viewBinding.searchView.isShowing,\n\t) {\n\t\tnavigationDelegate.navRailHeader?.railFab?.isVisible = isResumeEnabled\n\t\tval fab = viewBinding.fab ?: return\n\t\tif (isResumeEnabled && !actionModeDelegate.isActionModeStarted && !isSearchOpened && topFragment is HistoryListFragment) {\n\t\t\tif (!fab.isVisible) {\n\t\t\t\tfab.show()\n\t\t\t}\n\t\t} else {\n\t\t\tif (fab.isVisible) {\n\t\t\t\tfab.hide()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun adjustSearchUI(isOpened: Boolean) {\n\t\tval appBarScrollFlags = if (isOpened) {\n\t\t\tSCROLL_FLAG_NO_SCROLL\n\t\t} else {\n\t\t\tSCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP\n\t\t}\n\t\tviewBinding.insetsHolder.updateLayoutParams<AppBarLayout.LayoutParams> {\n\t\t\tscrollFlags = appBarScrollFlags\n\t\t}\n\t\tadjustFabVisibility(isSearchOpened = isOpened)\n\t\tbottomNav?.showOrHide(!isOpened)\n\t\tupdateContainerBottomMargin()\n\t}\n\n\tprivate fun requestNotificationsPermission() {\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(\n\t\t\t\tthis,\n\t\t\t\tManifest.permission.POST_NOTIFICATIONS,\n\t\t\t) != PERMISSION_GRANTED\n\t\t) {\n\t\t\tActivityCompat.requestPermissions(\n\t\t\t\tthis,\n\t\t\t\tarrayOf(Manifest.permission.POST_NOTIFICATIONS),\n\t\t\t\t1,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun handleSearchSuggestionsInsets(insets: WindowInsetsCompat) {\n\t\tval typeMask = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tviewBinding.recyclerViewSearch.setPadding(barsInsets.left, 0, barsInsets.right, barsInsets.bottom)\n\t}\n\n\tprivate fun initSearch() {\n\t\tval listener = SearchSuggestionListenerImpl(router, viewBinding.searchView, searchSuggestionViewModel)\n\t\tval adapter = SearchSuggestionAdapter(listener)\n\t\tviewBinding.searchView.toolbar.addMenuProvider(\n\t\t\tSearchSuggestionMenuProvider(this, voiceInputLauncher, searchSuggestionViewModel),\n\t\t)\n\t\tviewBinding.searchView.editText.addTextChangedListener(listener)\n\t\tviewBinding.recyclerViewSearch.adapter = adapter\n\t\tviewBinding.searchView.editText.setOnEditorActionListener(listener)\n\n\t\tviewBinding.searchView.observeState()\n\t\t\t.map { it >= SearchView.TransitionState.SHOWING }\n\t\t\t.distinctUntilChanged()\n\t\t\t.flatMapLatest { isShowing ->\n\t\t\t\tif (isShowing) {\n\t\t\t\t\tsearchSuggestionViewModel.suggestion\n\t\t\t\t} else {\n\t\t\t\t\temptyFlow()\n\t\t\t\t}\n\t\t\t}.observe(this, adapter)\n\t\tsearchSuggestionViewModel.onError.observeEvent(\n\t\t\tthis,\n\t\t\tSnackbarErrorObserver(viewBinding.recyclerViewSearch, null),\n\t\t)\n\t\tItemTouchHelper(SearchSuggestionItemCallback(this))\n\t\t\t.attachToRecyclerView(viewBinding.recyclerViewSearch)\n\t}\n\n\tprivate fun setNavbarPinned(isPinned: Boolean) {\n\t\tval bottomNavBar = viewBinding.bottomNav\n\t\tbottomNavBar?.isPinned = isPinned\n\t\tfor (view in viewBinding.appbar.children) {\n\t\t\tval lp = view.layoutParams as? AppBarLayout.LayoutParams ?: continue\n\t\t\tval scrollFlags = if (isPinned) {\n\t\t\t\tlp.scrollFlags and SCROLL_FLAG_SCROLL.inv()\n\t\t\t} else {\n\t\t\t\tlp.scrollFlags or SCROLL_FLAG_SCROLL\n\t\t\t}\n\t\t\tif (scrollFlags != lp.scrollFlags) {\n\t\t\t\tlp.scrollFlags = scrollFlags\n\t\t\t\tview.layoutParams = lp\n\t\t\t}\n\t\t}\n\t\tupdateContainerBottomMargin()\n\t}\n\n\tprivate fun updateContainerBottomMargin() {\n\t\tval bottomNavBar = viewBinding.bottomNav ?: return\n\t\tval newMargin = if (bottomNavBar.isPinned && bottomNavBar.isShownOrShowing) bottomNavBar.height else 0\n\t\twith(viewBinding.container) {\n\t\t\tval params = layoutParams as MarginLayoutParams\n\t\t\tif (params.bottomMargin != newMargin) {\n\t\t\t\tparams.bottomMargin = newMargin\n\t\t\t\tlayoutParams = params\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun SearchView.observeState() = callbackFlow {\n\t\tval listener = SearchView.TransitionListener { _, _, state ->\n\t\t\ttrySendBlocking(state)\n\t\t}\n\t\taddTransitionListener(listener)\n\t\tawaitClose { removeTransitionListener(listener) }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.main.ui\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\n\nclass MainMenuProvider(\n\tprivate val router: AppRouter,\n\tprivate val viewModel: MainViewModel,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_main, menu)\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tmenu.findItem(R.id.action_incognito)?.isChecked =\n\t\t\tviewModel.isIncognitoModeEnabled.value\n\t\tval hasAppUpdate = viewModel.appUpdate.value != null\n\t\tmenu.findItem(R.id.action_app_update)?.isVisible = hasAppUpdate\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n\t\tR.id.action_settings -> {\n\t\t\trouter.openSettings()\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_incognito -> {\n\t\t\tviewModel.setIncognitoMode(!menuItem.isChecked)\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_app_update -> {\n\t\t\trouter.openAppUpdate()\n\t\t\ttrue\n\t\t}\n\n\t\telse -> false\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt",
    "content": "package org.koitharu.kotatsu.main.ui\n\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.FrameLayout\nimport androidx.activity.OnBackPressedCallback\nimport androidx.annotation.IdRes\nimport androidx.core.view.isEmpty\nimport androidx.core.view.isVisible\nimport androidx.core.view.iterator\nimport androidx.core.view.size\nimport androidx.core.view.updateLayoutParams\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.navigation.NavigationBarView\nimport com.google.android.material.navigationrail.NavigationRailView\nimport com.google.android.material.transition.MaterialFadeThrough\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.channels.trySendBlocking\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.ui.AllBookmarksFragment\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.NavItem\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView\nimport org.koitharu.kotatsu.core.util.ext.buildBundle\nimport org.koitharu.kotatsu.core.util.ext.setContentDescriptionAndTooltip\nimport org.koitharu.kotatsu.core.util.ext.smoothScrollToTop\nimport org.koitharu.kotatsu.databinding.NavigationRailFabBinding\nimport org.koitharu.kotatsu.explore.ui.ExploreFragment\nimport org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment\nimport org.koitharu.kotatsu.history.ui.HistoryListFragment\nimport org.koitharu.kotatsu.local.ui.LocalListFragment\nimport org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment\nimport org.koitharu.kotatsu.tracker.ui.feed.FeedFragment\nimport org.koitharu.kotatsu.tracker.ui.updates.UpdatesFragment\nimport java.util.LinkedList\nimport com.google.android.material.R as materialR\n\nprivate const val TAG_PRIMARY = \"primary\"\n\nclass MainNavigationDelegate(\n\tprivate val navBar: NavigationBarView,\n\tprivate val fragmentManager: FragmentManager,\n\tprivate val settings: AppSettings,\n) : OnBackPressedCallback(false),\n\tNavigationBarView.OnItemSelectedListener,\n\tNavigationBarView.OnItemReselectedListener, View.OnClickListener {\n\n\tprivate val listeners = LinkedList<OnFragmentChangedListener>()\n\tval navRailHeader = (navBar as? NavigationRailView)?.headerView?.let {\n\t\tNavigationRailFabBinding.bind(it)\n\t}\n\n\tval primaryFragment: Fragment?\n\t\tget() = fragmentManager.findFragmentByTag(TAG_PRIMARY)\n\n\tinit {\n\t\tnavBar.setOnItemSelectedListener(this)\n\t\tnavBar.setOnItemReselectedListener(this)\n\t\tnavRailHeader?.run {\n\t\t\troot.updateLayoutParams<FrameLayout.LayoutParams> {\n\t\t\t\tgravity = Gravity.TOP or Gravity.CENTER\n\t\t\t}\n\t\t\tval horizontalPadding = (navBar as NavigationRailView).itemActiveIndicatorMarginHorizontal\n\t\t\troot.setPadding(horizontalPadding, 0, horizontalPadding, 0)\n\t\t\tbuttonExpand.setOnClickListener(this@MainNavigationDelegate)\n\t\t\tbuttonExpand.setContentDescriptionAndTooltip(R.string.expand)\n\t\t\trailFab.isExtended = false\n\t\t\trailFab.isAnimationEnabled = false\n\t\t}\n\t}\n\n\toverride fun onNavigationItemSelected(item: MenuItem): Boolean {\n\t\treturn if (onNavigationItemSelected(item.itemId)) {\n\t\t\titem.isChecked = true\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\toverride fun onNavigationItemReselected(item: MenuItem) {\n\t\tonNavigationItemReselected()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_expand -> {\n\t\t\t\tif (navBar is NavigationRailView) {\n\t\t\t\t\tsetNavbarIsExpanded(!navBar.isExpanded)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun handleOnBackPressed() {\n\t\tnavBar.selectedItemId = firstItem()?.itemId ?: return\n\t}\n\n\tfun onCreate(lifecycleOwner: LifecycleOwner, savedInstanceState: Bundle?) {\n\t\tif (navBar.menu.isEmpty()) {\n\t\t\tcreateMenu(settings.mainNavItems, navBar.menu)\n\t\t}\n\t\tobserveSettings(lifecycleOwner)\n\t\tval fragment = primaryFragment\n\t\tif (fragment != null) {\n\t\t\tonFragmentChanged(fragment, fromUser = false)\n\t\t\tval itemId = getItemId(fragment)\n\t\t\tif (navBar.selectedItemId != itemId) {\n\t\t\t\tnavBar.selectedItemId = itemId\n\t\t\t}\n\t\t} else {\n\t\t\tval itemId = if (savedInstanceState == null) {\n\t\t\t\tfirstItem()?.itemId ?: navBar.selectedItemId\n\t\t\t} else {\n\t\t\t\tnavBar.selectedItemId\n\t\t\t}\n\t\t\tonNavigationItemSelected(itemId)\n\t\t}\n\t}\n\n\tfun observeTitle() = callbackFlow {\n\t\tval listener = OnFragmentChangedListener { f, _ ->\n\t\t\ttrySendBlocking(getItemId(f))\n\t\t}\n\t\taddOnFragmentChangedListener(listener)\n\t\tawaitClose { removeOnFragmentChangedListener(listener) }\n\t}.map {\n\t\tnavBar.menu.findItem(it)?.title\n\t}\n\n\tfun setCounter(item: NavItem, counter: Int) {\n\t\tsetCounter(item.id, counter)\n\t}\n\n\tfun syncSelectedItem() {\n\t\tval fragment = primaryFragment ?: return\n\t\tonFragmentChanged(fragment, fromUser = false)\n\t\tval itemId = getItemId(fragment)\n\t\tif (navBar.selectedItemId != itemId) {\n\t\t\tnavBar.selectedItemId = itemId\n\t\t}\n\t}\n\n\tprivate fun setCounter(@IdRes id: Int, counter: Int) {\n\t\tif (counter == 0) {\n\t\t\tnavBar.getBadge(id)?.isVisible = false\n\t\t} else {\n\t\t\tval badge = navBar.getOrCreateBadge(id)\n\t\t\tif (counter < 0) {\n\t\t\t\tbadge.clearNumber()\n\t\t\t} else {\n\t\t\t\tbadge.number = counter\n\t\t\t}\n\t\t\tbadge.isVisible = true\n\t\t}\n\t}\n\n\tfun setItemVisibility(@IdRes itemId: Int, isVisible: Boolean) {\n\t\tval item = navBar.menu.findItem(itemId) ?: return\n\t\titem.isVisible = isVisible\n\t\tif (item.isChecked && !isVisible) {\n\t\t\tnavBar.selectedItemId = firstItem()?.itemId ?: return\n\t\t}\n\t}\n\n\tfun addOnFragmentChangedListener(listener: OnFragmentChangedListener) {\n\t\tlisteners.add(listener)\n\t}\n\n\tfun removeOnFragmentChangedListener(listener: OnFragmentChangedListener) {\n\t\tlisteners.remove(listener)\n\t}\n\n\tprivate fun onNavigationItemSelected(@IdRes itemId: Int): Boolean {\n\t\tval newFragment = when (itemId) {\n\t\t\tR.id.nav_history -> HistoryListFragment::class.java\n\t\t\tR.id.nav_favorites -> FavouritesContainerFragment::class.java\n\t\t\tR.id.nav_explore -> ExploreFragment::class.java\n\t\t\tR.id.nav_feed -> FeedFragment::class.java\n\t\t\tR.id.nav_local -> LocalListFragment::class.java\n\t\t\tR.id.nav_suggestions -> SuggestionsFragment::class.java\n\t\t\tR.id.nav_bookmarks -> AllBookmarksFragment::class.java\n\t\t\tR.id.nav_updated -> UpdatesFragment::class.java\n\t\t\telse -> return false\n\t\t}\n\t\tif (!setPrimaryFragment(newFragment)) {\n\t\t\t// probably already selected\n\t\t\tonNavigationItemReselected()\n\t\t}\n\t\treturn true\n\t}\n\n\tprivate fun getItemId(fragment: Fragment) = when (fragment) {\n\t\tis HistoryListFragment -> R.id.nav_history\n\t\tis FavouritesContainerFragment -> R.id.nav_favorites\n\t\tis ExploreFragment -> R.id.nav_explore\n\t\tis FeedFragment -> R.id.nav_feed\n\t\tis LocalListFragment -> R.id.nav_local\n\t\tis SuggestionsFragment -> R.id.nav_suggestions\n\t\tis AllBookmarksFragment -> R.id.nav_bookmarks\n\t\tis UpdatesFragment -> R.id.nav_updated\n\t\telse -> 0\n\t}\n\n\tprivate fun setPrimaryFragment(fragmentClass: Class<out Fragment>): Boolean {\n\t\tif (fragmentManager.isStateSaved || fragmentClass.isInstance(primaryFragment)) {\n\t\t\treturn false\n\t\t}\n\t\tval fragment = instantiateFragment(fragmentClass)\n\t\tval args = buildBundle(1) {\n\t\t\tputBoolean(AppRouter.KEY_IS_BOTTOMTAB, true)\n\t\t}\n\t\tfragment.enterTransition = MaterialFadeThrough()\n\t\tfragmentManager.beginTransaction()\n\t\t\t.setReorderingAllowed(true)\n\t\t\t.replace(R.id.container, fragmentClass, args, TAG_PRIMARY)\n\t\t\t.runOnCommit { onFragmentChanged(fragment, fromUser = true) }\n\t\t\t.commit()\n\t\treturn true\n\t}\n\n\tprivate fun onNavigationItemReselected() {\n\t\tval recyclerView = (primaryFragment as? RecyclerViewOwner)?.recyclerView ?: return\n\t\trecyclerView.smoothScrollToTop()\n\t}\n\n\tprivate fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {\n\t\tisEnabled = getItemId(fragment) != firstItem()?.itemId\n\t\tlisteners.forEach { it.onFragmentChanged(fragment, fromUser) }\n\t}\n\n\tprivate fun createMenu(items: List<NavItem>, menu: Menu) {\n\t\tfor (item in items) {\n\t\t\tmenu.add(Menu.NONE, item.id, Menu.NONE, item.title)\n\t\t\t\t.setIcon(item.icon)\n\t\t\tif (menu.size >= navBar.maxItemCount) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun instantiateFragment(fragmentClass: Class<out Fragment>): Fragment {\n\t\tval classLoader = navBar.context.classLoader\n\t\treturn fragmentManager.fragmentFactory.instantiate(classLoader, fragmentClass.name)\n\t}\n\n\tprivate fun observeSettings(lifecycleOwner: LifecycleOwner) {\n\t\tsettings.observe(AppSettings.KEY_TRACKER_ENABLED, AppSettings.KEY_SUGGESTIONS, AppSettings.KEY_NAV_LABELS)\n\t\t\t.onEach {\n\t\t\t\tsetItemVisibility(R.id.nav_suggestions, settings.isSuggestionsEnabled)\n\t\t\t\tsetItemVisibility(R.id.nav_feed, settings.isTrackerEnabled)\n\t\t\t\tsetNavbarIsLabeled(settings.isNavLabelsVisible)\n\t\t\t}.launchIn(lifecycleOwner.lifecycleScope)\n\t}\n\n\tprivate fun firstItem(): MenuItem? {\n\t\tval menu = navBar.menu\n\t\tfor (item in menu) {\n\t\t\tif (item.isVisible) return item\n\t\t}\n\t\treturn null\n\t}\n\n\tprivate fun setNavbarIsLabeled(value: Boolean) {\n\t\tif (navBar is SlidingBottomNavigationView) {\n\t\t\tnavBar.minimumHeight = navBar.resources.getDimensionPixelSize(\n\t\t\t\tif (value) {\n\t\t\t\t\tmaterialR.dimen.m3_bottom_nav_min_height\n\t\t\t\t} else {\n\t\t\t\t\tR.dimen.nav_bar_height_compact\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t\tnavRailHeader?.buttonExpand?.isVisible = value\n\t\tif (!value) {\n\t\t\tsetNavbarIsExpanded(false)\n\t\t}\n\t\tnavBar.labelVisibilityMode = if (value) {\n\t\t\tNavigationBarView.LABEL_VISIBILITY_LABELED\n\t\t} else {\n\t\t\tNavigationBarView.LABEL_VISIBILITY_UNLABELED\n\t\t}\n\t}\n\n\tprivate fun setNavbarIsExpanded(value: Boolean) {\n\t\tif (navBar !is NavigationRailView) {\n\t\t\treturn\n\t\t}\n\t\tif (value) {\n\t\t\tnavBar.expand()\n\t\t\tnavRailHeader?.run {\n\t\t\t\troot.updateLayoutParams<FrameLayout.LayoutParams> {\n\t\t\t\t\tgravity = Gravity.TOP or Gravity.START\n\t\t\t\t}\n\t\t\t\trailFab.extend()\n\t\t\t\tbuttonExpand.setImageResource(R.drawable.ic_drawer_menu_open)\n\t\t\t\tbuttonExpand.setContentDescriptionAndTooltip(R.string.collapse)\n\t\t\t\tval horizontalPadding = navBar.itemActiveIndicatorExpandedMarginHorizontal\n\t\t\t\troot.setPadding(horizontalPadding, 0, horizontalPadding, 0)\n\t\t\t}\n\t\t} else {\n\t\t\tnavBar.collapse()\n\t\t\tnavRailHeader?.run {\n\t\t\t\troot.updateLayoutParams<FrameLayout.LayoutParams> {\n\t\t\t\t\tgravity = Gravity.TOP or Gravity.CENTER\n\t\t\t\t}\n\t\t\t\trailFab.shrink()\n\t\t\t\tbuttonExpand.setImageResource(R.drawable.ic_drawer_menu)\n\t\t\t\tbuttonExpand.setContentDescriptionAndTooltip(R.string.expand)\n\t\t\t\tval horizontalPadding = navBar.itemActiveIndicatorMarginHorizontal\n\t\t\t\troot.setPadding(horizontalPadding, 0, horizontalPadding, 0)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun interface OnFragmentChangedListener {\n\n\t\tfun onFragmentChanged(fragment: Fragment, fromUser: Boolean)\n\t}\n\n\tcompanion object {\n\n\t\tconst val MAX_ITEM_COUNT = 6\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt",
    "content": "package org.koitharu.kotatsu.main.ui\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.exceptions.EmptyHistoryException\nimport org.koitharu.kotatsu.core.github.AppUpdateRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.main.domain.ReadingResumeEnabledUseCase\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.tracker.domain.TrackingRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MainViewModel @Inject constructor(\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val appUpdateRepository: AppUpdateRepository,\n\ttrackingRepository: TrackingRepository,\n\tprivate val settings: AppSettings,\n\treadingResumeEnabledUseCase: ReadingResumeEnabledUseCase,\n\tprivate val sourcesRepository: MangaSourcesRepository,\n) : BaseViewModel() {\n\n\tval onOpenReader = MutableEventFlow<Manga>()\n\tval onFirstStart = MutableEventFlow<Unit>()\n\n\tval isResumeEnabled = readingResumeEnabledUseCase()\n\t\t.withErrorHandling()\n\t\t.stateIn(\n\t\t\tscope = viewModelScope + Dispatchers.Default,\n\t\t\tstarted = SharingStarted.WhileSubscribed(5000),\n\t\t\tinitialValue = false,\n\t\t)\n\n\tval appUpdate = appUpdateRepository.observeAvailableUpdate()\n\n\tval feedCounter = trackingRepository.observeUnreadUpdatesCount()\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, 0)\n\n\tval isBottomNavPinned = settings.observeAsFlow(\n\t\tAppSettings.KEY_NAV_PINNED,\n\t) {\n\t\tisNavBarPinned\n\t}.flowOn(Dispatchers.Default)\n\n\tval isIncognitoModeEnabled = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_INCOGNITO_MODE,\n\t\tvalueProducer = { isIncognitoModeEnabled },\n\t)\n\n\tinit {\n\t\tlaunchJob {\n\t\t\tappUpdateRepository.fetchUpdate()\n\t\t}\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tif (sourcesRepository.isSetupRequired()) {\n\t\t\t\tonFirstStart.call(Unit)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun openLastReader() {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval manga = historyRepository.getLastOrNull() ?: throw EmptyHistoryException()\n\t\t\tonOpenReader.call(manga)\n\t\t}\n\t}\n\n\tfun setIncognitoMode(isEnabled: Boolean) {\n\t\tsettings.isIncognitoModeEnabled = isEnabled\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/SearchViewLegacyBackCallback.kt",
    "content": "package org.koitharu.kotatsu.main.ui\n\nimport android.os.Build\nimport androidx.activity.OnBackPressedCallback\nimport androidx.annotation.DeprecatedSinceApi\nimport com.google.android.material.search.SearchView\n\n@DeprecatedSinceApi(Build.VERSION_CODES.TIRAMISU)\nclass SearchViewLegacyBackCallback(\n\tprivate val searchView: SearchView\n) : OnBackPressedCallback(searchView.isShowing), SearchView.TransitionListener {\n\n\toverride fun handleOnBackPressed() {\n\t\tsearchView.hide()\n\t}\n\n\toverride fun onStateChanged(\n\t\tsearchView: SearchView,\n\t\tpreviousState: SearchView.TransitionState,\n\t\tnewState: SearchView.TransitionState\n\t) {\n\t\tisEnabled = newState >= SearchView.TransitionState.SHOWING\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/owners/AppBarOwner.kt",
    "content": "package org.koitharu.kotatsu.main.ui.owners\n\nimport com.google.android.material.appbar.AppBarLayout\n\ninterface AppBarOwner {\n\n\tval appBar: AppBarLayout\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/owners/BottomNavOwner.kt",
    "content": "package org.koitharu.kotatsu.main.ui.owners\n\nimport org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView\n\ninterface BottomNavOwner {\n\n\tval bottomNav: SlidingBottomNavigationView?\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/owners/BottomSheetOwner.kt",
    "content": "package org.koitharu.kotatsu.main.ui.owners\n\nimport android.view.View\n\ninterface BottomSheetOwner {\n\n\tval bottomSheet: View?\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/owners/SnackbarOwner.kt",
    "content": "package org.koitharu.kotatsu.main.ui.owners\n\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\n\ninterface SnackbarOwner {\n\n\tval snackbarHost: CoordinatorLayout\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/AppProtectHelper.kt",
    "content": "package org.koitharu.kotatsu.main.ui.protect\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.os.Bundle\nimport org.acra.dialog.CrashReportDialog\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass AppProtectHelper @Inject constructor(private val settings: AppSettings) :\n\tDefaultActivityLifecycleCallbacks {\n\n\tprivate var isUnlocked = settings.appPassword.isNullOrEmpty()\n\n\toverride fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n\t\tif (!isUnlocked && activity !is ProtectActivity && activity !is CrashReportDialog) {\n\t\t\tval sourceIntent = Intent(activity, activity.javaClass)\n\t\t\tactivity.intent?.let {\n\t\t\t\tsourceIntent.putExtras(it)\n\t\t\t\tsourceIntent.action = it.action\n\t\t\t\tsourceIntent.setDataAndType(it.data, it.type)\n\t\t\t}\n\t\t\tactivity.startActivity(ProtectActivity.newIntent(activity, sourceIntent))\n\t\t\tactivity.finishAfterTransition()\n\t\t}\n\t}\n\n\toverride fun onActivityDestroyed(activity: Activity) {\n\t\tif (activity !is ProtectActivity && activity.isFinishing && activity.isTaskRoot) {\n\t\t\trestoreLock()\n\t\t}\n\t}\n\n\tfun unlock() {\n\t\tisUnlocked = true\n\t}\n\n\tprivate fun restoreLock() {\n\t\tisUnlocked = settings.appPassword.isNullOrEmpty()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt",
    "content": "package org.koitharu.kotatsu.main.ui.protect\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.text.Editable\nimport android.view.KeyEvent\nimport android.view.View\nimport android.view.WindowManager\nimport android.view.inputmethod.EditorInfo\nimport android.widget.TextView\nimport androidx.activity.viewModels\nimport androidx.biometric.AuthenticationRequest\nimport androidx.biometric.AuthenticationRequest.Biometric\nimport androidx.biometric.AuthenticationResult\nimport androidx.biometric.AuthenticationResultCallback\nimport androidx.biometric.BiometricManager\nimport androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK\nimport androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS\nimport androidx.biometric.registerForAuthenticationResult\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.withResumed\nimport com.google.android.material.textfield.TextInputLayout\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityProtectBinding\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nclass ProtectActivity :\n\tBaseActivity<ActivityProtectBinding>(),\n\tTextView.OnEditorActionListener,\n\tDefaultTextWatcher,\n\tView.OnClickListener,\n\tAuthenticationResultCallback {\n\n\tprivate val viewModel by viewModels<ProtectViewModel>()\n\tprivate var canUseBiometric = false\n\n\tprivate val biometricPrompt = registerForAuthenticationResult(resultCallback = this)\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\twindow.addFlags(WindowManager.LayoutParams.FLAG_SECURE)\n\t\tsetContentView(ActivityProtectBinding.inflate(layoutInflater))\n\t\tviewBinding.editPassword.setOnEditorActionListener(this)\n\t\tviewBinding.editPassword.addTextChangedListener(this)\n\t\tviewBinding.buttonNext.setOnClickListener(this)\n\t\tviewBinding.buttonCancel.setOnClickListener(this)\n\n\t\tviewBinding.editPassword.inputType = if (viewModel.isNumericPassword) {\n\t\t\tEditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD\n\t\t} else {\n\t\t\tEditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD\n\t\t}\n\n\t\tviewModel.onError.observeEvent(this, this::onError)\n\t\tviewModel.isLoading.observe(this, this::onLoadingStateChanged)\n\t\tviewModel.onUnlockSuccess.observeEvent(this) {\n\t\t\tval intent = intent.getParcelableExtraCompat<Intent>(EXTRA_INTENT)\n\t\t\tstartActivity(intent)\n\t\t\tfinishAfterTransition()\n\t\t}\n\t\tlifecycleScope.launch {\n\t\t\twithResumed {\n\t\t\t\tcanUseBiometric = useFingerprint()\n\t\t\t\tupdateEndIcon()\n\t\t\t\tif (!canUseBiometric) {\n\t\t\t\t\tviewBinding.editPassword.requestFocus()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)\n\t\tviewBinding.root.setPadding(\n\t\t\tbarsInsets.left + basePadding,\n\t\t\tbarsInsets.top + basePadding,\n\t\t\tbarsInsets.right + basePadding,\n\t\t\tbarsInsets.bottom + basePadding,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_next -> viewModel.tryUnlock(viewBinding.editPassword.text?.toString().orEmpty())\n\t\t\tR.id.button_cancel -> finish()\n\t\t\tmaterialR.id.text_input_end_icon -> useFingerprint()\n\t\t}\n\t}\n\n\toverride fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {\n\t\treturn if (actionId == EditorInfo.IME_ACTION_DONE && viewBinding.buttonNext.isEnabled) {\n\t\t\tviewBinding.buttonNext.performClick()\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tviewBinding.layoutPassword.error = null\n\t\tviewBinding.buttonNext.isEnabled = !s.isNullOrEmpty()\n\t\tupdateEndIcon()\n\t}\n\n\toverride fun onAuthResult(result: AuthenticationResult) {\n\t\tif (result.isSuccess()) {\n\t\t\tviewModel.unlock()\n\t\t}\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tviewBinding.layoutPassword.error = e.getDisplayMessage(resources)\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tviewBinding.layoutPassword.isEnabled = !isLoading\n\t}\n\n\tprivate fun useFingerprint(): Boolean {\n\t\tif (!viewModel.isBiometricEnabled) {\n\t\t\treturn false\n\t\t}\n\t\tif (BiometricManager.from(this).canAuthenticate(BIOMETRIC_WEAK) != BIOMETRIC_SUCCESS) {\n\t\t\treturn false\n\t\t}\n\t\tval request = AuthenticationRequest.biometricRequest(\n\t\t\ttitle = getString(R.string.app_name),\n\t\t\tauthFallback = Biometric.Fallback.NegativeButton(getString(android.R.string.cancel)),\n\t\t\tinit = {\n\t\t\t\tsetMinStrength(Biometric.Strength.Class2)\n\t\t\t\tsetIsConfirmationRequired(false)\n\t\t\t},\n\t\t)\n\t\tbiometricPrompt.launch(request)\n\t\treturn true\n\t}\n\n\tprivate fun updateEndIcon() = with(viewBinding.layoutPassword) {\n\t\tval isFingerprintIcon = canUseBiometric && viewBinding.editPassword.text.isNullOrEmpty()\n\t\tif (isFingerprintIcon == (endIconMode == TextInputLayout.END_ICON_CUSTOM)) {\n\t\t\treturn@with\n\t\t}\n\t\tif (isFingerprintIcon) {\n\t\t\tendIconMode = TextInputLayout.END_ICON_CUSTOM\n\t\t\tsetEndIconDrawable(androidx.biometric.R.drawable.fingerprint_dialog_fp_icon)\n\t\t\tendIconContentDescription = getString(androidx.biometric.R.string.use_biometric_label)\n\t\t\tsetEndIconOnClickListener(this@ProtectActivity)\n\t\t} else {\n\t\t\tsetEndIconOnClickListener(null)\n\t\t\tsetEndIconDrawable(0)\n\t\t\tendIconContentDescription = null\n\t\t\tendIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val EXTRA_INTENT = \"src_intent\"\n\n\t\tfun newIntent(context: Context, sourceIntent: Intent): Intent {\n\t\t\treturn Intent(context, ProtectActivity::class.java)\n\t\t\t\t.putExtra(EXTRA_INTENT, sourceIntent)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt",
    "content": "package org.koitharu.kotatsu.main.ui.protect\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport org.koitharu.kotatsu.core.exceptions.WrongPasswordException\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.parsers.util.md5\nimport javax.inject.Inject\n\nprivate const val PASSWORD_COMPARE_DELAY = 1_000L\n\n@HiltViewModel\nclass ProtectViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val protectHelper: AppProtectHelper,\n) : BaseViewModel() {\n\n\tprivate var job: Job? = null\n\n\tval onUnlockSuccess = MutableEventFlow<Unit>()\n\n\tval isBiometricEnabled\n\t\tget() = settings.isBiometricProtectionEnabled\n\n\tval isNumericPassword\n\t\tget() = settings.isAppPasswordNumeric\n\n\tfun tryUnlock(password: String) {\n\t\tif (job?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tjob = launchLoadingJob {\n\t\t\tval passwordHash = password.md5()\n\t\t\tval appPasswordHash = settings.appPassword\n\t\t\tif (passwordHash == appPasswordHash) {\n\t\t\t\tunlock()\n\t\t\t} else {\n\t\t\t\tdelay(PASSWORD_COMPARE_DELAY)\n\t\t\t\tthrow WrongPasswordException()\n\t\t\t}\n\t\t}\n\t}\n\n\tfun unlock() {\n\t\tprotectHelper.unlock()\n\t\tonUnlockSuccess.call(Unit)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ScreenshotPolicyHelper.kt",
    "content": "package org.koitharu.kotatsu.main.ui.protect\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.view.WindowManager\nimport androidx.annotation.MainThread\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks\nimport javax.inject.Inject\n\nclass ScreenshotPolicyHelper @Inject constructor(\n\tprivate val settings: AppSettings,\n) : DefaultActivityLifecycleCallbacks {\n\n\toverride fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n\t\t(activity as? ContentContainer)?.setupScreenshotPolicy(activity)\n\t}\n\n\tprivate fun ContentContainer.setupScreenshotPolicy(activity: Activity) =\n\t\tlifecycleScope.launch(Dispatchers.Default) {\n\t\t\tsettings.observeAsFlow(AppSettings.KEY_SCREENSHOTS_POLICY) { screenshotsPolicy }\n\t\t\t\t.flatMapLatest { policy ->\n\t\t\t\t\twhen (policy) {\n\t\t\t\t\t\tScreenshotsPolicy.ALLOW -> flowOf(false)\n\t\t\t\t\t\tScreenshotsPolicy.BLOCK_NSFW -> withContext(Dispatchers.Main) {\n\t\t\t\t\t\t\tisNsfwContent()\n\t\t\t\t\t\t}.distinctUntilChanged()\n\n\t\t\t\t\t\tScreenshotsPolicy.BLOCK_ALL -> flowOf(true)\n\t\t\t\t\t\tScreenshotsPolicy.BLOCK_INCOGNITO -> settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) {\n\t\t\t\t\t\t\tisIncognitoModeEnabled\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}.collect { isSecure ->\n\t\t\t\t\twithContext(Dispatchers.Main) {\n\t\t\t\t\t\tif (isSecure) {\n\t\t\t\t\t\t\tactivity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tactivity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\n\tinterface ContentContainer : LifecycleOwner {\n\n\t\t@MainThread\n\t\tfun isNsfwContent(): Flow<Boolean>\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/welcome/WelcomeSheet.kt",
    "content": "package org.koitharu.kotatsu.main.ui.welcome\n\nimport android.accounts.AccountManager\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.chip.Chip\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.titleResId\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.getDisplayName\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.databinding.SheetWelcomeBinding\nimport org.koitharu.kotatsu.filter.ui.model.FilterProperty\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport java.util.Locale\n\n@AndroidEntryPoint\nclass WelcomeSheet : BaseAdaptiveSheet<SheetWelcomeBinding>(), ChipsView.OnChipClickListener, View.OnClickListener,\n\tActivityResultCallback<Uri?> {\n\n\tprivate val viewModel by viewModels<WelcomeViewModel>()\n\n\tprivate val backupSelectCall = registerForActivityResult(\n\t\tActivityResultContracts.OpenDocument(),\n\t\tthis,\n\t)\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetWelcomeBinding {\n\t\treturn SheetWelcomeBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: SheetWelcomeBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.textViewWelcomeTitle.isGone = resources.getBoolean(R.bool.is_tablet)\n\t\tbinding.chipsLocales.onChipClickListener = this\n\t\tbinding.chipsType.onChipClickListener = this\n\t\tbinding.chipBackup.setOnClickListener(this)\n\t\tbinding.chipSync.setOnClickListener(this)\n\t\tbinding.chipDirectories.setOnClickListener(this)\n\n\t\tviewModel.locales.observe(viewLifecycleOwner, ::onLocalesChanged)\n\t\tviewModel.types.observe(viewLifecycleOwner, ::onTypesChanged)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tviewBinding?.scrollView?.updatePadding(\n\t\t\tbottom = insets.getInsets(typeMask).bottom,\n\t\t)\n\t\treturn insets.consume(v, typeMask, bottom = true)\n\t}\n\n\toverride fun onChipClick(chip: Chip, data: Any?) {\n\t\twhen (data) {\n\t\t\tis ContentType -> viewModel.setTypeChecked(data, !chip.isChecked)\n\t\t\tis Locale -> viewModel.setLocaleChecked(data, !chip.isChecked)\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.chip_backup -> {\n\t\t\t\tif (!backupSelectCall.tryLaunch(arrayOf(\"*/*\"))) {\n\t\t\t\t\tSnackbar.make(\n\t\t\t\t\t\tv, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,\n\t\t\t\t\t).show()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tR.id.chip_sync -> {\n\t\t\t\tval am = AccountManager.get(v.context)\n\t\t\t\tval accountType = getString(R.string.account_type_sync)\n\t\t\t\tam.addAccount(accountType, accountType, null, null, requireActivity(), null, null)\n\t\t\t}\n\n            R.id.chip_directories -> {\n                router.openDirectoriesSettings()\n            }\n\t\t}\n\t}\n\n\toverride fun onActivityResult(result: Uri?) {\n\t\tif (result != null) {\n\t\t\trouter.showBackupRestoreDialog(result)\n\t\t}\n\t}\n\n\tprivate fun onLocalesChanged(value: FilterProperty<Locale>) {\n\t\tval chips = viewBinding?.chipsLocales ?: return\n\t\tchips.setChips(\n\t\t\tvalue.availableItems.map {\n\t\t\t\tChipsView.ChipModel(\n\t\t\t\t\ttitle = it.getDisplayName(chips.context),\n\t\t\t\t\tisChecked = it in value.selectedItems,\n\t\t\t\t\tdata = it,\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate fun onTypesChanged(value: FilterProperty<ContentType>) {\n\t\tval chips = viewBinding?.chipsType ?: return\n\t\tchips.setChips(\n\t\t\tvalue.availableItems.map {\n\t\t\t\tChipsView.ChipModel(\n\t\t\t\t\ttitle = getString(it.titleResId),\n\t\t\t\t\tisChecked = it in value.selectedItems,\n\t\t\t\t\tdata = it,\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/main/ui/welcome/WelcomeViewModel.kt",
    "content": "package org.koitharu.kotatsu.main.ui.welcome\n\nimport android.content.Context\nimport androidx.core.os.ConfigurationCompat\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.LocaleComparator\nimport org.koitharu.kotatsu.core.util.ext.mapSortedByCount\nimport org.koitharu.kotatsu.core.util.ext.sortedWithSafe\nimport org.koitharu.kotatsu.core.util.ext.toList\nimport org.koitharu.kotatsu.core.util.ext.toLocale\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.filter.ui.model.FilterProperty\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport java.util.EnumSet\nimport java.util.Locale\nimport javax.inject.Inject\n\n@HiltViewModel\nclass WelcomeViewModel @Inject constructor(\n\tprivate val repository: MangaSourcesRepository,\n\t@LocalizedAppContext context: Context,\n) : BaseViewModel() {\n\n\tprivate val allSources = repository.allMangaSources\n\tprivate val localesGroups by lazy { allSources.groupBy { it.locale.toLocale() } }\n\n\tprivate var updateJob: Job\n\n\tval locales = MutableStateFlow(\n\t\tFilterProperty<Locale>(\n\t\t\tavailableItems = listOf(Locale.ROOT),\n\t\t\tselectedItems = setOf(Locale.ROOT),\n\t\t\tisLoading = true,\n\t\t\terror = null,\n\t\t),\n\t)\n\n\tval types = MutableStateFlow(\n\t\tFilterProperty(\n\t\t\tavailableItems = listOf(ContentType.MANGA),\n\t\t\tselectedItems = setOf(ContentType.MANGA),\n\t\t\tisLoading = true,\n\t\t\terror = null,\n\t\t),\n\t)\n\n\tinit {\n\t\tupdateJob = launchJob(Dispatchers.Default) {\n\t\t\tval contentTypes = allSources.mapSortedByCount { it.contentType }\n\t\t\ttypes.value = types.value.copy(\n\t\t\t\tavailableItems = contentTypes,\n\t\t\t\tisLoading = false,\n\t\t\t)\n\t\t\tval languages = localesGroups.keys.associateBy { x -> x.language }\n\t\t\tval selectedLocales = HashSet<Locale>(2)\n\t\t\tConfigurationCompat.getLocales(context.resources.configuration).toList()\n\t\t\t\t.firstNotNullOfOrNull { lc -> languages[lc.language] }\n\t\t\t\t?.let { selectedLocales += it }\n\t\t\tselectedLocales += Locale.ROOT\n\t\t\tlocales.value = locales.value.copy(\n\t\t\t\tavailableItems = localesGroups.keys.sortedWithSafe(LocaleComparator()),\n\t\t\t\tselectedItems = selectedLocales,\n\t\t\t\tisLoading = false,\n\t\t\t)\n\t\t\trepository.clearNewSourcesBadge()\n\t\t\tcommit()\n\t\t}\n\t}\n\n\tfun setLocaleChecked(locale: Locale, isChecked: Boolean) {\n\t\tval snapshot = locales.value\n\t\tlocales.value = snapshot.copy(\n\t\t\tselectedItems = if (isChecked) {\n\t\t\t\tsnapshot.selectedItems + locale\n\t\t\t} else {\n\t\t\t\tsnapshot.selectedItems - locale\n\t\t\t},\n\t\t)\n\t\tval prevJob = updateJob\n\t\tupdateJob = launchJob(Dispatchers.Default) {\n\t\t\tprevJob.join()\n\t\t\tcommit()\n\t\t}\n\t}\n\n\tfun setTypeChecked(type: ContentType, isChecked: Boolean) {\n\t\tval snapshot = types.value\n\t\ttypes.value = snapshot.copy(\n\t\t\tselectedItems = if (isChecked) {\n\t\t\t\tsnapshot.selectedItems + type\n\t\t\t} else {\n\t\t\t\tsnapshot.selectedItems - type\n\t\t\t},\n\t\t)\n\t\tval prevJob = updateJob\n\t\tupdateJob = launchJob(Dispatchers.Default) {\n\t\t\tprevJob.join()\n\t\t\tcommit()\n\t\t}\n\t}\n\n\tprivate suspend fun commit() {\n\t\tval languages = locales.value.selectedItems.mapToSet { it.language }\n\t\tval types = types.value.selectedItems\n\t\tval enabledSources = allSources.filterTo(EnumSet.noneOf(MangaParserSource::class.java)) { x ->\n\t\t\tx.contentType in types && x.locale in languages\n\t\t}\n\t\trepository.setSourcesEnabledExclusive(enabledSources)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickActivity.kt",
    "content": "package org.koitharu.kotatsu.picker.ui\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.content.FileProvider\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.commit\nimport com.google.android.material.appbar.AppBarLayout\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.databinding.ActivityPickerBinding\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.main.ui.owners.SnackbarOwner\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.picker.ui.manga.MangaPickerFragment\nimport org.koitharu.kotatsu.picker.ui.page.PagePickerFragment\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport java.io.File\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass PageImagePickActivity : BaseActivity<ActivityPickerBinding>(),\n\tAppBarOwner,\n\tSnackbarOwner {\n\n\t@Inject\n\tlateinit var pageSaveHelperFactory: PageSaveHelper.Factory\n\n\toverride val appBar: AppBarLayout\n\t\tget() = viewBinding.appbar\n\n\toverride val snackbarHost: CoordinatorLayout\n\t\tget() = viewBinding.root\n\n\tprivate lateinit var pageSaveHelper: PageSaveHelper\n\tprivate val viewModel by viewModels<PageImagePickViewModel>()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityPickerBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tpageSaveHelper = pageSaveHelperFactory.create(this)\n\t\tviewModel.onError.observeEvent(this, DialogErrorObserver(viewBinding.container, null))\n\t\tviewModel.onFileReady.observeEvent(this, ::finishWithResult)\n\t\tviewModel.isLoading.observe(this, ::onLoadingStateChanged)\n\t\tval fm = supportFragmentManager\n\t\tif (fm.findFragmentById(R.id.container) == null) {\n\t\t\tfm.commit {\n\t\t\t\tsetReorderingAllowed(true)\n\t\t\t\tif (intent?.hasExtra(AppRouter.KEY_MANGA) == true) {\n\t\t\t\t\treplace(R.id.container, PagePickerFragment::class.java, intent.extras)\n\t\t\t\t} else {\n\t\t\t\t\treplace(R.id.container, MangaPickerFragment::class.java, null)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval bars = insets.getInsets(typeMask)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = bars.left,\n\t\t\tright = bars.right,\n\t\t\ttop = bars.top,\n\t\t)\n\t\treturn insets.consume(v, typeMask, top = true)\n\t}\n\n\tfun onMangaPicked(manga: Manga) {\n\t\tval args = Bundle(1)\n\t\targs.putLong(AppRouter.KEY_ID, manga.id)\n\t\tsupportFragmentManager.commit {\n\t\t\tsetReorderingAllowed(true)\n\t\t\treplace(R.id.container, PagePickerFragment::class.java, args)\n\t\t\taddToBackStack(null)\n\t\t}\n\t}\n\n\tfun onPagePicked(manga: Manga, page: ReaderPage) {\n\t\tval task = PageSaveHelper.Task(\n\t\t\tmanga = manga,\n\t\t\tchapterId = page.chapterId,\n\t\t\tpageNumber = page.index + 1,\n\t\t\tpage = page.toMangaPage(),\n\t\t)\n\t\tviewModel.savePageToTempFile(pageSaveHelper, task)\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tviewBinding.container.isGone = isLoading\n\t\tviewBinding.progressBar.isVisible = isLoading\n\t}\n\n\tprivate fun finishWithResult(file: File) {\n\t\tval uri = FileProvider.getUriForFile(applicationContext, \"${BuildConfig.APPLICATION_ID}.files\", file)\n\t\tval result = Intent()\n\t\tresult.setData(uri)\n\t\tresult.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n\t\tsetResult(RESULT_OK, result)\n\t\tfinish()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickContract.kt",
    "content": "package org.koitharu.kotatsu.picker.ui\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.activity.result.contract.ActivityResultContract\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.parsers.model.Manga\n\nclass PageImagePickContract : ActivityResultContract<Manga?, Uri?>() {\n\n\toverride fun createIntent(context: Context, input: Manga?): Intent =\n\t\tIntent(context, PageImagePickActivity::class.java)\n\t\t\t.putExtra(AppRouter.KEY_MANGA, input?.let { ParcelableManga(it) })\n\n\toverride fun parseResult(resultCode: Int, intent: Intent?): Uri? = intent?.data\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickViewModel.kt",
    "content": "package org.koitharu.kotatsu.picker.ui\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.reader.ui.PageSaveHelper\nimport java.io.File\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PageImagePickViewModel @Inject constructor() : BaseViewModel() {\n\n\tval onFileReady = MutableEventFlow<File>()\n\n\tfun savePageToTempFile(pageSaveHelper: PageSaveHelper, task: PageSaveHelper.Task) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval file = pageSaveHelper.saveToTempFile(task)\n\t\t\tonFileReady.call(file)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerFragment.kt",
    "content": "package org.koitharu.kotatsu.picker.ui.manga\n\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.picker.ui.PageImagePickActivity\n\n@AndroidEntryPoint\nclass MangaPickerFragment : MangaListFragment() {\n\n\toverride val isSwipeRefreshEnabled = false\n\n\toverride val viewModel by viewModels<MangaPickerViewModel>()\n\n\toverride fun onScrolledToEnd() = Unit\n\n\toverride fun onItemClick(item: MangaListModel, view: View) {\n\t\t(activity as PageImagePickActivity).onMangaPicked(item.manga)\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tactivity?.setTitle(R.string.pick_manga_page)\n\t}\n\n\toverride fun onItemLongClick(item: MangaListModel, view: View): Boolean = false\n\n\toverride fun onItemContextClick(item: MangaListModel, view: View): Boolean = false\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerViewModel.kt",
    "content": "package org.koitharu.kotatsu.picker.ui.manga\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport javax.inject.Inject\nimport kotlinx.coroutines.flow.SharedFlow\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\n\n@HiltViewModel\nclass MangaPickerViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tmangaDataRepository: MangaDataRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val favouritesRepository: FavouritesRepository,\n\tprivate val mangaListMapper: MangaListMapper,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges) {\n\n\toverride val content: StateFlow<List<ListModel>>\n\t\tget() = flow {\n\t\t\temit(loadList())\n\t\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, listOf(LoadingState))\n\n\toverride fun onRefresh() = Unit\n\n\toverride fun onRetry() = Unit\n\n\tprivate suspend fun loadList() = buildList {\n\t\tval history = historyRepository.getList(0, Int.MAX_VALUE)\n\t\tif (history.isNotEmpty()) {\n\t\t\tadd(ListHeader(R.string.history))\n\t\t\tmangaListMapper.toListModelList(this, history, settings.listMode)\n\t\t}\n\t\tval categories = favouritesRepository.observeCategoriesForLibrary().first()\n\t\tfor (category in categories) {\n\t\t\tval favorites = favouritesRepository.getManga(category.id)\n\t\t\tif (favorites.isNotEmpty()) {\n\t\t\t\tadd(ListHeader(category.title))\n\t\t\t\tmangaListMapper.toListModelList(this, favorites, settings.listMode)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerFragment.kt",
    "content": "package org.koitharu.kotatsu.picker.ui.page\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.list.BoundsScrollListener\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.showOrHide\nimport org.koitharu.kotatsu.databinding.FragmentPagesBinding\nimport org.koitharu.kotatsu.details.ui.pager.pages.PageThumbnail\nimport org.koitharu.kotatsu.details.ui.pager.pages.PageThumbnailAdapter\nimport org.koitharu.kotatsu.list.ui.GridSpanResolver\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.picker.ui.PageImagePickActivity\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass PagePickerFragment :\n\tBaseFragment<FragmentPagesBinding>(),\n\tOnListItemClickListener<PageThumbnail> {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate val viewModel by viewModels<PagePickerViewModel>()\n\n\tprivate var thumbnailsAdapter: PageThumbnailAdapter? = null\n\tprivate var spanResolver: GridSpanResolver? = null\n\tprivate var scrollListener: ScrollListener? = null\n\n\tprivate val spanSizeLookup = SpanSizeLookup()\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPagesBinding {\n\t\treturn FragmentPagesBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: FragmentPagesBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tspanResolver = GridSpanResolver(binding.root.resources)\n\t\tthumbnailsAdapter = PageThumbnailAdapter(\n\t\t\tclickListener = this@PagePickerFragment,\n\t\t)\n\t\tviewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) // before rv initialization\n\t\twith(binding.recyclerView) {\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\tadapter = thumbnailsAdapter\n\t\t\tsetHasFixedSize(true)\n\t\t\tPagerNestedScrollHelper(this).bind(viewLifecycleOwner)\n\t\t\taddOnLayoutChangeListener(spanResolver)\n\t\t\taddOnScrollListener(ScrollListener().also { scrollListener = it })\n\t\t\t(layoutManager as GridLayoutManager).let {\n\t\t\t\tit.spanSizeLookup = spanSizeLookup\n\t\t\t\tit.spanCount = checkNotNull(spanResolver).spanCount\n\t\t\t}\n\t\t}\n\t\tviewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged)\n\t\tviewModel.isNoChapters.observe(viewLifecycleOwner, ::onNoChaptersChanged)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))\n\t\tviewModel.isLoading.observe(viewLifecycleOwner) { binding.progressBar.showOrHide(it) }\n\t\tviewModel.isLoadingDown.observe(viewLifecycleOwner) { binding.progressBarBottom.showOrHide(it) }\n\t\tviewModel.manga.observe(viewLifecycleOwner, Lifecycle.State.RESUMED) {\n\t\t\tactivity?.title = it?.toManga()?.title.ifNullOrEmpty { getString(R.string.pick_manga_page) }\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\tspanResolver = null\n\t\tscrollListener = null\n\t\tthumbnailsAdapter = null\n\t\tspanSizeLookup.invalidateCache()\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeBask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeBask)\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAll(typeBask)\n\t}\n\n\toverride fun onItemClick(item: PageThumbnail, view: View) {\n\t\tval manga = viewModel.manga.value?.toManga() ?: return\n\t\t(activity as PageImagePickActivity).onPagePicked(manga, item.page)\n\t}\n\n\toverride fun onItemLongClick(item: PageThumbnail, view: View): Boolean = false\n\n\toverride fun onItemContextClick(item: PageThumbnail, view: View): Boolean = false\n\n\tprivate suspend fun onThumbnailsChanged(list: List<ListModel>) {\n\t\tval adapter = thumbnailsAdapter ?: return\n\t\tadapter.emit(list)\n\t\tspanSizeLookup.invalidateCache()\n\t\tviewBinding?.recyclerView?.let {\n\t\t\tscrollListener?.postInvalidate(it)\n\t\t}\n\t}\n\n\tprivate fun onGridScaleChanged(scale: Float) {\n\t\tspanSizeLookup.invalidateCache()\n\t\tspanResolver?.setGridSize(scale, requireViewBinding().recyclerView)\n\t}\n\n\tprivate fun onNoChaptersChanged(isNoChapters: Boolean) {\n\t\twith(viewBinding ?: return) {\n\t\t\ttextViewHolder.isVisible = isNoChapters\n\t\t\trecyclerView.isInvisible = isNoChapters\n\t\t}\n\t}\n\n\tprivate inner class ScrollListener : BoundsScrollListener(3, 3) {\n\n\t\toverride fun onScrolledToStart(recyclerView: RecyclerView) = Unit\n\n\t\toverride fun onScrolledToEnd(recyclerView: RecyclerView) {\n\t\t\tviewModel.loadNextChapter()\n\t\t}\n\t}\n\n\tprivate inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() {\n\n\t\tinit {\n\t\t\tisSpanIndexCacheEnabled = true\n\t\t\tisSpanGroupIndexCacheEnabled = true\n\t\t}\n\n\t\toverride fun getSpanSize(position: Int): Int {\n\t\t\tval total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1\n\t\t\treturn when (thumbnailsAdapter?.getItemViewType(position)) {\n\t\t\t\tListItemType.PAGE_THUMB.ordinal -> 1\n\t\t\t\telse -> total\n\t\t\t}\n\t\t}\n\n\t\tfun invalidateCache() {\n\t\t\tinvalidateSpanGroupIndexCache()\n\t\t\tinvalidateSpanIndexCache()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerViewModel.kt",
    "content": "package org.koitharu.kotatsu.picker.ui.page\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.nav.MangaIntent\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.firstNotNull\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.domain.DetailsLoadUseCase\nimport org.koitharu.kotatsu.details.ui.pager.pages.PageThumbnail\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.reader.domain.ChaptersLoader\nimport javax.inject.Inject\n\n@HiltViewModel\nclass PagePickerViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val chaptersLoader: ChaptersLoader,\n\tprivate val detailsLoadUseCase: DetailsLoadUseCase,\n\tsettings: AppSettings,\n) : BaseViewModel() {\n\n\tprivate val intent = MangaIntent(savedStateHandle)\n\n\tprivate var loadingJob: Job? = null\n\tprivate var loadingNextJob: Job? = null\n\n\tval thumbnails = MutableStateFlow<List<ListModel>>(emptyList())\n\tval isLoadingDown = MutableStateFlow(false)\n\tval manga = MutableStateFlow(intent.manga?.let { MangaDetails(it) })\n\n\tval isNoChapters = manga.map {\n\t\tit != null && it.isLoaded && it.allChapters.isEmpty()\n\t}\n\n\tval gridScale = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_GRID_SIZE_PAGES,\n\t\tvalueProducer = { gridSizePages / 100f },\n\t)\n\n\tinit {\n\t\tloadingJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tdoInit()\n\t\t}\n\t}\n\n\tprivate suspend fun doInit() {\n\t\tval details = detailsLoadUseCase.invoke(intent, force = false)\n\t\t\t.onEach { manga.value = it }\n\t\t\t.first { x -> x.isLoaded }\n\t\tchaptersLoader.init(details)\n\t\tval initialChapterId = details.allChapters.firstOrNull()?.id ?: return\n\t\tif (!chaptersLoader.hasPages(initialChapterId)) {\n\t\t\tchaptersLoader.loadSingleChapter(initialChapterId)\n\t\t}\n\t\tupdateList()\n\t}\n\n\tfun loadNextChapter() {\n\t\tif (loadingJob?.isActive == true || loadingNextJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tloadingNextJob = launchJob(Dispatchers.Default) {\n\t\t\tisLoadingDown.value = true\n\t\t\ttry {\n\t\t\t\tval currentId = chaptersLoader.last().chapterId\n\t\t\t\tchaptersLoader.loadPrevNextChapter(manga.firstNotNull(), currentId, isNext = true)\n\t\t\t\tupdateList()\n\t\t\t} finally {\n\t\t\t\tisLoadingDown.value = false\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun updateList() {\n\t\tval snapshot = chaptersLoader.snapshot()\n\t\tval pages = buildList(snapshot.size + chaptersLoader.size + 2) {\n\t\t\tvar previousChapterId = 0L\n\t\t\tfor (page in snapshot) {\n\t\t\t\tif (page.chapterId != previousChapterId) {\n\t\t\t\t\tchaptersLoader.peekChapter(page.chapterId)?.let {\n\t\t\t\t\t\tadd(ListHeader(it))\n\t\t\t\t\t}\n\t\t\t\t\tpreviousChapterId = page.chapterId\n\t\t\t\t}\n\t\t\t\tthis += PageThumbnail(\n\t\t\t\t\tisCurrent = false,\n\t\t\t\t\tpage = page,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tthumbnails.value = pages\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/data/ModelMapping.kt",
    "content": "package org.koitharu.kotatsu.reader.data\n\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\n\nfun Manga.filterChapters(branch: String?): Manga {\n\tif (chapters.isNullOrEmpty()) return this\n\treturn withChapters(chapters = chapters?.filter { it.branch == branch })\n}\n\nprivate fun Manga.withChapters(chapters: List<MangaChapter>?) = copy(\n\tchapters = chapters,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/data/TapGridSettings.kt",
    "content": "package org.koitharu.kotatsu.reader.data\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport dagger.Reusable\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.flowOn\nimport org.koitharu.kotatsu.core.util.ext.getEnumValue\nimport org.koitharu.kotatsu.core.util.ext.observeChanges\nimport org.koitharu.kotatsu.core.util.ext.putAll\nimport org.koitharu.kotatsu.core.util.ext.putEnumValue\nimport org.koitharu.kotatsu.reader.domain.TapGridArea\nimport org.koitharu.kotatsu.reader.ui.tapgrid.TapAction\nimport javax.inject.Inject\n\n@Reusable\nclass TapGridSettings @Inject constructor(@ApplicationContext context: Context) {\n\n\tprivate val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)\n\n\tinit {\n\t\tif (!prefs.getBoolean(KEY_INIT, false)) {\n\t\t\tinitPrefs(withDefaultValues = true)\n\t\t}\n\t}\n\n\tfun getTapAction(area: TapGridArea, isLongTap: Boolean): TapAction? {\n\t\tval key = getPrefKey(area, isLongTap)\n\t\treturn prefs.getEnumValue(key, TapAction::class.java)\n\t}\n\n\tfun setTapAction(area: TapGridArea, isLongTap: Boolean, action: TapAction?) {\n\t\tval key = getPrefKey(area, isLongTap)\n\t\tprefs.edit { putEnumValue(key, action) }\n\t}\n\n\tfun reset() {\n\t\tinitPrefs(withDefaultValues = true)\n\t}\n\n\tfun disableAll() {\n\t\tinitPrefs(withDefaultValues = false)\n\t}\n\n\tfun observeChanges() = prefs.observeChanges().flowOn(Dispatchers.IO)\n\n\tfun getAllValues(): Map<String, *> = prefs.all\n\n\tfun upsertAll(m: Map<String, *>) = prefs.edit {\n\t\tclear()\n\t\tputAll(m)\n\t}\n\n\tprivate fun initPrefs(withDefaultValues: Boolean) {\n\t\tprefs.edit {\n\t\t\tclear()\n\t\t\tif (withDefaultValues) {\n\t\t\t\tinitDefaultActions(this)\n\t\t\t}\n\t\t\tputBoolean(KEY_INIT, true)\n\t\t}\n\t}\n\n\tprivate fun getPrefKey(area: TapGridArea, isLongTap: Boolean): String = if (isLongTap) {\n\t\tarea.name + SUFFIX_LONG\n\t} else {\n\t\tarea.name\n\t}\n\n\tprivate fun initDefaultActions(editor: SharedPreferences.Editor) {\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.TOP_LEFT, false), TapAction.PAGE_PREV)\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.TOP_CENTER, false), TapAction.PAGE_PREV)\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.CENTER_LEFT, false), TapAction.PAGE_PREV)\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.BOTTOM_LEFT, false), TapAction.PAGE_PREV)\n\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.CENTER, false), TapAction.TOGGLE_UI)\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.CENTER, true), TapAction.SHOW_MENU)\n\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.TOP_RIGHT, false), TapAction.PAGE_NEXT)\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.CENTER_RIGHT, false), TapAction.PAGE_NEXT)\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.BOTTOM_CENTER, false), TapAction.PAGE_NEXT)\n\t\teditor.putEnumValue(getPrefKey(TapGridArea.BOTTOM_RIGHT, false), TapAction.PAGE_NEXT)\n\t}\n\n\tprivate companion object {\n\n\t\tprivate const val PREFS_NAME = \"tap_grid\"\n\t\tprivate const val KEY_INIT = \"_init\"\n\t\tprivate const val SUFFIX_LONG = \"_long\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/ChapterPages.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nimport androidx.collection.LongSparseArray\nimport androidx.collection.contains\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\n\nclass ChapterPages private constructor(private val pages: ArrayDeque<ReaderPage>) : List<ReaderPage> by pages {\n\n\t// map chapterId to index in pages deque\n\tprivate val indices = LongSparseArray<IntRange>()\n\n\tconstructor() : this(ArrayDeque())\n\n\tval chaptersSize: Int\n\t\tget() = indices.size()\n\n\t@Synchronized\n\tfun removeFirst() {\n\t\tval chapterId = pages.first().chapterId\n\t\tindices.remove(chapterId)\n\t\tvar delta = 0\n\t\twhile (pages.first().chapterId == chapterId) {\n\t\t\tpages.removeFirst()\n\t\t\tdelta--\n\t\t}\n\t\tshiftIndices(delta)\n\t}\n\n\t@Synchronized\n\tfun removeLast() {\n\t\tval chapterId = pages.last().chapterId\n\t\tindices.remove(chapterId)\n\t\twhile (pages.last().chapterId == chapterId) {\n\t\t\tpages.removeLast()\n\t\t}\n\t}\n\n\t@Synchronized\n\tfun addLast(id: Long, newPages: List<ReaderPage>): Boolean {\n\t\tif (id in indices) {\n\t\t\treturn false\n\t\t}\n\t\tindices.put(id, pages.size until (pages.size + newPages.size))\n\t\tpages.addAll(newPages)\n\t\treturn true\n\t}\n\n\t@Synchronized\n\tfun addFirst(id: Long, newPages: List<ReaderPage>): Boolean {\n\t\tif (id in indices) {\n\t\t\treturn false\n\t\t}\n\t\tshiftIndices(newPages.size)\n\t\tindices.put(id, newPages.indices)\n\t\tpages.addAll(0, newPages)\n\t\treturn true\n\t}\n\n\t@Synchronized\n\tfun clear() {\n\t\tindices.clear()\n\t\tpages.clear()\n\t}\n\n\tfun size(id: Long) = indices[id]?.run {\n\t\tendInclusive - start + 1\n\t} ?: 0\n\n\tfun subList(id: Long): List<ReaderPage> {\n\t\tval range = indices[id] ?: return emptyList()\n\t\treturn pages.subList(range.first, range.last + 1)\n\t}\n\n\toperator fun contains(chapterId: Long) = chapterId in indices\n\n\tprivate fun shiftIndices(delta: Int) {\n\t\tfor (i in 0 until indices.size()) {\n\t\t\tval range = indices.valueAt(i)\n\t\t\tindices.setValueAt(i, range + delta)\n\t\t}\n\t}\n\n\tprivate operator fun IntRange.plus(delta: Int): IntRange {\n\t\treturn IntRange(start + delta, endInclusive + delta)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/ChaptersLoader.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nimport android.util.LongSparseArray\nimport androidx.annotation.CheckResult\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport javax.inject.Inject\n\nprivate const val PAGES_TRIM_THRESHOLD = 120\n\n@ViewModelScoped\nclass ChaptersLoader @Inject constructor(\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\n\tprivate val chapters = LongSparseArray<MangaChapter>()\n\tprivate val chapterPages = ChapterPages()\n\tprivate val mutex = Mutex()\n\n\tval size: Int\n\t\tget() = chapters.size()\n\n\tsuspend fun init(manga: MangaDetails) = mutex.withLock {\n\t\tchapters.clear()\n\t\tmanga.allChapters.forEach {\n\t\t\tchapters.put(it.id, it)\n\t\t}\n\t}\n\n\tsuspend fun loadPrevNextChapter(manga: MangaDetails, currentId: Long, isNext: Boolean): Boolean {\n\t\tval chapters = manga.allChapters\n\t\tval predicate: (MangaChapter) -> Boolean = { it.id == currentId }\n\t\tval index = if (isNext) chapters.indexOfFirst(predicate) else chapters.indexOfLast(predicate)\n\t\tif (index == -1) return false\n\t\tval newChapter = chapters.getOrNull(if (isNext) index + 1 else index - 1) ?: return false\n\t\tval newPages = loadChapter(newChapter.id)\n\t\tmutex.withLock {\n\t\t\tif (chapterPages.chaptersSize > 1) {\n\t\t\t\t// trim pages\n\t\t\t\tif (chapterPages.size > PAGES_TRIM_THRESHOLD) {\n\t\t\t\t\tif (isNext) {\n\t\t\t\t\t\tchapterPages.removeFirst()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tchapterPages.removeLast()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isNext) {\n\t\t\t\tchapterPages.addLast(newChapter.id, newPages)\n\t\t\t} else {\n\t\t\t\tchapterPages.addFirst(newChapter.id, newPages)\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\t@CheckResult\n\tsuspend fun loadSingleChapter(chapterId: Long): Boolean {\n\t\tval pages = loadChapter(chapterId)\n\t\treturn mutex.withLock {\n\t\t\tchapterPages.clear()\n\t\t\tchapterPages.addLast(chapterId, pages)\n\t\t\tpages.isNotEmpty()\n\t\t}\n\t}\n\n\tfun peekChapter(chapterId: Long): MangaChapter? = chapters[chapterId]\n\n\tfun hasPages(chapterId: Long): Boolean {\n\t\treturn chapterId in chapterPages\n\t}\n\n\tfun getPages(chapterId: Long): List<MangaPage> = synchronized(chapterPages) {\n\t\treturn chapterPages.subList(chapterId).map { it.toMangaPage() }\n\t}\n\n\tfun getPagesCount(chapterId: Long): Int {\n\t\treturn chapterPages.size(chapterId)\n\t}\n\n\tfun last() = chapterPages.last()\n\n\tfun first() = chapterPages.first()\n\n\tfun snapshot() = chapterPages.toList()\n\n\tprivate suspend fun loadChapter(chapterId: Long): List<ReaderPage> {\n\t\tval chapter = checkNotNull(chapters[chapterId]) { \"Requested chapter not found\" }\n\t\tval repo = mangaRepositoryFactory.create(chapter.source)\n\t\treturn repo.getPages(chapter).mapIndexed { index, page ->\n\t\t\tReaderPage(page, index, chapterId)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/DetectReaderModeUseCase.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nimport android.graphics.BitmapFactory\nimport android.util.Size\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport okhttp3.OkHttpClient\nimport org.koitharu.kotatsu.core.network.MangaHttpClient\nimport org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.util.ext.isFileUri\nimport org.koitharu.kotatsu.core.util.ext.isZipUri\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport java.io.InputStream\nimport java.util.zip.ZipFile\nimport javax.inject.Inject\nimport kotlin.math.roundToInt\n\nclass DetectReaderModeUseCase @Inject constructor(\n\tprivate val dataRepository: MangaDataRepository,\n\tprivate val settings: AppSettings,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\t@MangaHttpClient private val okHttpClient: OkHttpClient,\n\tprivate val imageProxyInterceptor: ImageProxyInterceptor,\n) {\n\n\tsuspend operator fun invoke(manga: Manga, state: ReaderState?): ReaderMode {\n\t\tdataRepository.getReaderMode(manga.id)?.let { return it }\n\t\tval defaultMode = settings.defaultReaderMode\n\t\tif (!settings.isReaderModeDetectionEnabled || defaultMode == ReaderMode.WEBTOON) {\n\t\t\treturn defaultMode\n\t\t}\n\t\tval chapter = state?.let { manga.findChapterById(it.chapterId) }\n\t\t\t?: manga.chapters?.firstOrNull()\n\t\t\t?: error(\"There are no chapters in this manga\")\n\t\tval repo = mangaRepositoryFactory.create(manga.source)\n\t\tval pages = repo.getPages(chapter)\n\t\treturn runCatchingCancellable {\n\t\t\tval isWebtoon = guessMangaIsWebtoon(repo, pages)\n\t\t\tif (isWebtoon) ReaderMode.WEBTOON else defaultMode\n\t\t}.onSuccess {\n\t\t\tdataRepository.saveReaderMode(manga, it)\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}.getOrDefault(defaultMode)\n\t}\n\n\t/**\n\t * Automatic determine type of manga by page size\n\t * @return ReaderMode.WEBTOON if page is wide\n\t */\n\tprivate suspend fun guessMangaIsWebtoon(repository: MangaRepository, pages: List<MangaPage>): Boolean {\n\t\tval pageIndex = (pages.size * 0.3).roundToInt()\n\t\tval page = requireNotNull(pages.getOrNull(pageIndex)) { \"No pages\" }\n\t\tval url = repository.getPageUrl(page)\n\t\tval uri = url.toUri()\n\n\t\tval size = when {\n\t\t\turi.isZipUri() -> runInterruptible(Dispatchers.IO) {\n\t\t\t\tZipFile(uri.schemeSpecificPart).use { zip ->\n\t\t\t\t\tval entry = zip.getEntry(uri.fragment)\n\t\t\t\t\tzip.getInputStream(entry).use {\n\t\t\t\t\t\tgetBitmapSize(it)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\turi.isFileUri() -> runInterruptible(Dispatchers.IO) {\n\t\t\t\turi.toFile().inputStream().use {\n\t\t\t\t\tgetBitmapSize(it)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tval request = PageLoader.createPageRequest(url, page.source)\n\t\t\t\timageProxyInterceptor.interceptPageRequest(request, okHttpClient).use {\n\t\t\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\t\t\tgetBitmapSize(it.body?.byteStream())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn size.width * MIN_WEBTOON_RATIO < size.height\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val MIN_WEBTOON_RATIO = 1.8\n\n\t\tprivate fun getBitmapSize(input: InputStream?): Size {\n\t\t\tval options = BitmapFactory.Options().apply {\n\t\t\t\tinJustDecodeBounds = true\n\t\t\t}\n\t\t\tBitmapFactory.decodeStream(input, null, options)?.recycle()\n\t\t\tval imageHeight: Int = options.outHeight\n\t\t\tval imageWidth: Int = options.outWidth\n\t\t\tcheck(imageHeight > 0 && imageWidth > 0)\n\t\t\treturn Size(imageWidth, imageHeight)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/EdgeDetector.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Color\nimport android.graphics.Point\nimport android.graphics.Rect\nimport androidx.annotation.ColorInt\nimport androidx.core.graphics.alpha\nimport androidx.core.graphics.blue\nimport androidx.core.graphics.green\nimport androidx.core.graphics.red\nimport com.davemorrissey.labs.subscaleview.ImageSource\nimport com.davemorrissey.labs.subscaleview.decoder.SkiaPooledImageRegionDecoder\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.core.util.SynchronizedSieveCache\nimport kotlin.math.abs\nimport kotlin.math.max\nimport kotlin.math.min\n\nclass EdgeDetector(private val context: Context) {\n\n\tprivate val mutex = Mutex()\n\tprivate val cache = SynchronizedSieveCache<ImageSource, Rect>(CACHE_SIZE)\n\n\tsuspend fun getBounds(imageSource: ImageSource): Rect? {\n\t\tcache[imageSource]?.let { rect ->\n\t\t\treturn if (rect.isEmpty) null else rect\n\t\t}\n\t\treturn mutex.withLock {\n\t\t\twithContext(Dispatchers.IO) {\n\t\t\t\tval decoder = SkiaPooledImageRegionDecoder(Bitmap.Config.RGB_565)\n\t\t\t\ttry {\n\t\t\t\t\tval size = runInterruptible {\n\t\t\t\t\t\tdecoder.init(context, imageSource)\n\t\t\t\t\t}\n\t\t\t\t\tval scaleFactor = calculateScaleFactor(size)\n\t\t\t\t\tval sampleSize = (1f / scaleFactor).toInt().coerceAtLeast(1)\n\n\t\t\t\t\tval fullBitmap = decoder.decodeRegion(\n\t\t\t\t\t\tRect(0, 0, size.x, size.y),\n\t\t\t\t\t\tsampleSize,\n\t\t\t\t\t)\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tval edges = coroutineScope {\n\t\t\t\t\t\t\tlistOf(\n\t\t\t\t\t\t\t\tasync { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = true) },\n\t\t\t\t\t\t\t\tasync { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = true) },\n\t\t\t\t\t\t\t\tasync { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = false) },\n\t\t\t\t\t\t\t\tasync { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = false) },\n\t\t\t\t\t\t\t).awaitAll()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar hasEdges = false\n\t\t\t\t\t\tfor (edge in edges) {\n\t\t\t\t\t\t\tif (edge > 0) {\n\t\t\t\t\t\t\t\thasEdges = true\n\t\t\t\t\t\t\t} else if (edge < 0) {\n\t\t\t\t\t\t\t\treturn@withContext null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (hasEdges) {\n\t\t\t\t\t\t\tRect(edges[0], edges[1], size.x - edges[2], size.y - edges[3])\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnull\n\t\t\t\t\t\t}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tfullBitmap.recycle()\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tdecoder.recycle()\n\t\t\t\t}\n\t\t\t}\n\t\t}.also {\n\t\t\tcache.put(imageSource, it ?: EMPTY_RECT)\n\t\t}\n\t}\n\n\tprivate fun detectLeftRightEdge(bitmap: Bitmap, size: Point, sampleSize: Int, isLeft: Boolean): Int {\n\t\tvar width = size.x\n\t\tval rectCount = size.x / BLOCK_SIZE\n\t\tval maxRect = rectCount / 3\n\t\tval blockPixels = IntArray(BLOCK_SIZE * BLOCK_SIZE)\n\n\t\tval bitmapWidth = bitmap.width\n\t\tval bitmapHeight = bitmap.height\n\n\t\tfor (i in 0 until rectCount) {\n\t\t\tif (i > maxRect) {\n\t\t\t\treturn -1\n\t\t\t}\n\t\t\tvar dd = BLOCK_SIZE\n\t\t\tfor (j in 0 until size.y / BLOCK_SIZE) {\n\t\t\t\tval regionX = if (isLeft) i * BLOCK_SIZE else size.x - (i + 1) * BLOCK_SIZE\n\t\t\t\tval regionY = j * BLOCK_SIZE\n\n\t\t\t\t// Convert to bitmap coordinates\n\t\t\t\tval bitmapX = regionX / sampleSize\n\t\t\t\tval bitmapY = regionY / sampleSize\n\t\t\t\tval blockWidth = min(BLOCK_SIZE / sampleSize, bitmapWidth - bitmapX)\n\t\t\t\tval blockHeight = min(BLOCK_SIZE / sampleSize, bitmapHeight - bitmapY)\n\n\t\t\t\tif (blockWidth > 0 && blockHeight > 0) {\n\t\t\t\t\tbitmap.getPixels(blockPixels, 0, blockWidth, bitmapX, bitmapY, blockWidth, blockHeight)\n\n\t\t\t\t\tfor (ii in 0 until minOf(blockWidth, dd / sampleSize)) {\n\t\t\t\t\t\tfor (jj in 0 until blockHeight) {\n\t\t\t\t\t\t\tval bi = if (isLeft) ii else blockWidth - ii - 1\n\t\t\t\t\t\t\tval pixel = blockPixels[jj * blockWidth + bi]\n\t\t\t\t\t\t\tif (pixel.isNotWhite()) {\n\t\t\t\t\t\t\t\twidth = minOf(width, BLOCK_SIZE * i + ii * sampleSize)\n\t\t\t\t\t\t\t\tdd -= sampleSize\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (dd == 0) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (dd < BLOCK_SIZE) {\n\t\t\t\tbreak // We have already found vertical field or it is not exist\n\t\t\t}\n\t\t}\n\t\treturn width\n\t}\n\n\tprivate fun detectTopBottomEdge(bitmap: Bitmap, size: Point, sampleSize: Int, isTop: Boolean): Int {\n\t\tvar height = size.y\n\t\tval rectCount = size.y / BLOCK_SIZE\n\t\tval maxRect = rectCount / 3\n\t\tval blockPixels = IntArray(BLOCK_SIZE * BLOCK_SIZE)\n\n\t\tval bitmapWidth = bitmap.width\n\t\tval bitmapHeight = bitmap.height\n\n\t\tfor (j in 0 until rectCount) {\n\t\t\tif (j > maxRect) {\n\t\t\t\treturn -1\n\t\t\t}\n\t\t\tvar dd = BLOCK_SIZE\n\t\t\tfor (i in 0 until size.x / BLOCK_SIZE) {\n\t\t\t\tval regionX = i * BLOCK_SIZE\n\t\t\t\tval regionY = if (isTop) j * BLOCK_SIZE else size.y - (j + 1) * BLOCK_SIZE\n\n\t\t\t\t// Convert to bitmap coordinates\n\t\t\t\tval bitmapX = regionX / sampleSize\n\t\t\t\tval bitmapY = regionY / sampleSize\n\t\t\t\tval blockWidth = min(BLOCK_SIZE / sampleSize, bitmapWidth - bitmapX)\n\t\t\t\tval blockHeight = min(BLOCK_SIZE / sampleSize, bitmapHeight - bitmapY)\n\n\t\t\t\tif (blockWidth > 0 && blockHeight > 0) {\n\t\t\t\t\tbitmap.getPixels(blockPixels, 0, blockWidth, bitmapX, bitmapY, blockWidth, blockHeight)\n\n\t\t\t\t\tfor (jj in 0 until minOf(blockHeight, dd / sampleSize)) {\n\t\t\t\t\t\tfor (ii in 0 until blockWidth) {\n\t\t\t\t\t\t\tval bj = if (isTop) jj else blockHeight - jj - 1\n\t\t\t\t\t\t\tval pixel = blockPixels[bj * blockWidth + ii]\n\t\t\t\t\t\t\tif (pixel.isNotWhite()) {\n\t\t\t\t\t\t\t\theight = minOf(height, BLOCK_SIZE * j + jj * sampleSize)\n\t\t\t\t\t\t\t\tdd -= sampleSize\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (dd == 0) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (dd < BLOCK_SIZE) {\n\t\t\t\tbreak // We have already found vertical field or it is not exist\n\t\t\t}\n\t\t}\n\t\treturn height\n\t}\n\n\t/**\n\t * Calculate scale factor for performance optimization.\n\t * Large images can be downscaled for edge detection without losing accuracy.\n\t */\n\tprivate fun calculateScaleFactor(size: Point): Float {\n\t\tval maxDimension = max(size.x, size.y)\n\t\treturn when {\n\t\t\tmaxDimension <= 1024 -> 1.0f\n\t\t\tmaxDimension <= 2048 -> 0.75f\n\t\t\tmaxDimension <= 4096 -> 0.5f\n\t\t\telse -> 0.25f\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val BLOCK_SIZE = 100\n\t\tprivate const val COLOR_TOLERANCE = 16\n\t\tprivate const val CACHE_SIZE = 24\n\t\tprivate val EMPTY_RECT = Rect(0, 0, 0, 0)\n\n\t\tfun isColorTheSame(@ColorInt a: Int, @ColorInt b: Int, tolerance: Int): Boolean {\n\t\t\treturn abs(a.red - b.red) <= tolerance &&\n\t\t\t\tabs(a.green - b.green) <= tolerance &&\n\t\t\t\tabs(a.blue - b.blue) <= tolerance &&\n\t\t\t\tabs(a.alpha - b.alpha) <= tolerance\n\t\t}\n\n\t\tprivate fun Int.isNotWhite() = !isColorTheSame(this, Color.WHITE, COLOR_TOLERANCE)\n\n\t\tprivate fun region(x: Int, y: Int) = Rect(x, y, x + BLOCK_SIZE, y + BLOCK_SIZE)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.net.Uri\nimport androidx.annotation.AnyThread\nimport androidx.annotation.CheckResult\nimport androidx.collection.LongSparseArray\nimport androidx.collection.set\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport coil3.BitmapImage\nimport coil3.Image\nimport coil3.ImageLoader\nimport coil3.memory.MemoryCache\nimport coil3.request.ImageRequest\nimport coil3.request.transformations\nimport coil3.toBitmap\nimport com.davemorrissey.labs.subscaleview.ImageSource\nimport dagger.hilt.android.ActivityRetainedLifecycle\nimport dagger.hilt.android.scopes.ActivityRetainedScoped\nimport kotlinx.coroutines.CoroutineExceptionHandler\nimport kotlinx.coroutines.Deferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.sync.withPermit\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okio.use\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.image.BitmapDecoderCompat\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.network.MangaHttpClient\nimport org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor\nimport org.koitharu.kotatsu.core.parser.CachingMangaRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.image.TrimTransformation\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP\nimport org.koitharu.kotatsu.core.util.ext.cancelChildrenAndJoin\nimport org.koitharu.kotatsu.core.util.ext.compressToPNG\nimport org.koitharu.kotatsu.core.util.ext.ensureRamAtLeast\nimport org.koitharu.kotatsu.core.util.ext.ensureSuccess\nimport org.koitharu.kotatsu.core.util.ext.getCompletionResultOrNull\nimport org.koitharu.kotatsu.core.util.ext.isFileUri\nimport org.koitharu.kotatsu.core.util.ext.isNotEmpty\nimport org.koitharu.kotatsu.core.util.ext.isPowerSaveMode\nimport org.koitharu.kotatsu.core.util.ext.isZipUri\nimport org.koitharu.kotatsu.core.util.ext.lifecycleScope\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.ramAvailable\nimport org.koitharu.kotatsu.core.util.ext.toMimeType\nimport org.koitharu.kotatsu.core.util.ext.use\nimport org.koitharu.kotatsu.core.util.ext.withProgress\nimport org.koitharu.kotatsu.core.util.progress.ProgressDeferred\nimport org.koitharu.kotatsu.download.ui.worker.DownloadSlowdownDispatcher\nimport org.koitharu.kotatsu.local.data.LocalStorageCache\nimport org.koitharu.kotatsu.local.data.PageCache\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.requireBody\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport java.io.File\nimport java.util.LinkedList\nimport java.util.concurrent.atomic.AtomicInteger\nimport java.util.zip.ZipFile\nimport javax.inject.Inject\nimport kotlin.coroutines.AbstractCoroutineContextElement\nimport kotlin.coroutines.CoroutineContext\n\n@ActivityRetainedScoped\nclass PageLoader @Inject constructor(\n\t@LocalizedAppContext private val context: Context,\n\tlifecycle: ActivityRetainedLifecycle,\n\t@MangaHttpClient private val okHttp: OkHttpClient,\n\t@PageCache private val cache: LocalStorageCache,\n\tprivate val coil: ImageLoader,\n\tprivate val settings: AppSettings,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val imageProxyInterceptor: ImageProxyInterceptor,\n\tprivate val downloadSlowdownDispatcher: DownloadSlowdownDispatcher,\n) {\n\n\tval loaderScope = lifecycle.lifecycleScope + InternalErrorHandler() + Dispatchers.Default\n\n\tprivate val tasks = LongSparseArray<ProgressDeferred<Uri, Float>>()\n\tprivate val semaphore = Semaphore(3)\n\tprivate val convertLock = Mutex()\n\tprivate val prefetchLock = Mutex()\n\n\t@Volatile\n\tprivate var repository: MangaRepository? = null\n\tprivate val prefetchQueue = LinkedList<MangaPage>()\n\tprivate val counter = AtomicInteger(0)\n\tprivate var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive\n\tprivate val edgeDetector = EdgeDetector(context)\n\n\tfun isPrefetchApplicable(): Boolean {\n\t\treturn repository is CachingMangaRepository\n\t\t\t&& settings.isPagesPreloadEnabled\n\t\t\t&& !context.isPowerSaveMode()\n\t\t\t&& !isLowRam()\n\t}\n\n\t@AnyThread\n\tfun prefetch(pages: List<ReaderPage>) = loaderScope.launch {\n\t\tprefetchLock.withLock {\n\t\t\tfor (page in pages.asReversed()) {\n\t\t\t\tif (tasks.containsKey(page.id)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprefetchQueue.offerFirst(page.toMangaPage())\n\t\t\t\tif (prefetchQueue.size > prefetchQueueLimit) {\n\t\t\t\t\tprefetchQueue.pollLast()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (counter.get() == 0) {\n\t\t\tonIdle()\n\t\t}\n\t}\n\n\tsuspend fun loadPreview(page: MangaPage): ImageSource? {\n\t\tval preview = page.preview\n\t\tif (preview.isNullOrEmpty()) {\n\t\t\treturn null\n\t\t}\n\t\tval request = ImageRequest.Builder(context)\n\t\t\t.data(preview)\n\t\t\t.mangaSourceExtra(page.source)\n\t\t\t.transformations(TrimTransformation())\n\t\t\t.build()\n\t\treturn coil.execute(request).image?.toImageSource()\n\t}\n\n\tfun peekPreviewSource(preview: String?): ImageSource? {\n\t\tif (preview.isNullOrEmpty()) {\n\t\t\treturn null\n\t\t}\n\t\tcoil.memoryCache?.let { cache ->\n\t\t\tval key = MemoryCache.Key(preview)\n\t\t\tcache[key]?.image?.let {\n\t\t\t\treturn if (it is BitmapImage) {\n\t\t\t\t\tImageSource.cachedBitmap(it.toBitmap())\n\t\t\t\t} else {\n\t\t\t\t\tImageSource.bitmap(it.toBitmap())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcoil.diskCache?.let { cache ->\n\t\t\tcache.openSnapshot(preview)?.use { snapshot ->\n\t\t\t\treturn ImageSource.file(snapshot.data.toFile())\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tfun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<Uri, Float> {\n\t\tvar task = tasks[page.id]?.takeIf { it.isValid() }\n\t\tif (force) {\n\t\t\ttask?.cancel()\n\t\t} else if (task?.isCancelled == false) {\n\t\t\treturn task\n\t\t}\n\t\ttask = loadPageAsyncImpl(page, skipCache = force, isPrefetch = false)\n\t\tsynchronized(tasks) {\n\t\t\ttasks[page.id] = task\n\t\t}\n\t\treturn task\n\t}\n\n\tsuspend fun loadPage(page: MangaPage, force: Boolean): Uri {\n\t\treturn loadPageAsync(page, force).await()\n\t}\n\n\t@CheckResult\n\tsuspend fun convertBimap(uri: Uri): Uri = convertLock.withLock {\n\t\tif (uri.isZipUri()) {\n\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\tZipFile(uri.schemeSpecificPart).use { zip ->\n\t\t\t\t\tval entry = zip.getEntry(uri.fragment)\n\t\t\t\t\tcontext.ensureRamAtLeast(entry.size * 2)\n\t\t\t\t\tzip.getInputStream(entry).use {\n\t\t\t\t\t\tBitmapDecoderCompat.decode(it, MimeTypes.getMimeTypeFromExtension(entry.name))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}.use { image ->\n\t\t\t\tcache.set(uri.toString(), image).toUri()\n\t\t\t}\n\t\t} else {\n\t\t\tval file = uri.toFile()\n\t\t\trunInterruptible(Dispatchers.IO) {\n\t\t\t\tcontext.ensureRamAtLeast(file.length() * 2)\n\t\t\t\tBitmapDecoderCompat.decode(file)\n\t\t\t}.use { image ->\n\t\t\t\timage.compressToPNG(file)\n\t\t\t}\n\t\t\turi\n\t\t}\n\t}\n\n\tsuspend fun getTrimmedBounds(uri: Uri): Rect? = runCatchingCancellable {\n\t\tedgeDetector.getBounds(ImageSource.uri(uri))\n\t}.onFailure { error ->\n\t\terror.printStackTraceDebug()\n\t}.getOrNull()\n\n\tsuspend fun getPageUrl(page: MangaPage): String {\n\t\treturn getRepository(page.source).getPageUrl(page)\n\t}\n\n\tsuspend fun invalidate(clearCache: Boolean) {\n\t\ttasks.clear()\n\t\tloaderScope.cancelChildrenAndJoin()\n\t\tif (clearCache) {\n\t\t\tcache.clear()\n\t\t}\n\t}\n\n\tprivate fun onIdle() = loaderScope.launch {\n\t\tprefetchLock.withLock {\n\t\t\twhile (prefetchQueue.isNotEmpty()) {\n\t\t\t\tval page = prefetchQueue.pollFirst() ?: return@launch\n\t\t\t\tsynchronized(tasks) {\n\t\t\t\t\ttasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun loadPageAsyncImpl(\n\t\tpage: MangaPage,\n\t\tskipCache: Boolean,\n\t\tisPrefetch: Boolean,\n\t): ProgressDeferred<Uri, Float> {\n\t\tval progress = MutableStateFlow(PROGRESS_UNDEFINED)\n\t\tval deferred = loaderScope.async {\n\t\t\tcounter.incrementAndGet()\n\t\t\ttry {\n\t\t\t\tloadPageImpl(\n\t\t\t\t\tpage = page,\n\t\t\t\t\tprogress = progress,\n\t\t\t\t\tisPrefetch = isPrefetch,\n\t\t\t\t\tskipCache = skipCache,\n\t\t\t\t)\n\t\t\t} finally {\n\t\t\t\tif (counter.decrementAndGet() == 0) {\n\t\t\t\t\tonIdle()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn ProgressDeferred(deferred, progress)\n\t}\n\n\t@Synchronized\n\tprivate fun getRepository(source: MangaSource): MangaRepository {\n\t\tval result = repository\n\t\treturn if (result != null && result.source == source) {\n\t\t\tresult\n\t\t} else {\n\t\t\tmangaRepositoryFactory.create(source).also { repository = it }\n\t\t}\n\t}\n\n\tprivate suspend fun loadPageImpl(\n\t\tpage: MangaPage,\n\t\tprogress: MutableStateFlow<Float>,\n\t\tisPrefetch: Boolean,\n\t\tskipCache: Boolean,\n\t): Uri = semaphore.withPermit {\n\t\tval pageUrl = getPageUrl(page)\n\t\tcheck(pageUrl.isNotBlank()) { \"Cannot obtain full image url for $page\" }\n\t\tif (!skipCache) {\n\t\t\tcache.get(pageUrl)?.let { return it.toUri() }\n\t\t}\n\t\tval uri = pageUrl.toUri()\n\t\treturn when {\n\t\t\turi.isZipUri() -> if (uri.scheme == URI_SCHEME_ZIP) {\n\t\t\t\turi\n\t\t\t} else { // legacy uri\n\t\t\t\turi.buildUpon().scheme(URI_SCHEME_ZIP).build()\n\t\t\t}\n\n\t\t\turi.isFileUri() -> uri\n\t\t\telse -> {\n\t\t\t\tif (isPrefetch) {\n\t\t\t\t\tdownloadSlowdownDispatcher.delay(page.source)\n\t\t\t\t}\n\t\t\t\tval request = createPageRequest(pageUrl, page.source)\n\t\t\t\timageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->\n\t\t\t\t\tresponse.requireBody().withProgress(progress).use {\n\t\t\t\t\t\tcache.set(pageUrl, it.source(), it.contentType()?.toMimeType())\n\t\t\t\t\t}\n\t\t\t\t}.toUri()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun isLowRam(): Boolean {\n\t\treturn context.ramAvailable <= FileSize.MEGABYTES.convert(PREFETCH_MIN_RAM_MB, FileSize.BYTES)\n\t}\n\n\tprivate fun Image.toImageSource(): ImageSource = if (this is BitmapImage) {\n\t\tImageSource.cachedBitmap(toBitmap())\n\t} else {\n\t\tImageSource.bitmap(toBitmap())\n\t}\n\n\tprivate fun Deferred<Uri>.isValid(): Boolean {\n\t\treturn getCompletionResultOrNull()?.map { uri ->\n\t\t\turi.exists() && uri.isTargetNotEmpty()\n\t\t}?.getOrDefault(false) != false\n\t}\n\n\tprivate class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),\n\t\tCoroutineExceptionHandler {\n\n\t\toverride fun handleException(context: CoroutineContext, exception: Throwable) {\n\t\t\texception.printStackTraceDebug()\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val PROGRESS_UNDEFINED = -1f\n\t\tprivate const val PREFETCH_LIMIT_DEFAULT = 6\n\t\tprivate const val PREFETCH_MIN_RAM_MB = 80L\n\n\t\tfun createPageRequest(pageUrl: String, mangaSource: MangaSource) = Request.Builder()\n\t\t\t.url(pageUrl)\n\t\t\t.get()\n\t\t\t.header(CommonHeaders.ACCEPT, \"image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8\")\n\t\t\t.cacheControl(CommonHeaders.CACHE_CONTROL_NO_STORE)\n\t\t\t.tag(MangaSource::class.java, mangaSource)\n\t\t\t.build()\n\n\n\t\t@Blocking\n\t\tprivate fun Uri.exists(): Boolean = when {\n\t\t\tisFileUri() -> toFile().exists()\n\t\t\tisZipUri() -> {\n\t\t\t\tval file = File(requireNotNull(schemeSpecificPart))\n\t\t\t\tfile.exists() && ZipFile(file).use { it.getEntry(fragment) != null }\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\n\t\t@Blocking\n\t\tprivate fun Uri.isTargetNotEmpty(): Boolean = when {\n\t\t\tisFileUri() -> toFile().isNotEmpty()\n\t\t\tisZipUri() -> {\n\t\t\t\tval file = File(requireNotNull(schemeSpecificPart))\n\t\t\t\tfile.exists() && ZipFile(file).use { (it.getEntry(fragment)?.size ?: 0L) != 0L }\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/ReaderColorFilter.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nimport android.content.res.ColorStateList\nimport android.graphics.Color\nimport android.graphics.ColorMatrix\nimport android.graphics.ColorMatrixColorFilter\n\ndata class ReaderColorFilter(\n\tval brightness: Float,\n\tval contrast: Float,\n\tval isInverted: Boolean,\n\tval isGrayscale: Boolean,\n\tval isBookBackground: Boolean,\n) {\n\n\tval isEmpty: Boolean\n\t\tget() = !isGrayscale && !isInverted && !isBookBackground && brightness == 0f && contrast == 0f\n\n\tfun toColorFilter(): ColorMatrixColorFilter {\n\t\tval cm = ColorMatrix()\n\t\tif (isGrayscale) {\n\t\t\tcm.grayscale()\n\t\t}\n\t\tif (isInverted) {\n\t\t\tcm.inverted()\n\t\t}\n\t\tcm.setBrightness(brightness)\n\t\tcm.setContrast(contrast)\n\t\tif (isBookBackground) {\n\t\t\tcm.addBookEffect()\n\t\t}\n\t\treturn ColorMatrixColorFilter(cm)\n\t}\n\n\tfun getBackgroundTint(): ColorStateList? = if (isBookBackground) {\n\t\tval color = Color.rgb(255, 255, (255 * BOOK_BLUE_FACTOR).toInt())\n\t\tColorStateList.valueOf(color)\n\t} else {\n\t\tnull\n\t}\n\n\tprivate fun ColorMatrix.setBrightness(brightness: Float) {\n\t\tval scale = brightness + 1f\n\t\tval matrix = ColorMatrix()\n\t\tmatrix.setScale(scale, scale, scale, 1f)\n\t\tpostConcat(matrix)\n\t}\n\n\tprivate fun ColorMatrix.setContrast(contrast: Float) {\n\t\tval scale = contrast + 1f\n\t\tval translate = (-.5f * scale + .5f) * 255f\n\t\tval array = floatArrayOf(\n\t\t\tscale, 0f, 0f, 0f, translate,\n\t\t\t0f, scale, 0f, 0f, translate,\n\t\t\t0f, 0f, scale, 0f, translate,\n\t\t\t0f, 0f, 0f, 1f, 0f,\n\t\t)\n\t\tval matrix = ColorMatrix(array)\n\t\tpostConcat(matrix)\n\t}\n\n\tprivate fun ColorMatrix.inverted() {\n\t\tval matrix = floatArrayOf(\n\t\t\t-1.0f, 0.0f, 0.0f, 1.0f, 1.0f,\n\t\t\t0.0f, -1.0f, 0.0f, 1.0f, 1.0f,\n\t\t\t0.0f, 0.0f, -1.0f, 1.0f, 1.0f,\n\t\t\t0.0f, 0.0f, 0.0f, 1.0f, 0.0f,\n\t\t)\n\t\tpostConcat(ColorMatrix(matrix))\n\t}\n\n\tprivate fun ColorMatrix.grayscale() {\n\t\tsetSaturation(0f)\n\t}\n\n\tprivate fun ColorMatrix.addBookEffect() {\n\t\tval removeBlueMatrix = floatArrayOf(\n\t\t\t1f, 0f, 0f, 0f, 0f,\n\t\t\t0f, 1f, 0f, 0f, 0f,\n\t\t\t0f, 0f, BOOK_BLUE_FACTOR, 0f, 0f,\n\t\t\t0f, 0f, 0f, 1f, 0f,\n\t\t)\n\t\tpostConcat(ColorMatrix(removeBlueMatrix))\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val BOOK_BLUE_FACTOR = 0.92f\n\n\t\tval EMPTY = ReaderColorFilter(\n\t\t\tbrightness = 0.0f,\n\t\t\tcontrast = 0.0f,\n\t\t\tisInverted = false,\n\t\t\tisGrayscale = false,\n\t\t\tisBookBackground = false,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/TapGridArea.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nenum class TapGridArea {\n\n\tTOP_LEFT,\n\tTOP_CENTER,\n\tTOP_RIGHT,\n\tCENTER_LEFT,\n\tCENTER,\n\tCENTER_RIGHT,\n\tBOTTOM_LEFT,\n\tBOTTOM_CENTER,\n\tBOTTOM_RIGHT;\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageLabelFormatter.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport com.google.android.material.slider.LabelFormatter\nimport org.koitharu.kotatsu.parsers.util.format\n\nclass PageLabelFormatter : LabelFormatter {\n\n\toverride fun getFormattedValue(value: Float): String {\n\t\treturn (value + 1).format(0)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveContract.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.DocumentsContract\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport java.io.File\n\nclass PageSaveContract : ActivityResultContracts.CreateDocument(\"image/*\") {\n\n\toverride fun createIntent(context: Context, input: String): Intent {\n\t\tval intent = super.createIntent(context, input.substringAfterLast(File.separatorChar))\n\t\tintent.type = MimeTypes.getMimeTypeFromExtension(input)?.toString() ?: \"image/*\"\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\tval defaultUri = input.toUriOrNull()?.run {\n\t\t\t\tpath?.let { p ->\n\t\t\t\t\tbuildUpon().path(p.substringBeforeLast('/')).build()\n\t\t\t\t}\n\t\t\t}\n\t\t\tintent.putExtra(\n\t\t\t\tDocumentsContract.EXTRA_INITIAL_URI,\n\t\t\t\tdefaultUri ?: Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toUri(),\n\t\t\t)\n\t\t}\n\t\treturn intent\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.ActivityResultCaller\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport androidx.documentfile.provider.DocumentFile\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport kotlinx.coroutines.CancellableContinuation\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runInterruptible\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.withContext\nimport okio.FileSystem\nimport okio.IOException\nimport okio.Path.Companion.toPath\nimport okio.Source\nimport okio.buffer\nimport okio.openZip\nimport okio.sink\nimport okio.source\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.image.BitmapDecoderCompat\nimport org.koitharu.kotatsu.core.os.OpenDocumentTreeHelper\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.isFileUri\nimport org.koitharu.kotatsu.core.util.ext.isZipUri\nimport org.koitharu.kotatsu.core.util.ext.toFileNameSafe\nimport org.koitharu.kotatsu.core.util.ext.toFileOrNull\nimport org.koitharu.kotatsu.core.util.ext.writeAllCancellable\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport java.io.File\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport javax.inject.Provider\nimport kotlin.coroutines.resume\n\nclass PageSaveHelper @AssistedInject constructor(\n\t@Assisted activityResultCaller: ActivityResultCaller,\n\t@LocalizedAppContext private val context: Context,\n\tprivate val settings: AppSettings,\n\tprivate val pageLoaderProvider: Provider<PageLoader>,\n) : ActivityResultCallback<Uri?> {\n\n\tprivate val savePageRequest = activityResultCaller.registerForActivityResult(PageSaveContract(), this)\n\tprivate val pickDirectoryRequest = OpenDocumentTreeHelper(activityResultCaller, this)\n\n\tprivate var continuation: CancellableContinuation<Uri>? = null\n\n\toverride fun onActivityResult(result: Uri?) {\n\t\tcontinuation?.also { cont ->\n\t\t\tif (result != null) {\n\t\t\t\tcont.resume(result)\n\t\t\t} else {\n\t\t\t\tcont.cancel()\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun save(tasks: Collection<Task>): Collection<Uri> = when (tasks.size) {\n\t\t0 -> emptySet()\n\t\t1 -> setOf(saveImpl(tasks.first()))\n\t\telse -> saveImpl(tasks)\n\t}\n\n\tsuspend fun saveToTempFile(task: Task): File {\n\t\tval pageLoader = getPageLoader()\n\t\tval pageUrl = pageLoader.getPageUrl(task.page).toUri()\n\t\tval pageUri = pageLoader.loadPage(task.page, force = false)\n\t\tval proposedName = task.getFileBaseName() + \".\" + getPageExtension(pageUrl, pageUri)\n\t\tval destination = File(checkNotNull(context.getExternalFilesDir(TEMP_DIR)), proposedName)\n\t\tcopyImpl(pageUri, destination.toUri())\n\t\treturn destination\n\t}\n\n\tprivate suspend fun saveImpl(task: Task): Uri {\n\t\tval pageLoader = getPageLoader()\n\t\tval pageUrl = pageLoader.getPageUrl(task.page).toUri()\n\t\tval pageUri = pageLoader.loadPage(task.page, force = false)\n\t\tval proposedName = task.getFileBaseName() + \".\" + getPageExtension(pageUrl, pageUri)\n\t\tval destination = getDefaultFileUri(proposedName)?.uri ?: run {\n\t\t\tval defaultUri = settings.getPagesSaveDir(context)?.uri?.buildUpon()?.appendPath(proposedName)?.toString()\n\t\t\tsavePageRequest.launchAndAwait(defaultUri ?: proposedName)\n\t\t}\n\t\tcopyImpl(pageUri, destination)\n\t\treturn destination\n\t}\n\n\tprivate suspend fun saveImpl(tasks: Collection<Task>): Collection<Uri> {\n\t\tval pageLoader = getPageLoader()\n\t\tval destinationDir = getDefaultFileUri(null) ?: run {\n\t\t\tval defaultUri = settings.getPagesSaveDir(context)?.uri\n\t\t\tDocumentFile.fromTreeUri(context, pickDirectoryRequest.launchAndAwait(defaultUri))\n\t\t} ?: throw IOException(\"Cannot get destination directory\")\n\n\t\tval result = ArrayList<Uri>(tasks.size)\n\t\tfor (task in tasks) {\n\t\t\tval pageUrl = pageLoader.getPageUrl(task.page).toUri()\n\t\t\tval pageUri = pageLoader.loadPage(task.page, force = false)\n\t\t\tval proposedName = task.getFileBaseName()\n\t\t\tval ext = getPageExtension(pageUrl, pageUri)\n\t\t\tval mime = requireNotNull(MimeTypes.getMimeTypeFromExtension(\"_.$ext\")) {\n\t\t\t\t\"Unknown type of $proposedName\"\n\t\t\t}\n\t\t\tval destination = destinationDir.createFile(mime.toString(), proposedName)\n\t\t\tcopyImpl(pageUri, destination?.uri ?: throw IOException(\"Cannot create destination file\"))\n\t\t\tresult.add(destination.uri)\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate suspend fun getPageExtension(url: Uri, fileUri: Uri): String {\n\t\tval name = requireNotNull(\n\t\t\tif (url.isZipUri()) {\n\t\t\t\turl.fragment?.substringAfterLast(File.separatorChar)\n\t\t\t} else {\n\t\t\t\turl.lastPathSegment\n\t\t\t},\n\t\t) { \"Invalid page url: $url\" }\n\t\tvar extension = name.substringAfterLast('.', \"\")\n\t\tif (extension.length !in 2..4) {\n\t\t\textension = fileUri.toFileOrNull()?.let { file -> getImageExtension(file) } ?: EXTENSION_FALLBACK\n\t\t}\n\t\treturn extension\n\t}\n\n\tprivate suspend fun <I> ActivityResultLauncher<I>.launchAndAwait(input: I): Uri {\n\t\tcontinuation?.cancel()\n\t\treturn withContext(Dispatchers.Main) {\n\t\t\ttry {\n\t\t\t\tsuspendCancellableCoroutine { cont ->\n\t\t\t\t\tcontinuation = cont\n\t\t\t\t\tlaunch(input)\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tcontinuation = null\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun getPageLoader() = withContext(Dispatchers.Main.immediate) {\n\t\tpageLoaderProvider.get()\n\t}\n\n\tprivate fun getDefaultFileUri(proposedName: String?): DocumentFile? {\n\t\tif (settings.isPagesSavingAskEnabled) {\n\t\t\treturn null\n\t\t}\n\t\tval dir = settings.getPagesSaveDir(context) ?: return null\n\t\tif (proposedName == null) {\n\t\t\treturn dir\n\t\t} else {\n\t\t\tval mime = MimeTypes.getMimeTypeFromExtension(proposedName)?.toString() ?: return null\n\t\t\treturn dir.createFile(mime, proposedName.substringBeforeLast('.'))\n\t\t}\n\t}\n\n\tprivate fun getSource(uri: Uri): Source = when {\n\t\turi.isFileUri() -> uri.toFile().source()\n\t\turi.isZipUri() -> FileSystem.SYSTEM.openZip(uri.schemeSpecificPart.toPath())\n\t\t\t.source(requireNotNull(uri.fragment).toPath())\n\n\t\telse -> throw IllegalArgumentException(\"Bad uri $uri: unsupported scheme\")\n\t}\n\n\tprivate suspend fun copyImpl(source: Uri, destination: Uri) = withContext(Dispatchers.IO) {\n\t\trunInterruptible {\n\t\t\tcontext.contentResolver.openOutputStream(destination) ?: throw IOException(\"Output stream is null\")\n\t\t}.sink().buffer().use { sink ->\n\t\t\tgetSource(source).use { input ->\n\t\t\t\tsink.writeAllCancellable(input)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun getImageExtension(file: File): String? = runInterruptible(Dispatchers.IO) {\n\t\tMimeTypes.getExtension(BitmapDecoderCompat.probeMimeType(file))\n\t}\n\n\tdata class Task(\n\t\tval manga: Manga,\n\t\tval chapterId: Long,\n\t\tval pageNumber: Int,\n\t\tval page: MangaPage,\n\t) {\n\n\t\tfun getFileBaseName() = buildString {\n\t\t\tappend(manga.title.toFileNameSafe().take(MAX_BASENAME_LENGTH))\n\t\t\tmanga.findChapterById(chapterId)?.let { chapter ->\n\t\t\t\tappend('-')\n\t\t\t\tappend(chapter.number)\n\t\t\t}\n\t\t\tappend('-')\n\t\t\tappend(pageNumber)\n\t\t\tappend('_')\n\t\t\tappend(SimpleDateFormat(\"yyyy-MM-dd_HHmm\").format(Date()))\n\t\t}\n\t}\n\n\t@AssistedFactory\n\tinterface Factory {\n\n\t\tfun create(activityResultCaller: ActivityResultCaller): PageSaveHelper\n\t}\n\n\tprivate companion object {\n\n\t\tprivate const val MAX_BASENAME_LENGTH = 12\n\t\tprivate const val EXTENSION_FALLBACK = \"png\"\n\t\tprivate const val TEMP_DIR = \"pages\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActionsView.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.database.ContentObserver\nimport android.provider.Settings\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.Button\nimport android.widget.FrameLayout\nimport android.widget.LinearLayout\nimport androidx.annotation.AttrRes\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport com.google.android.material.slider.Slider\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderControl\nimport org.koitharu.kotatsu.core.util.ext.hasVisibleChildren\nimport org.koitharu.kotatsu.core.util.ext.isRtl\nimport org.koitharu.kotatsu.core.util.ext.setContentDescriptionAndTooltip\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.core.util.ext.setValueRounded\nimport org.koitharu.kotatsu.databinding.LayoutReaderActionsBinding\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_PAGES\nimport org.koitharu.kotatsu.reader.ui.ReaderControlDelegate.OnInteractionListener\nimport javax.inject.Inject\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nclass ReaderActionsView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : LinearLayout(context, attrs, defStyleAttr),\n\tView.OnClickListener,\n\tSharedPreferences.OnSharedPreferenceChangeListener,\n\tSlider.OnChangeListener,\n\tSlider.OnSliderTouchListener, View.OnLongClickListener {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate val binding = LayoutReaderActionsBinding.inflate(LayoutInflater.from(context), this)\n\tprivate val rotationObserver = object : ContentObserver(handler) {\n\t\toverride fun onChange(selfChange: Boolean) {\n\t\t\tpost {\n\t\t\t\tupdateRotationButton()\n\t\t\t}\n\t\t}\n\t}\n\tprivate var isSliderChanged = false\n\tprivate var isSliderTracking = false\n\n\tvar isSliderEnabled: Boolean\n\t\tget() = binding.slider.isEnabled\n\t\tset(value) {\n\t\t\tbinding.slider.isEnabled = value\n\t\t\tbinding.slider.setThumbVisible(value)\n\t\t}\n\n\tvar isNextEnabled: Boolean\n\t\tget() = binding.buttonNext.isEnabled\n\t\tset(value) {\n\t\t\tbinding.buttonNext.isEnabled = value\n\t\t}\n\n\tvar isPrevEnabled: Boolean\n\t\tget() = binding.buttonPrev.isEnabled\n\t\tset(value) {\n\t\t\tbinding.buttonPrev.isEnabled = value\n\t\t}\n\n\tvar isBookmarkAdded: Boolean = false\n\t\tset(value) {\n\t\t\tif (field != value) {\n\t\t\t\tfield = value\n\t\t\t\tupdateBookmarkButton()\n\t\t\t}\n\t\t}\n\n\tvar listener: OnInteractionListener? = null\n\n\tinit {\n\t\torientation = HORIZONTAL\n\t\tgravity = Gravity.CENTER_VERTICAL\n\t\tbinding.buttonNext.initAction()\n\t\tbinding.buttonPrev.initAction()\n\t\tbinding.buttonSave.initAction()\n\t\tbinding.buttonOptions.initAction()\n\t\tbinding.buttonScreenRotation.initAction()\n\t\tbinding.buttonPagesThumbs.initAction()\n\t\tbinding.buttonTimer.initAction()\n\t\tbinding.buttonBookmark.initAction()\n\t\tbinding.slider.setLabelFormatter(PageLabelFormatter())\n\t\tbinding.slider.addOnChangeListener(this)\n\t\tbinding.slider.addOnSliderTouchListener(this)\n\t\tupdateControlsVisibility()\n\t\tupdatePagesSheetButton()\n\t\tupdateRotationButton()\n\t}\n\n\toverride fun onAttachedToWindow() {\n\t\tsuper.onAttachedToWindow()\n\t\tsettings.subscribe(this)\n\t\tcontext.contentResolver.registerContentObserver(\n\t\t\tSettings.System.CONTENT_URI, true, rotationObserver,\n\t\t)\n\t}\n\n\toverride fun onDetachedFromWindow() {\n\t\tsettings.unsubscribe(this)\n\t\tcontext.contentResolver.unregisterContentObserver(rotationObserver)\n\t\tsuper.onDetachedFromWindow()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_prev -> listener?.switchChapterBy(-1)\n\t\t\tR.id.button_next -> listener?.switchChapterBy(1)\n\t\t\tR.id.button_save -> listener?.onSavePageClick()\n\t\t\tR.id.button_timer -> listener?.onScrollTimerClick(isLongClick = false)\n\t\t\tR.id.button_pages_thumbs -> AppRouter.from(this)?.showChapterPagesSheet()\n\t\t\tR.id.button_screen_rotation -> listener?.toggleScreenOrientation()\n\t\t\tR.id.button_options -> listener?.openMenu()\n\t\t\tR.id.button_bookmark -> listener?.onBookmarkClick()\n\t\t}\n\t}\n\n\toverride fun onLongClick(v: View): Boolean = when (v.id) {\n\t\tR.id.button_bookmark -> AppRouter.from(this)\n\t\t\t?.showChapterPagesSheet(ChaptersPagesSheet.TAB_BOOKMARKS)\n\n\t\tR.id.button_timer -> listener?.onScrollTimerClick(isLongClick = true)\n\t\tR.id.button_options -> AppRouter.from(this)?.openReaderSettings()\n\t\telse -> null\n\t} != null\n\n\toverride fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {\n\t\tif (fromUser) {\n\t\t\tif (isSliderTracking) {\n\t\t\t\tisSliderChanged = true\n\t\t\t} else {\n\t\t\t\tlistener?.switchPageTo(value.toInt())\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onStartTrackingTouch(slider: Slider) {\n\t\tif (!isSliderTracking) {\n\t\t\tisSliderChanged = false\n\t\t\tisSliderTracking = true\n\t\t}\n\t}\n\n\toverride fun onStopTrackingTouch(slider: Slider) {\n\t\tisSliderTracking = false\n\t\tif (isSliderChanged) {\n\t\t\tlistener?.switchPageTo(slider.value.toInt())\n\t\t}\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_READER_CONTROLS -> updateControlsVisibility()\n\t\t\tAppSettings.KEY_PAGES_TAB,\n\t\t\tAppSettings.KEY_DETAILS_TAB,\n\t\t\tAppSettings.KEY_DETAILS_LAST_TAB -> updatePagesSheetButton()\n\t\t}\n\t}\n\n\tfun setSliderValue(value: Int, max: Int) {\n\t\tbinding.slider.valueTo = max.toFloat()\n\t\tbinding.slider.setValueRounded(value.toFloat())\n\t}\n\n\tfun setSliderReversed(reversed: Boolean) {\n\t\tbinding.slider.isRtl = reversed != isRtl\n\t}\n\n\tfun setTimerActive(isActive: Boolean) {\n\t\tbinding.buttonTimer.setIconResource(\n\t\t\tif (isActive) R.drawable.ic_timer_run else R.drawable.ic_timer,\n\t\t)\n\t}\n\n\tprivate fun updateControlsVisibility() {\n\t\tval controls = settings.readerControls\n\t\tbinding.buttonPrev.isVisible = ReaderControl.PREV_CHAPTER in controls\n\t\tbinding.buttonNext.isVisible = ReaderControl.NEXT_CHAPTER in controls\n\t\tbinding.buttonPagesThumbs.isVisible = ReaderControl.PAGES_SHEET in controls\n\t\tbinding.buttonScreenRotation.isVisible = ReaderControl.SCREEN_ROTATION in controls\n\t\tbinding.buttonSave.isVisible = ReaderControl.SAVE_PAGE in controls\n\t\tbinding.buttonTimer.isVisible = ReaderControl.TIMER in controls\n\t\tbinding.buttonBookmark.isVisible = ReaderControl.BOOKMARK in controls\n\t\tbinding.slider.isVisible = ReaderControl.SLIDER in controls\n\t\tadjustLayoutParams()\n\t}\n\n\tprivate fun updatePagesSheetButton() {\n\t\tval isPagesMode = settings.defaultDetailsTab == TAB_PAGES\n\t\tval button = binding.buttonPagesThumbs\n\t\tbutton.setIconResource(\n\t\t\tif (isPagesMode) R.drawable.ic_grid else R.drawable.ic_list,\n\t\t)\n\t\tbutton.setContentDescriptionAndTooltip(\n\t\t\tif (isPagesMode) R.string.pages else R.string.chapters,\n\t\t)\n\t}\n\n\tprivate fun updateBookmarkButton() {\n\t\tval button = binding.buttonBookmark\n\t\tbutton.setIconResource(\n\t\t\tif (isBookmarkAdded) R.drawable.ic_bookmark_added else R.drawable.ic_bookmark,\n\t\t)\n\t\tbutton.setContentDescriptionAndTooltip(\n\t\t\tif (isBookmarkAdded) R.string.bookmark_remove else R.string.bookmark_add,\n\t\t)\n\t}\n\n\tprivate fun adjustLayoutParams() {\n\t\tval isSliderVisible = binding.slider.isVisible\n\t\trepeat(childCount) { i ->\n\t\t\tval child = getChildAt(i)\n\t\t\tif (child is FrameLayout) {\n\t\t\t\tchild.isVisible = child.hasVisibleChildren\n\t\t\t\tchild.updateLayoutParams<LayoutParams> {\n\t\t\t\t\twidth = if (isSliderVisible) LayoutParams.WRAP_CONTENT else 0\n\t\t\t\t\tweight = if (isSliderVisible) 0f else 1f\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun updateRotationButton() {\n\t\tval button = binding.buttonScreenRotation\n\t\twhen {\n\t\t\t!button.isVisible -> return\n\t\t\tisAutoRotationEnabled() -> {\n\t\t\t\tbutton.setContentDescriptionAndTooltip(R.string.lock_screen_rotation)\n\t\t\t\tbutton.setIconResource(R.drawable.ic_screen_rotation_lock)\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tbutton.setContentDescriptionAndTooltip(R.string.rotate_screen)\n\t\t\t\tbutton.setIconResource(R.drawable.ic_screen_rotation)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun Button.initAction() {\n\t\tsetOnClickListener(this@ReaderActionsView)\n\t\tsetOnLongClickListener(this@ReaderActionsView)\n\t\tsetTooltipCompat(contentDescription)\n\t}\n\n\tprivate fun isAutoRotationEnabled(): Boolean = Settings.System.getInt(\n\t\tcontext.contentResolver,\n\t\tSettings.System.ACCELEROMETER_ROTATION,\n\t\t0,\n\t) == 1\n\n\tprivate fun Slider.setThumbVisible(visible: Boolean) {\n\t\tthumbWidth = if (visible) {\n\t\t\tresources.getDimensionPixelSize(materialR.dimen.m3_comp_slider_active_handle_width)\n\t\t} else {\n\t\t\t0\n\t\t}\n\t\tthumbHeight = if (visible) {\n\t\t\tresources.getDimensionPixelSize(materialR.dimen.m3_comp_slider_active_handle_height)\n\t\t} else {\n\t\t\t0\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.app.assist.AssistContent\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.KeyEvent\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport androidx.activity.viewModels\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.graphics.Insets\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.transition.Fade\nimport androidx.transition.Slide\nimport androidx.transition.TransitionManager\nimport androidx.transition.TransitionSet\nimport androidx.window.layout.FoldingFeature\nimport androidx.window.layout.WindowInfoTracker\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.ui.BaseFullscreenActivity\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.dialog.setCheckbox\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.ui.widgets.ZoomControl\nimport org.koitharu.kotatsu.core.util.IdlingDetector\nimport org.koitharu.kotatsu.core.util.ext.getThemeDimensionPixelOffset\nimport org.koitharu.kotatsu.core.util.ext.hasGlobalPoint\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.postDelayed\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.core.util.ext.zipWithPrevious\nimport org.koitharu.kotatsu.databinding.ActivityReaderBinding\nimport org.koitharu.kotatsu.details.ui.pager.pages.PagesSavedObserver\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.reader.data.TapGridSettings\nimport org.koitharu.kotatsu.reader.domain.TapGridArea\nimport org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderUiState\nimport org.koitharu.kotatsu.reader.ui.tapgrid.TapGridDispatcher\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\nclass ReaderActivity :\n    BaseFullscreenActivity<ActivityReaderBinding>(),\n    TapGridDispatcher.OnGridTouchListener,\n    ReaderConfigSheet.Callback,\n    ReaderControlDelegate.OnInteractionListener,\n    ReaderNavigationCallback,\n    IdlingDetector.Callback,\n    ZoomControl.ZoomControlListener,\n    View.OnClickListener,\n    ScrollTimerControlView.OnVisibilityChangeListener {\n\n    @Inject\n    lateinit var settings: AppSettings\n\n    @Inject\n    lateinit var tapGridSettings: TapGridSettings\n\n    @Inject\n    lateinit var pageSaveHelperFactory: PageSaveHelper.Factory\n\n    @Inject\n    lateinit var scrollTimerFactory: ScrollTimer.Factory\n\n    @Inject\n    lateinit var screenOrientationHelper: ScreenOrientationHelper\n\n    private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this)\n\n    private val viewModel: ReaderViewModel by viewModels()\n\n    override val readerMode: ReaderMode?\n        get() = readerManager.currentMode\n\n    private lateinit var scrollTimer: ScrollTimer\n    private lateinit var pageSaveHelper: PageSaveHelper\n    private lateinit var touchHelper: TapGridDispatcher\n    private lateinit var controlDelegate: ReaderControlDelegate\n    private var gestureInsets: Insets = Insets.NONE\n    private lateinit var readerManager: ReaderManager\n    private val hideUiRunnable = Runnable { setUiIsVisible(false) }\n\n    // Tracks whether the foldable device is in an unfolded state (half-opened or flat)\n    private var isFoldUnfolded: Boolean = false\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(ActivityReaderBinding.inflate(layoutInflater))\n        readerManager = ReaderManager(supportFragmentManager, viewBinding.container, settings)\n        setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n        touchHelper = TapGridDispatcher(viewBinding.root, this)\n        scrollTimer = scrollTimerFactory.create(resources, this, this)\n        pageSaveHelper = pageSaveHelperFactory.create(this)\n        controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)\n        viewBinding.zoomControl.listener = this\n        viewBinding.actionsView.listener = this\n        viewBinding.buttonTimer?.setOnClickListener(this)\n        idlingDetector.bindToLifecycle(this)\n        screenOrientationHelper.applySettings()\n        viewModel.isBookmarkAdded.observe(this) { viewBinding.actionsView.isBookmarkAdded = it }\n        scrollTimer.isActive.observe(this) {\n            updateScrollTimerButton()\n            viewBinding.actionsView.setTimerActive(it)\n        }\n        viewBinding.timerControl.onVisibilityChangeListener = this\n        viewBinding.timerControl.attach(scrollTimer, this)\n        if (resources.getBoolean(R.bool.is_tablet)) {\n            viewBinding.timerControl.updateLayoutParams<CoordinatorLayout.LayoutParams> {\n                topMargin = marginEnd + getThemeDimensionPixelOffset(appcompatR.attr.actionBarSize)\n            }\n        }\n\n        viewModel.onLoadingError.observeEvent(\n            this,\n            DialogErrorObserver(\n                host = viewBinding.container,\n                fragment = null,\n                resolver = exceptionResolver,\n                onResolved = { isResolved ->\n                    if (isResolved) {\n                        viewModel.reload()\n                    } else if (viewModel.content.value.pages.isEmpty()) {\n                        dispatchNavigateUp()\n                    }\n                },\n            ),\n        )\n        viewModel.onError.observeEvent(\n            this,\n            SnackbarErrorObserver(\n                host = viewBinding.container,\n                fragment = null,\n                resolver = exceptionResolver,\n                onResolved = null,\n            ),\n        )\n        viewModel.readerMode.observe(this, Lifecycle.State.STARTED, this::onInitReader)\n        viewModel.onPageSaved.observeEvent(this, PagesSavedObserver(viewBinding.container))\n        viewModel.uiState.zipWithPrevious().observe(this, this::onUiStateChanged)\n        combine(\n            viewModel.isLoading,\n            viewModel.content.map { it.pages.isNotEmpty() }.distinctUntilChanged(),\n            ::Pair,\n        ).flowOn(Dispatchers.Default)\n            .observe(this, this::onLoadingStateChanged)\n        viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn)\n        viewModel.isInfoBarTransparent.observe(this) { viewBinding.infoBar.drawBackground = !it }\n        viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)\n        viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this))\n        viewModel.onAskNsfwIncognito.observeEvent(this) { askForIncognitoMode() }\n        viewModel.onShowToast.observeEvent(this) { msgId ->\n            Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT)\n                .setAnchorView(viewBinding.toolbarDocked)\n                .show()\n        }\n        viewModel.readerSettingsProducer.observe(this) {\n            viewBinding.infoBar.applyColorScheme(isBlackOnWhite = it.background.isLight(this))\n        }\n        viewModel.isZoomControlsEnabled.observe(this) {\n            viewBinding.zoomControl.isVisible = it\n        }\n        addMenuProvider(ReaderMenuProvider(viewModel))\n\n        observeWindowLayout()\n\n        // Apply initial double-mode considering foldable setting\n        applyDoubleModeAuto()\n    }\n\n    override fun getParentActivityIntent(): Intent? {\n        val manga = viewModel.getMangaOrNull() ?: return null\n        return AppRouter.detailsIntent(this, manga)\n    }\n\n    override fun onUserInteraction() {\n        super.onUserInteraction()\n        if (!viewBinding.timerControl.isVisible) {\n            scrollTimer.onUserInteraction()\n        }\n        idlingDetector.onUserInteraction()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        viewModel.onPause()\n    }\n\n    override fun onStop() {\n        super.onStop()\n        viewModel.onStop()\n    }\n\n    override fun onProvideAssistContent(outContent: AssistContent) {\n        super.onProvideAssistContent(outContent)\n        viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }\n    }\n\n    override fun isNsfwContent(): Flow<Boolean> = viewModel.isMangaNsfw\n\n    override fun onIdle() {\n        viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())\n        viewModel.onIdle()\n    }\n\n    override fun onVisibilityChanged(v: View, visibility: Int) {\n        updateScrollTimerButton()\n    }\n\n    override fun onZoomIn() {\n        readerManager.currentReader?.onZoomIn()\n    }\n\n    override fun onZoomOut() {\n        readerManager.currentReader?.onZoomOut()\n    }\n\n    override fun onClick(v: View) {\n        when (v.id) {\n            R.id.button_timer -> onScrollTimerClick(isLongClick = false)\n        }\n    }\n\n    private fun onInitReader(mode: ReaderMode?) {\n        if (mode == null) {\n            return\n        }\n        if (readerManager.currentMode != mode) {\n            readerManager.replace(mode)\n        }\n        if (viewBinding.appbarTop.isVisible) {\n            lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable)\n        }\n        viewBinding.actionsView.setSliderReversed(mode == ReaderMode.REVERSED)\n        viewBinding.timerControl.onReaderModeChanged(mode)\n    }\n\n    private fun onLoadingStateChanged(value: Pair<Boolean, Boolean>) {\n        val (isLoading, hasPages) = value\n        val showLoadingLayout = isLoading && !hasPages\n        if (viewBinding.layoutLoading.isVisible != showLoadingLayout) {\n            val transition = Fade().addTarget(viewBinding.layoutLoading)\n            TransitionManager.beginDelayedTransition(viewBinding.root, transition)\n            viewBinding.layoutLoading.isVisible = showLoadingLayout\n        }\n        if (isLoading && hasPages) {\n            viewBinding.toastView.show(R.string.loading_)\n        } else {\n            viewBinding.toastView.hide()\n        }\n        invalidateOptionsMenu()\n    }\n\n    override fun onGridTouch(area: TapGridArea): Boolean {\n        return isReaderResumed() && controlDelegate.onGridTouch(area)\n    }\n\n    override fun onGridLongTouch(area: TapGridArea) {\n        if (isReaderResumed()) {\n            controlDelegate.onGridLongTouch(area)\n        }\n    }\n\n    override fun onProcessTouch(rawX: Int, rawY: Int): Boolean {\n        return if (\n            rawX <= gestureInsets.left ||\n            rawY <= gestureInsets.top ||\n            rawX >= viewBinding.root.width - gestureInsets.right ||\n            rawY >= viewBinding.root.height - gestureInsets.bottom ||\n            viewBinding.appbarTop.hasGlobalPoint(rawX, rawY) ||\n            viewBinding.toolbarDocked?.hasGlobalPoint(rawX, rawY) == true\n        ) {\n            false\n        } else {\n            val touchables = window.peekDecorView()?.touchables\n            touchables?.none { it.hasGlobalPoint(rawX, rawY) } != false\n        }\n    }\n\n    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n        touchHelper.dispatchTouchEvent(ev)\n        if (!viewBinding.timerControl.hasGlobalPoint(ev.rawX.toInt(), ev.rawY.toInt())) {\n            scrollTimer.onTouchEvent(ev)\n        }\n        return super.dispatchTouchEvent(ev)\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        return controlDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)\n    }\n\n    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {\n        return controlDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event)\n    }\n\n    override fun onChapterSelected(chapter: MangaChapter): Boolean {\n        viewModel.switchChapter(chapter.id, 0)\n        return true\n    }\n\n    override fun onPageSelected(page: ReaderPage): Boolean {\n        lifecycleScope.launch(Dispatchers.Default) {\n            val pages = viewModel.content.value.pages\n            val index = pages.indexOfFirst { it.chapterId == page.chapterId && it.id == page.id }\n            if (index != -1) {\n                withContext(Dispatchers.Main) {\n                    readerManager.currentReader?.switchPageTo(index, true)\n                }\n            } else {\n                viewModel.switchChapter(page.chapterId, page.index)\n            }\n        }\n        return true\n    }\n\n    override fun onReaderModeChanged(mode: ReaderMode) {\n        viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())\n        viewModel.switchMode(mode)\n        viewBinding.timerControl.onReaderModeChanged(mode)\n    }\n\n    override fun onDoubleModeChanged(isEnabled: Boolean) {\n        // Combine manual toggle with foldable auto setting\n        applyDoubleModeAuto(isEnabled)\n    }\n\n    private fun applyDoubleModeAuto(manualEnabled: Boolean? = null) {\n        val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE\n        // Auto double-page on foldable when device is unfolded (half-opened or flat)\n        val autoFoldable = settings.isReaderDoubleOnFoldable && isFoldUnfolded\n        val manualLandscape = (manualEnabled ?: settings.isReaderDoubleOnLandscape) && isLandscape\n        val autoEnabled = autoFoldable || manualLandscape\n        readerManager.setDoubleReaderMode(autoEnabled)\n    }\n\n    private fun setKeepScreenOn(isKeep: Boolean) {\n        if (isKeep) {\n            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        } else {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        }\n    }\n\n    private fun setUiIsVisible(isUiVisible: Boolean) {\n        if (viewBinding.appbarTop.isVisible != isUiVisible) {\n            if (isAnimationsEnabled) {\n                val transition = TransitionSet()\n                    .setOrdering(TransitionSet.ORDERING_TOGETHER)\n                    .addTransition(Slide(Gravity.TOP).addTarget(viewBinding.appbarTop))\n                    .addTransition(Fade().addTarget(viewBinding.infoBar))\n                viewBinding.toolbarDocked?.let {\n                    transition.addTransition(Slide(Gravity.BOTTOM).addTarget(it))\n                }\n                TransitionManager.beginDelayedTransition(viewBinding.root, transition)\n            }\n            val isFullscreen = settings.isReaderFullscreenEnabled\n            viewBinding.appbarTop.isVisible = isUiVisible\n            viewBinding.toolbarDocked?.isVisible = isUiVisible\n            viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value)\n            viewBinding.infoBar.isTimeVisible = isFullscreen\n            updateScrollTimerButton()\n            systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)\n            viewBinding.root.requestApplyInsets()\n        }\n    }\n\n    override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n        gestureInsets = insets.getInsets(WindowInsetsCompat.Type.systemGestures())\n        val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n        viewBinding.toolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n            topMargin = systemBars.top\n            rightMargin = systemBars.right\n            leftMargin = systemBars.left\n        }\n        if (viewBinding.toolbarDocked != null) {\n            viewBinding.actionsView.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n                bottomMargin = systemBars.bottom\n                rightMargin = systemBars.right\n                leftMargin = systemBars.left\n            }\n        }\n        viewBinding.infoBar.updatePadding(\n            top = systemBars.top,\n        )\n        val innerInsets = Insets.of(\n            systemBars.left,\n            if (viewBinding.appbarTop.isVisible) viewBinding.appbarTop.height else systemBars.top,\n            systemBars.right,\n            viewBinding.toolbarDocked?.takeIf { it.isVisible }?.height ?: systemBars.bottom,\n        )\n        return WindowInsetsCompat.Builder(insets)\n            .setInsets(WindowInsetsCompat.Type.systemBars(), innerInsets)\n            .build()\n    }\n\n    override fun switchPageBy(delta: Int) {\n        readerManager.currentReader?.switchPageBy(delta)\n    }\n\n    override fun switchChapterBy(delta: Int) {\n        viewModel.switchChapterBy(delta)\n    }\n\n    override fun openMenu() {\n        viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())\n        val currentMode = readerManager.currentMode ?: return\n        router.showReaderConfigSheet(currentMode)\n    }\n\n    override fun scrollBy(delta: Int, smooth: Boolean): Boolean {\n        return readerManager.currentReader?.scrollBy(delta, smooth) == true\n    }\n\n    override fun toggleUiVisibility() {\n        setUiIsVisible(!viewBinding.appbarTop.isVisible)\n    }\n\n    override fun isReaderResumed(): Boolean {\n        val reader = readerManager.currentReader ?: return false\n        return reader.isResumed && supportFragmentManager.fragments.lastOrNull() === reader\n    }\n\n    override fun onBookmarkClick() {\n        viewModel.toggleBookmark()\n    }\n\n    override fun onSavePageClick() {\n        viewModel.saveCurrentPage(pageSaveHelper)\n    }\n\n    override fun onScrollTimerClick(isLongClick: Boolean) {\n        if (isLongClick) {\n            scrollTimer.setActive(!scrollTimer.isActive.value)\n        } else {\n            viewBinding.timerControl.showOrHide()\n        }\n    }\n\n    override fun toggleScreenOrientation() {\n        if (screenOrientationHelper.toggleScreenOrientation()) {\n            Snackbar.make(\n                viewBinding.container,\n                if (screenOrientationHelper.isLocked) {\n                    R.string.screen_rotation_locked\n                } else {\n                    R.string.screen_rotation_unlocked\n                },\n                Snackbar.LENGTH_SHORT,\n            ).setAnchorView(viewBinding.toolbarDocked)\n                .show()\n        }\n    }\n\n    override fun switchPageTo(index: Int) {\n        val pages = viewModel.getCurrentChapterPages()\n        val page = pages?.getOrNull(index) ?: return\n        val chapterId = viewModel.getCurrentState()?.chapterId ?: return\n        onPageSelected(ReaderPage(page, index, chapterId))\n    }\n\n    private fun onReaderBarChanged(isBarEnabled: Boolean) {\n        viewBinding.infoBar.isVisible = isBarEnabled && viewBinding.appbarTop.isGone\n    }\n\n    private fun onUiStateChanged(pair: Pair<ReaderUiState?, ReaderUiState?>) {\n        val (previous: ReaderUiState?, uiState: ReaderUiState?) = pair\n        title = uiState?.mangaName ?: getString(R.string.loading_)\n        viewBinding.infoBar.update(uiState)\n        if (uiState == null) {\n            supportActionBar?.subtitle = null\n            viewBinding.actionsView.setSliderValue(0, 1)\n            viewBinding.actionsView.isSliderEnabled = false\n            return\n        }\n        val chapterTitle = uiState.getChapterTitle(resources)\n        supportActionBar?.subtitle = when {\n            uiState.incognito -> getString(R.string.incognito_mode)\n            else -> chapterTitle\n        }\n        if (\n            settings.isReaderChapterToastEnabled &&\n            chapterTitle != previous?.getChapterTitle(resources) &&\n            chapterTitle.isNotEmpty()\n        ) {\n            viewBinding.toastView.showTemporary(chapterTitle, TOAST_DURATION)\n        }\n        if (uiState.isSliderAvailable()) {\n            viewBinding.actionsView.setSliderValue(\n                value = uiState.currentPage,\n                max = uiState.totalPages - 1,\n            )\n        } else {\n            viewBinding.actionsView.setSliderValue(0, 1)\n        }\n        viewBinding.actionsView.isSliderEnabled = uiState.isSliderAvailable()\n        viewBinding.actionsView.isNextEnabled = uiState.hasNextChapter()\n        viewBinding.actionsView.isPrevEnabled = uiState.hasPreviousChapter()\n    }\n\n    private fun updateScrollTimerButton() {\n        val button = viewBinding.buttonTimer ?: return\n        val isButtonVisible = scrollTimer.isActive.value\n            && settings.isReaderAutoscrollFabVisible\n            && !viewBinding.appbarTop.isVisible\n            && !viewBinding.timerControl.isVisible\n        if (button.isVisible != isButtonVisible) {\n            val transition = Fade().addTarget(button)\n            TransitionManager.beginDelayedTransition(viewBinding.root, transition)\n            button.isVisible = isButtonVisible\n        }\n    }\n\n    // Observe foldable window layout to auto-enable double-page if configured\n    private fun observeWindowLayout() {\n        WindowInfoTracker.getOrCreate(this)\n            .windowLayoutInfo(this)\n            .onEach { info ->\n                val fold = info.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()\n                val unfolded = when (fold?.state) {\n                    FoldingFeature.State.HALF_OPENED, FoldingFeature.State.FLAT -> true\n                    else -> false\n                }\n                if (unfolded != isFoldUnfolded) {\n                    isFoldUnfolded = unfolded\n                    applyDoubleModeAuto()\n                }\n            }\n            .launchIn(lifecycleScope)\n    }\n\n    private fun askForIncognitoMode() {\n        buildAlertDialog(this, isCentered = true) {\n            var dontAskAgain = false\n            val listener = DialogInterface.OnClickListener { _, which ->\n                if (which == DialogInterface.BUTTON_NEUTRAL) {\n                    finishAfterTransition()\n                } else {\n                    viewModel.setIncognitoMode(which == DialogInterface.BUTTON_POSITIVE, dontAskAgain)\n                }\n            }\n            setCheckbox(R.string.dont_ask_again, dontAskAgain) { _, isChecked ->\n                dontAskAgain = isChecked\n            }\n            setIcon(R.drawable.ic_incognito)\n            setTitle(R.string.incognito_mode)\n            setMessage(R.string.incognito_mode_hint_nsfw)\n            setPositiveButton(R.string.incognito, listener)\n            setNegativeButton(R.string.disable, listener)\n            setNeutralButton(android.R.string.cancel, listener)\n            setOnCancelListener { finishAfterTransition() }\n            setCancelable(true)\n        }.show()\n    }\n\n    companion object {\n\n        private const val TOAST_DURATION = 2000L\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderContent.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\n\ndata class ReaderContent(\n\tval pages: List<ReaderPage>,\n\tval state: ReaderState?\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderControlDelegate.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.content.res.Resources\nimport android.view.KeyEvent\nimport android.view.View\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.reader.data.TapGridSettings\nimport org.koitharu.kotatsu.reader.domain.TapGridArea\nimport org.koitharu.kotatsu.reader.ui.tapgrid.TapAction\nimport kotlin.math.sign\n\nclass ReaderControlDelegate(\n\tresources: Resources,\n\tprivate val settings: AppSettings,\n\tprivate val tapGridSettings: TapGridSettings,\n\tprivate val listener: OnInteractionListener,\n) : View.OnClickListener {\n\n\tprivate var minScrollDelta = resources.getDimensionPixelSize(R.dimen.reader_scroll_delta_min)\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_prev -> listener.switchChapterBy(-1)\n\t\t\tR.id.button_next -> listener.switchChapterBy(1)\n\t\t}\n\t}\n\n\tfun onGridTouch(area: TapGridArea): Boolean {\n\t\tval action = tapGridSettings.getTapAction(\n\t\t\tarea = area,\n\t\t\tisLongTap = false,\n\t\t) ?: return false\n\t\tprocessAction(action)\n\t\treturn true\n\t}\n\n\tfun onGridLongTouch(area: TapGridArea) {\n\t\tval action = tapGridSettings.getTapAction(\n\t\t\tarea = area,\n\t\t\tisLongTap = true,\n\t\t) ?: return\n\t\tprocessAction(action)\n\t}\n\n\tfun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n\t\twhen (keyCode) {\n\t\t\tKeyEvent.KEYCODE_NAVIGATE_NEXT,\n\t\t\tKeyEvent.KEYCODE_SPACE -> switchBy(1, event, false)\n\n\t\t\tKeyEvent.KEYCODE_PAGE_DOWN -> switchBy(1, event, false)\n\n\n\t\t\tKeyEvent.KEYCODE_NAVIGATE_PREVIOUS -> switchBy(-1, event, false)\n\t\t\tKeyEvent.KEYCODE_PAGE_UP -> switchBy(-1, event, false)\n\n\t\t\tKeyEvent.KEYCODE_R -> switchBy(1, null, false)\n\n\t\t\tKeyEvent.KEYCODE_L -> switchBy(-1, null, false)\n\n\t\t\tKeyEvent.KEYCODE_VOLUME_UP -> if (settings.isReaderVolumeButtonsEnabled) {\n\t\t\t\tswitchBy(if (settings.isReaderNavigationInverted) 1 else -1, event, false)\n\t\t\t} else {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tKeyEvent.KEYCODE_VOLUME_DOWN -> if (settings.isReaderVolumeButtonsEnabled) {\n\t\t\t\tswitchBy(if (settings.isReaderNavigationInverted) -1 else 1, event, false)\n\t\t\t} else {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tKeyEvent.KEYCODE_DPAD_RIGHT -> switchByRelative(if (settings.isReaderNavigationInverted) -1 else 1, event)\n\n\t\t\tKeyEvent.KEYCODE_DPAD_LEFT -> switchByRelative(if (settings.isReaderNavigationInverted) 1 else -1, event)\n\n\t\t\tKeyEvent.KEYCODE_DPAD_CENTER -> listener.toggleUiVisibility()\n\n\t\t\tKeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,\n\t\t\tKeyEvent.KEYCODE_DPAD_UP -> switchBy(if (settings.isReaderNavigationInverted) 1 else -1, event, true)\n\n\t\t\tKeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,\n\t\t\tKeyEvent.KEYCODE_DPAD_DOWN -> switchBy(if (settings.isReaderNavigationInverted) -1 else 1, event, true)\n\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n\n\tfun onKeyUp(keyCode: Int, @Suppress(\"UNUSED_PARAMETER\") event: KeyEvent?): Boolean {\n\t\treturn (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)\n\t\t\t&& settings.isReaderVolumeButtonsEnabled\n\t}\n\n\tprivate fun processAction(action: TapAction) {\n\t\twhen (action) {\n\t\t\tTapAction.PAGE_NEXT -> listener.switchPageBy(1)\n\t\t\tTapAction.PAGE_PREV -> listener.switchPageBy(-1)\n\t\t\tTapAction.CHAPTER_NEXT -> listener.switchChapterBy(1)\n\t\t\tTapAction.CHAPTER_PREV -> listener.switchChapterBy(-1)\n\t\t\tTapAction.TOGGLE_UI -> listener.toggleUiVisibility()\n\t\t\tTapAction.SHOW_MENU -> listener.openMenu()\n\t\t}\n\t}\n\n\tprivate fun isReaderTapsReversed(): Boolean {\n\t\treturn settings.isReaderControlAlwaysLTR && listener.readerMode == ReaderMode.REVERSED\n\t}\n\n\tprivate fun switchBy(delta: Int, event: KeyEvent?, scroll: Boolean) {\n\t\tif (event?.isCtrlPressed == true) {\n\t\t\tlistener.switchChapterBy(delta)\n\t\t} else if (scroll) {\n\t\t\tif (!listener.scrollBy(minScrollDelta * delta.sign, smooth = true)) {\n\t\t\t\tlistener.switchPageBy(delta)\n\t\t\t}\n\t\t} else {\n\t\t\tlistener.switchPageBy(delta)\n\t\t}\n\t}\n\n\tprivate fun switchByRelative(delta: Int, event: KeyEvent?) {\n\t\treturn switchBy(if (isReaderTapsReversed()) -delta else delta, event, scroll = false)\n\t}\n\n\tinterface OnInteractionListener {\n\n\t\tval readerMode: ReaderMode?\n\n\t\tfun switchPageBy(delta: Int)\n\n\t\tfun switchPageTo(index: Int)\n\n\t\tfun switchChapterBy(delta: Int)\n\n\t\tfun scrollBy(delta: Int, smooth: Boolean): Boolean\n\n\t\tfun toggleUiVisibility()\n\n\t\tfun onBookmarkClick()\n\n\t\tfun openMenu()\n\n\t\tfun onSavePageClick()\n\n\t\tfun onScrollTimerClick(isLongClick: Boolean)\n\n\t\tfun toggleScreenOrientation()\n\n\t\tfun isReaderResumed(): Boolean\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.annotation.SuppressLint\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.res.ColorStateList\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.graphics.drawable.Drawable\nimport android.os.BatteryManager\nimport android.os.Build\nimport android.util.AttributeSet\nimport android.view.RoundedCorner\nimport android.view.View\nimport android.view.WindowInsets\nimport androidx.annotation.AttrRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.withStyledAttributes\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.graphics.withScale\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getThemeColorStateList\nimport org.koitharu.kotatsu.core.util.ext.isNightMode\nimport org.koitharu.kotatsu.core.util.ext.measureDimension\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.util.format\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderUiState\nimport java.time.LocalTime\nimport java.time.format.DateTimeFormatter\nimport java.time.format.FormatStyle\nimport com.google.android.material.R as materialR\n\nprivate const val ALPHA_TEXT = 200\nprivate const val ALPHA_BG = 180\n\nclass ReaderInfoBarView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : View(context, attrs, defStyleAttr) {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG)\n\tprivate val textBounds = Rect()\n\tprivate val timeFormat = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)\n\tprivate val systemStateReceiver = SystemStateReceiver()\n\tprivate var insetLeft: Int = 0\n\tprivate var insetRight: Int = 0\n\tprivate var insetTop: Int = 0\n\tprivate val insetLeftFallback: Int\n\tprivate val insetRightFallback: Int\n\tprivate val insetTopFallback: Int\n\tprivate val insetCornerFallback = getSystemUiDimensionOffset(\"rounded_corner_content_padding\")\n\tprivate var colorText =\n\t\t(context.getThemeColorStateList(materialR.attr.colorOnSurface)\n\t\t\t?: ColorStateList.valueOf(Color.BLACK)).withAlpha(ALPHA_TEXT)\n\tprivate var colorBackground =\n\t\t(context.getThemeColorStateList(materialR.attr.colorSurface)\n\t\t\t?: ColorStateList.valueOf(Color.WHITE)).withAlpha(ALPHA_BG)\n\tprivate val batteryIcon = ContextCompat.getDrawable(context, R.drawable.ic_battery_outline)\n\n\tprivate var currentTextColor: Int = Color.TRANSPARENT\n\tprivate var currentBackgroundColor: Int = Color.TRANSPARENT\n\tprivate var currentOutlineColor: Int = Color.TRANSPARENT\n\tprivate var timeText = timeFormat.format(LocalTime.now())\n\tprivate var batteryText = \"\"\n\tprivate var text: String = \"\"\n\tprivate var prevTextHeight: Int = 0\n\n\tprivate val innerHeight\n\t\tget() = height - paddingTop - paddingBottom - insetTop\n\n\tprivate val innerWidth\n\t\tget() = width - paddingLeft - paddingRight - insetLeft - insetRight\n\n\tvar drawBackground: Boolean = false\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tinvalidate()\n\t\t}\n\n\tvar isTimeVisible: Boolean = true\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tinvalidate()\n\t\t}\n\n\tinit {\n\t\tcontext.withStyledAttributes(attrs, R.styleable.ReaderInfoBarView, defStyleAttr) {\n\t\t\tpaint.strokeWidth = getDimension(R.styleable.ReaderInfoBarView_android_strokeWidth, 2f)\n\t\t\tpaint.textSize = getDimension(R.styleable.ReaderInfoBarView_android_textSize, 16f)\n\t\t}\n\t\tval insetStart = getSystemUiDimensionOffset(\"status_bar_padding_start\").coerceAtLeast(0)\n\t\tval insetEnd = getSystemUiDimensionOffset(\"status_bar_padding_end\").coerceAtLeast(0)\n\t\tval isRtl = layoutDirection == LAYOUT_DIRECTION_RTL\n\t\tinsetLeftFallback = if (isRtl) insetEnd else insetStart\n\t\tinsetRightFallback = if (isRtl) insetStart else insetEnd\n\t\tinsetTopFallback = minOf(insetLeftFallback, insetRightFallback)\n\t\tbatteryIcon?.setTintList(colorText)\n\t}\n\n\toverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\t\tval desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight + insetLeft + insetRight\n\t\tval desiredHeight = maxOf(\n\t\t\tcomputeTextHeight().also { prevTextHeight = it },\n\t\t\tsuggestedMinimumHeight,\n\t\t) + paddingTop + paddingBottom + insetTop\n\t\tsetMeasuredDimension(\n\t\t\tmeasureDimension(desiredWidth, widthMeasureSpec),\n\t\t\tmeasureDimension(desiredHeight, heightMeasureSpec),\n\t\t)\n\t}\n\n\toverride fun onDraw(canvas: Canvas) {\n\t\tsuper.onDraw(canvas)\n\t\tif (drawBackground) {\n\t\t\tcanvas.drawColor(currentBackgroundColor)\n\t\t}\n\t\tcomputeTextHeight()\n\t\tval h = innerHeight.toFloat()\n\t\tval ty = h / 2f + textBounds.height() / 2f - textBounds.bottom\n\t\tpaint.textAlign = Paint.Align.LEFT\n\t\tpaint.color = currentTextColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tif (drawBackground) {\n\t\t\tcanvas.drawText(text, (paddingLeft + insetLeft).toFloat(), paddingTop + insetTop + ty, paint)\n\t\t} else {\n\t\t\tcanvas.drawTextOutline(text, (paddingLeft + insetLeft).toFloat(), paddingTop + insetTop + ty)\n\t\t}\n\t\tif (isTimeVisible) {\n\t\t\tpaint.textAlign = Paint.Align.RIGHT\n\t\t\tvar endX = (width - paddingRight - insetRight).toFloat()\n\t\t\tif (drawBackground) {\n\t\t\t\tcanvas.drawText(timeText, endX, paddingTop + insetTop + ty, paint)\n\t\t\t} else {\n\t\t\t\tcanvas.drawTextOutline(timeText, endX, paddingTop + insetTop + ty)\n\t\t\t}\n\t\t\tif (batteryText.isNotEmpty()) {\n\t\t\t\tpaint.getTextBounds(timeText, 0, timeText.length, textBounds)\n\t\t\t\tendX -= textBounds.width()\n\t\t\t\tendX -= h * 0.6f\n\t\t\t\tif (drawBackground) {\n\t\t\t\t\tcanvas.drawText(batteryText, endX, paddingTop + insetTop + ty, paint)\n\t\t\t\t} else {\n\t\t\t\t\tcanvas.drawTextOutline(batteryText, endX, paddingTop + insetTop + ty)\n\t\t\t\t}\n\t\t\t\tbatteryIcon?.let {\n\t\t\t\t\tpaint.getTextBounds(batteryText, 0, batteryText.length, textBounds)\n\t\t\t\t\tendX -= textBounds.width()\n\t\t\t\t\tval iconCenter = paddingTop + insetTop + textBounds.height() / 2\n\t\t\t\t\tit.setBounds(\n\t\t\t\t\t\t(endX - h).toInt(),\n\t\t\t\t\t\t(iconCenter - h / 2).toInt(),\n\t\t\t\t\t\tendX.toInt(),\n\t\t\t\t\t\t(iconCenter + h / 2).toInt(),\n\t\t\t\t\t)\n\t\t\t\t\tif (drawBackground) {\n\t\t\t\t\t\tit.draw(canvas)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tit.drawWithOutline(canvas)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n\t\tsuper.onSizeChanged(w, h, oldw, oldh)\n\t\tupdateCutoutInsets(ViewCompat.getRootWindowInsets(this))\n\t}\n\n\toverride fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {\n\t\tupdateCutoutInsets(WindowInsetsCompat.toWindowInsetsCompat(insets))\n\t\treturn super.onApplyWindowInsets(insets)\n\t}\n\n\toverride fun onAttachedToWindow() {\n\t\tsuper.onAttachedToWindow()\n\t\tContextCompat.registerReceiver(\n\t\t\tcontext,\n\t\t\tsystemStateReceiver,\n\t\t\tIntentFilter().apply {\n\t\t\t\taddAction(Intent.ACTION_TIME_TICK)\n\t\t\t\taddAction(Intent.ACTION_BATTERY_CHANGED)\n\t\t\t},\n\t\t\tContextCompat.RECEIVER_EXPORTED,\n\t\t)\n\t\tupdateCutoutInsets(ViewCompat.getRootWindowInsets(this))\n\t}\n\n\toverride fun onDetachedFromWindow() {\n\t\tsuper.onDetachedFromWindow()\n\t\tcontext.unregisterReceiver(systemStateReceiver)\n\t}\n\n\toverride fun verifyDrawable(who: Drawable): Boolean {\n\t\treturn who == batteryIcon || super.verifyDrawable(who)\n\t}\n\n\toverride fun jumpDrawablesToCurrentState() {\n\t\tsuper.jumpDrawablesToCurrentState()\n\t\tbatteryIcon?.jumpToCurrentState()\n\t}\n\n\toverride fun onCreateDrawableState(extraSpace: Int): IntArray? {\n\t\tval iconState = batteryIcon?.state ?: return super.onCreateDrawableState(extraSpace)\n\t\treturn mergeDrawableStates(super.onCreateDrawableState(extraSpace + iconState.size), iconState)\n\t}\n\n\toverride fun drawableStateChanged() {\n\t\tcurrentTextColor = colorText.getColorForState(drawableState, colorText.defaultColor)\n\t\tcurrentBackgroundColor = colorBackground.getColorForState(drawableState, colorBackground.defaultColor)\n\t\tcurrentOutlineColor = ColorUtils.setAlphaComponent(currentBackgroundColor, Color.alpha(currentTextColor))\n\t\tsuper.drawableStateChanged()\n\t\tif (batteryIcon != null && batteryIcon.isStateful && batteryIcon.setState(drawableState)) {\n\t\t\tinvalidateDrawable(batteryIcon)\n\t\t}\n\t}\n\n\tfun applyColorScheme(isBlackOnWhite: Boolean) {\n\t\tval isDarkTheme = resources.isNightMode\n\t\tcolorText = (context.getThemeColorStateList(\n\t\t\tif (isBlackOnWhite != isDarkTheme) materialR.attr.colorOnSurface else materialR.attr.colorOnSurfaceInverse,\n\t\t) ?: ColorStateList.valueOf(if (isBlackOnWhite) Color.BLACK else Color.WHITE)).withAlpha(ALPHA_TEXT)\n\t\tcolorBackground = (context.getThemeColorStateList(\n\t\t\tif (isBlackOnWhite != isDarkTheme) materialR.attr.colorSurface else materialR.attr.colorSurfaceInverse,\n\t\t) ?: ColorStateList.valueOf(if (isBlackOnWhite) Color.WHITE else Color.BLACK)).withAlpha(ALPHA_BG)\n\t\tbatteryIcon?.setTintList(colorText)\n\t\tdrawableStateChanged()\n\t}\n\n\t@SuppressLint(\"StringFormatMatches\")\n\tfun update(state: ReaderUiState?) {\n\t\ttext = if (state != null) {\n\t\t\tcontext.getString(\n\t\t\t\tR.string.reader_info_pattern,\n\t\t\t\tstate.chapterNumber,\n\t\t\t\tstate.chaptersTotal,\n\t\t\t\tstate.currentPage + 1,\n\t\t\t\tstate.totalPages,\n\t\t\t) + if (state.percent in 0f..1f) {\n\t\t\t\t\"     \" + context.getString(R.string.percent_string_pattern, (state.percent * 100).format())\n\t\t\t} else {\n\t\t\t\t\"\"\n\t\t\t}\n\t\t} else {\n\t\t\t\"\"\n\t\t}\n\t\tval newHeight = computeTextHeight()\n\t\tif (newHeight != prevTextHeight) {\n\t\t\tprevTextHeight = newHeight\n\t\t\trequestLayout()\n\t\t}\n\t\tinvalidate()\n\t}\n\n\tprivate fun computeTextHeight(): Int {\n\t\tval str = text + batteryText + timeText\n\t\tpaint.getTextBounds(str, 0, str.length, textBounds)\n\t\treturn textBounds.height()\n\t}\n\n\tprivate fun updateCutoutInsets(insetsCompat: WindowInsetsCompat?) {\n\t\tinsetLeft = insetLeftFallback\n\t\tinsetRight = insetRightFallback\n\t\tinsetTop = insetTopFallback\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && insetsCompat != null) {\n\t\t\tval nativeInsets = insetsCompat.toWindowInsets()\n\t\t\tnativeInsets?.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)?.let { corner ->\n\t\t\t\tinsetLeft += corner.radius\n\t\t\t}\n\t\t\tnativeInsets?.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT)?.let { corner ->\n\t\t\t\tinsetRight += corner.radius\n\t\t\t}\n\t\t} else {\n\t\t\tinsetLeft += insetCornerFallback\n\t\t\tinsetRight += insetCornerFallback\n\t\t}\n\t\tinsetsCompat?.displayCutout?.let { cutout ->\n\t\t\tfor (rect in cutout.boundingRects) {\n\t\t\t\tif (rect.left <= paddingLeft) {\n\t\t\t\t\tinsetLeft += rect.width()\n\t\t\t\t}\n\t\t\t\tif (rect.right >= width - paddingRight) {\n\t\t\t\t\tinsetRight += rect.width()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun Canvas.drawTextOutline(text: String, x: Float, y: Float) {\n\t\tpaint.color = currentOutlineColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tdrawText(text, x, y, paint)\n\t\tpaint.color = currentTextColor\n\t\tpaint.style = Paint.Style.FILL\n\t\tdrawText(text, x, y, paint)\n\t}\n\n\tprivate fun Drawable.drawWithOutline(canvas: Canvas) {\n\t\tif (bounds.isEmpty) {\n\t\t\treturn\n\t\t}\n\t\tvar requiredScale = (bounds.width() + paint.strokeWidth * 2f) / bounds.width().toFloat()\n\t\tsetTint(currentOutlineColor)\n\t\tcanvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) {\n\t\t\tdraw(canvas)\n\t\t}\n\t\trequiredScale = 1f / requiredScale\n\t\tcanvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) {\n\t\t\tdraw(canvas)\n\t\t}\n\t\tsetTint(currentTextColor)\n\t\tdraw(canvas)\n\t}\n\n\tprivate inner class SystemStateReceiver : BroadcastReceiver() {\n\n\t\toverride fun onReceive(context: Context, intent: Intent) {\n\t\t\tval level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)\n\t\t\tval scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)\n\t\t\tif (level != -1 && scale != -1) {\n\t\t\t\tbatteryText = context.getString(R.string.percent_string_pattern, (level * 100 / scale).toString())\n\t\t\t}\n\n\t\t\ttimeText = timeFormat.format(LocalTime.now())\n\t\t\tif (isTimeVisible) {\n\t\t\t\tinvalidate()\n\t\t\t}\n\t\t}\n\t}\n\n\t@SuppressLint(\"DiscouragedApi\")\n\tprivate fun getSystemUiDimensionOffset(name: String, fallback: Int = 0): Int = runCatching {\n\t\tval manager = context.packageManager\n\t\tval resources = manager.getResourcesForApplication(\"com.android.systemui\")\n\t\tval resId = resources.getIdentifier(name, \"dimen\", \"com.android.systemui\")\n\t\tresources.getDimensionPixelOffset(resId)\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrDefault(fallback)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderManager.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.content.res.Configuration\nimport androidx.fragment.app.FragmentContainerView\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.commit\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.util.ext.findKeyByValue\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.doublepage.DoubleReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.doublereversed.ReversedDoubleReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.reversed.ReversedReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.vertical.VerticalReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonReaderFragment\nimport java.util.EnumMap\n\nclass ReaderManager(\n\tprivate val fragmentManager: FragmentManager,\n\tprivate val container: FragmentContainerView,\n\tsettings: AppSettings,\n) {\n\n\tprivate val modeMap = EnumMap<ReaderMode, Class<out BaseReaderFragment<*>>>(ReaderMode::class.java)\n\n\tinit {\n\t\tval useDoublePages = isLandscape() && settings.isReaderDoubleOnLandscape\n\t\tinvalidateTypesMap(useDoublePages)\n\t}\n\n\tval currentReader: BaseReaderFragment<*>?\n\t\tget() = fragmentManager.findFragmentById(container.id) as? BaseReaderFragment<*>\n\n\tval currentMode: ReaderMode?\n\t\tget() {\n\t\t\tval readerClass = currentReader?.javaClass ?: return null\n\t\t\treturn modeMap.findKeyByValue(readerClass)\n\t\t}\n\n\tfun replace(newMode: ReaderMode) {\n\t\tval readerClass = requireNotNull(modeMap[newMode])\n\t\tfragmentManager.commit {\n\t\t\tsetReorderingAllowed(true)\n\t\t\treplace(container.id, readerClass, null, null)\n\t\t}\n\t}\n\n\tfun setDoubleReaderMode(isEnabled: Boolean) {\n\t\tval mode = currentMode\n\t\tval prevReader = currentReader?.javaClass\n\t\tinvalidateTypesMap(isEnabled)\n\t\tval newReader = modeMap[mode]\n\t\tif (mode != null && newReader != prevReader) {\n\t\t\treplace(mode)\n\t\t}\n\t}\n\n\tprivate fun invalidateTypesMap(useDoublePages: Boolean) {\n\t\tmodeMap[ReaderMode.STANDARD] = if (useDoublePages) {\n\t\t\tDoubleReaderFragment::class.java\n\t\t} else {\n\t\t\tPagerReaderFragment::class.java\n\t\t}\n\t\tmodeMap[ReaderMode.REVERSED] = if (useDoublePages) {\n\t\t\tReversedDoubleReaderFragment::class.java\n\t\t} else {\n\t\t\tReversedReaderFragment::class.java\n\t\t}\n\t\tmodeMap[ReaderMode.WEBTOON] = WebtoonReaderFragment::class.java\n\t\tmodeMap[ReaderMode.VERTICAL] = VerticalReaderFragment::class.java\n\t}\n\n\tprivate fun isLandscape() = container.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\n\nclass ReaderMenuProvider(\n\tprivate val viewModel: ReaderViewModel,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_reader, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\treturn when (menuItem.itemId) {\n\t\t\tR.id.action_info -> {\n\t\t\t\t// TODO\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderNavigationCallback.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\n\ninterface ReaderNavigationCallback {\n\n\tfun onPageSelected(page: ReaderPage): Boolean\n\n\tfun onChapterSelected(chapter: MangaChapter): Boolean\n\n\tfun onBookmarkSelected(bookmark: Bookmark): Boolean = onPageSelected(\n\t\tReaderPage(bookmark.toMangaPage(), bookmark.page, bookmark.chapterId),\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderState.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport org.koitharu.kotatsu.core.model.MangaHistory\nimport org.koitharu.kotatsu.parsers.model.Manga\n\n@Parcelize\ndata class ReaderState(\n\tval chapterId: Long,\n\tval page: Int,\n\tval scroll: Int,\n) : Parcelable {\n\n\tconstructor(history: MangaHistory) : this(\n\t\tchapterId = history.chapterId,\n\t\tpage = history.page,\n\t\tscroll = history.scroll,\n\t)\n\n\tconstructor(manga: Manga, branch: String?) : this(\n\t\tchapterId = manga.chapters?.let {\n\t\t\tit.firstOrNull { x -> x.branch == branch } ?: it.firstOrNull()\n\t\t}?.id ?: error(\"Cannot find first chapter\"),\n\t\tpage = 0,\n\t\tscroll = 0,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderToastView.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewPropertyAnimator\nimport android.view.animation.AccelerateInterpolator\nimport android.view.animation.DecelerateInterpolator\nimport androidx.annotation.StringRes\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport com.google.android.material.textview.MaterialTextView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\n\nclass ReaderToastView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = 0,\n) : MaterialTextView(context, attrs, defStyleAttr) {\n\n\tprivate var currentAnimator: ViewPropertyAnimator? = null\n\n\tprivate var hideRunnable = Runnable {\n\t\thide()\n\t}\n\n\tfun show(message: CharSequence) {\n\t\tremoveCallbacks(hideRunnable)\n\t\ttext = message\n\t\tshowImpl()\n\t}\n\n\tfun show(@StringRes messageId: Int) {\n\t\tshow(context.getString(messageId))\n\t}\n\n\tfun showTemporary(message: CharSequence, duration: Long) {\n\t\tshow(message)\n\t\tpostDelayed(hideRunnable, duration)\n\t}\n\n\tfun hide() {\n\t\tremoveCallbacks(hideRunnable)\n\t\thideImpl()\n\t}\n\n\toverride fun onDetachedFromWindow() {\n\t\tremoveCallbacks(hideRunnable)\n\t\tsuper.onDetachedFromWindow()\n\t}\n\n\tprivate fun showImpl() {\n\t\tcurrentAnimator?.cancel()\n\t\tclearAnimation()\n\t\tif (!context.isAnimationsEnabled) {\n\t\t\tcurrentAnimator = null\n\t\t\tisVisible = true\n\t\t\treturn\n\t\t}\n\t\talpha = 0f\n\t\tisVisible = true\n\t\tcurrentAnimator = animate()\n\t\t\t.alpha(1f)\n\t\t\t.setInterpolator(DecelerateInterpolator())\n\t\t\t.setDuration(context.getAnimationDuration(R.integer.config_shorterAnimTime))\n\t\t\t.setListener(null)\n\t}\n\n\tprivate fun hideImpl() {\n\t\tcurrentAnimator?.cancel()\n\t\tclearAnimation()\n\t\tif (!context.isAnimationsEnabled) {\n\t\t\tcurrentAnimator = null\n\t\t\tisGone = true\n\t\t\treturn\n\t\t}\n\t\tcurrentAnimator = animate()\n\t\t\t.alpha(0f)\n\t\t\t.setInterpolator(AccelerateInterpolator())\n\t\t\t.setDuration(context.getAnimationDuration(R.integer.config_shorterAnimTime))\n\t\t\t.setListener(\n\t\t\t\tobject : AnimatorListenerAdapter() {\n\t\t\t\t\toverride fun onAnimationEnd(animation: Animator) {\n\t\t\t\t\t\tisGone = true\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.net.Uri\nimport androidx.annotation.AnyThread\nimport androidx.annotation.MainThread\nimport androidx.annotation.WorkerThread\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.mapNotNull\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.bookmarks.domain.Bookmark\nimport org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository\nimport org.koitharu.kotatsu.core.exceptions.EmptyMangaException\nimport org.koitharu.kotatsu.core.model.getPreferredBranch\nimport org.koitharu.kotatsu.core.nav.MangaIntent\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.firstNotNull\nimport org.koitharu.kotatsu.core.util.ext.requireValue\nimport org.koitharu.kotatsu.details.data.MangaDetails\nimport org.koitharu.kotatsu.details.domain.DetailsInteractor\nimport org.koitharu.kotatsu.details.domain.DetailsLoadUseCase\nimport org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel\nimport org.koitharu.kotatsu.details.ui.pager.EmptyMangaReason\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.history.domain.HistoryUpdateUseCase\nimport org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.ContentRating\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.sizeOrZero\nimport org.koitharu.kotatsu.reader.domain.ChaptersLoader\nimport org.koitharu.kotatsu.reader.domain.DetectReaderModeUseCase\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderUiState\nimport org.koitharu.kotatsu.scrobbling.discord.ui.DiscordRpc\nimport org.koitharu.kotatsu.stats.domain.StatsCollector\nimport java.time.Instant\nimport javax.inject.Inject\n\nprivate const val BOUNDS_PAGE_OFFSET = 2\nprivate const val PREFETCH_LIMIT = 10\n\n@HiltViewModel\nclass ReaderViewModel @Inject constructor(\n    private val savedStateHandle: SavedStateHandle,\n    private val dataRepository: MangaDataRepository,\n    private val historyRepository: HistoryRepository,\n    private val bookmarksRepository: BookmarksRepository,\n    settings: AppSettings,\n    private val pageLoader: PageLoader,\n    private val chaptersLoader: ChaptersLoader,\n    private val appShortcutManager: AppShortcutManager,\n    private val detailsLoadUseCase: DetailsLoadUseCase,\n    private val historyUpdateUseCase: HistoryUpdateUseCase,\n    private val detectReaderModeUseCase: DetectReaderModeUseCase,\n    private val statsCollector: StatsCollector,\n    private val discordRpc: DiscordRpc,\n    @LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n    interactor: DetailsInteractor,\n    deleteLocalMangaUseCase: DeleteLocalMangaUseCase,\n    downloadScheduler: DownloadWorker.Scheduler,\n    readerSettingsProducerFactory: ReaderSettings.Producer.Factory,\n) : ChaptersPagesViewModel(\n    settings = settings,\n    interactor = interactor,\n    bookmarksRepository = bookmarksRepository,\n    historyRepository = historyRepository,\n    downloadScheduler = downloadScheduler,\n    deleteLocalMangaUseCase = deleteLocalMangaUseCase,\n    localStorageChanges = localStorageChanges,\n) {\n    private val intent = MangaIntent(savedStateHandle)\n\n    private var loadingJob: Job? = null\n    private var pageSaveJob: Job? = null\n    private var bookmarkJob: Job? = null\n    private var stateChangeJob: Job? = null\n\n    init {\n        mangaDetails.value = intent.manga?.let { MangaDetails(it) }\n    }\n\n    val readerMode = MutableStateFlow<ReaderMode?>(null)\n    val onPageSaved = MutableEventFlow<Collection<Uri>>()\n    val onLoadingError = MutableEventFlow<Throwable>()\n    val onShowToast = MutableEventFlow<Int>()\n    val onAskNsfwIncognito = MutableEventFlow<Unit>()\n    val uiState = MutableStateFlow<ReaderUiState?>(null)\n\n    val isIncognitoMode = MutableStateFlow(savedStateHandle.get<Boolean>(ReaderIntent.EXTRA_INCOGNITO))\n\n    val content = MutableStateFlow(ReaderContent(emptyList(), null))\n\n    val pageAnimation = settings.observeAsStateFlow(\n        scope = viewModelScope + Dispatchers.Default,\n        key = AppSettings.KEY_READER_ANIMATION,\n        valueProducer = { readerAnimation },\n    )\n\n    val isInfoBarEnabled = settings.observeAsStateFlow(\n        scope = viewModelScope + Dispatchers.Default,\n        key = AppSettings.KEY_READER_BAR,\n        valueProducer = { isReaderBarEnabled },\n    )\n\n    val isInfoBarTransparent = settings.observeAsStateFlow(\n        scope = viewModelScope + Dispatchers.Default,\n        key = AppSettings.KEY_READER_BAR_TRANSPARENT,\n        valueProducer = { isReaderBarTransparent },\n    )\n\n    val isKeepScreenOnEnabled = settings.observeAsStateFlow(\n        scope = viewModelScope + Dispatchers.Default,\n        key = AppSettings.KEY_READER_SCREEN_ON,\n        valueProducer = { isReaderKeepScreenOn },\n    )\n\n    val isWebtoonZooEnabled = observeIsWebtoonZoomEnabled()\n        .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)\n\n    val isWebtoonGapsEnabled = settings.observeAsStateFlow(\n        scope = viewModelScope + Dispatchers.Default,\n        key = AppSettings.KEY_WEBTOON_GAPS,\n        valueProducer = { isWebtoonGapsEnabled },\n    )\n\n    val isWebtoonPullGestureEnabled = settings.observeAsStateFlow(\n        scope = viewModelScope + Dispatchers.Default,\n        key = AppSettings.KEY_WEBTOON_PULL_GESTURE,\n        valueProducer = { isWebtoonPullGestureEnabled },\n    )\n\n    val defaultWebtoonZoomOut = observeIsWebtoonZoomEnabled().flatMapLatest {\n        if (it) {\n            observeWebtoonZoomOut()\n        } else {\n            flowOf(0f)\n        }\n    }.flowOn(Dispatchers.Default)\n\n    val isZoomControlsEnabled = getObserveIsZoomControlEnabled().flatMapLatest { zoom ->\n        if (zoom) {\n            combine(readerMode, isWebtoonZooEnabled) { mode, ze -> ze || mode != ReaderMode.WEBTOON }\n        } else {\n            flowOf(false)\n        }\n    }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)\n\n    val readerSettingsProducer = readerSettingsProducerFactory.create(\n        manga.mapNotNull { it?.id },\n    )\n\n    val isMangaNsfw = manga.map { it?.contentRating == ContentRating.ADULT }\n\n    val isBookmarkAdded = readingState.flatMapLatest { state ->\n        val manga = mangaDetails.value?.toManga()\n        if (state == null || manga == null) {\n            flowOf(false)\n        } else {\n            bookmarksRepository.observeBookmark(manga, state.chapterId, state.page)\n                .map {\n                    it != null && it.chapterId == state.chapterId && it.page == state.page\n                }\n        }\n    }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)\n\n    init {\n        initIncognitoMode()\n        loadImpl()\n        launchJob(Dispatchers.Default) {\n            val mangaId = manga.filterNotNull().first().id\n            if (!isIncognitoMode.firstNotNull()) {\n                appShortcutManager.notifyMangaOpened(mangaId)\n            }\n        }\n    }\n\n    fun reload() {\n        loadingJob?.cancel()\n        loadImpl()\n    }\n\n    fun onPause() {\n        getMangaOrNull()?.let {\n            statsCollector.onPause(it.id)\n        }\n    }\n\n    fun onStop() {\n        discordRpc.clearRpc()\n    }\n\n    fun onIdle() {\n        discordRpc.setIdle()\n    }\n\n    fun switchMode(newMode: ReaderMode) {\n        launchJob {\n            val manga = checkNotNull(getMangaOrNull())\n            dataRepository.saveReaderMode(\n                manga = manga,\n                mode = newMode,\n            )\n            readerMode.value = newMode\n            content.update {\n                it.copy(state = getCurrentState())\n            }\n        }\n    }\n\n    fun saveCurrentState(state: ReaderState? = null) {\n        if (state != null) {\n            readingState.value = state\n            savedStateHandle[ReaderIntent.EXTRA_STATE] = state\n        }\n        if (isIncognitoMode.value != false) {\n            return\n        }\n        val readerState = state ?: readingState.value ?: return\n        historyUpdateUseCase.invokeAsync(\n            manga = getMangaOrNull() ?: return,\n            readerState = readerState,\n            percent = computePercent(readerState.chapterId, readerState.page),\n        )\n    }\n\n    fun getCurrentState() = readingState.value\n\n    fun getCurrentChapterPages(): List<MangaPage>? {\n        val chapterId = readingState.value?.chapterId ?: return null\n        return chaptersLoader.getPages(chapterId)\n    }\n\n    fun saveCurrentPage(\n        pageSaveHelper: PageSaveHelper\n    ) {\n        val prevJob = pageSaveJob\n        pageSaveJob = launchLoadingJob(Dispatchers.Default) {\n            prevJob?.cancelAndJoin()\n            val state = checkNotNull(getCurrentState())\n            val currentManga = manga.requireValue()\n            val task = PageSaveHelper.Task(\n                manga = currentManga,\n                chapterId = state.chapterId,\n                pageNumber = state.page + 1,\n                page = checkNotNull(getCurrentPage()) { \"Cannot find current page\" },\n            )\n            val dest = pageSaveHelper.save(setOf(task))\n            onPageSaved.call(dest)\n        }\n    }\n\n    fun getCurrentPage(): MangaPage? {\n        val state = readingState.value ?: return null\n        return content.value.pages.find {\n            it.chapterId == state.chapterId && it.index == state.page\n        }?.toMangaPage()\n    }\n\n    fun switchChapter(id: Long, page: Int) {\n        val prevJob = loadingJob\n        loadingJob = launchLoadingJob(Dispatchers.Default) {\n            prevJob?.cancelAndJoin()\n            content.value = ReaderContent(emptyList(), null)\n            chaptersLoader.loadSingleChapter(id)\n            val newState = ReaderState(id, page, 0)\n            content.value = ReaderContent(chaptersLoader.snapshot(), newState)\n            saveCurrentState(newState)\n        }\n    }\n\n    fun switchChapterBy(delta: Int) {\n        val prevJob = loadingJob\n        loadingJob = launchLoadingJob(Dispatchers.Default) {\n            prevJob?.cancelAndJoin()\n            val prevState = readingState.requireValue()\n            val newChapterId = if (delta != 0) {\n                val allChapters = mangaDetails.requireValue().allChapters\n                var index = allChapters.indexOfFirst { x -> x.id == prevState.chapterId }\n                if (index < 0) {\n                    return@launchLoadingJob\n                }\n                index += delta\n                (allChapters.getOrNull(index) ?: return@launchLoadingJob).id\n            } else {\n                prevState.chapterId\n            }\n            content.value = ReaderContent(emptyList(), null)\n            chaptersLoader.loadSingleChapter(newChapterId)\n            val newState = ReaderState(\n                chapterId = newChapterId,\n                page = if (delta == 0) prevState.page else 0,\n                scroll = if (delta == 0) prevState.scroll else 0,\n            )\n            content.value = ReaderContent(chaptersLoader.snapshot(), newState)\n            saveCurrentState(newState)\n        }\n    }\n\n    @MainThread\n    fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) {\n        val prevJob = stateChangeJob\n        val pages = content.value.pages // capture immediately\n        stateChangeJob = launchJob(Dispatchers.Default) {\n            prevJob?.cancelAndJoin()\n            loadingJob?.join()\n            if (pages.size != content.value.pages.size) {\n                return@launchJob // TODO\n            }\n            val centerPos = (lowerPos + upperPos) / 2\n            pages.getOrNull(centerPos)?.let { page ->\n                readingState.update { cs ->\n                    cs?.copy(chapterId = page.chapterId, page = page.index)\n                }\n            }\n            notifyStateChanged()\n            if (pages.isEmpty() || loadingJob?.isActive == true) {\n                return@launchJob\n            }\n            ensureActive()\n            val autoLoadAllowed = readerMode.value != ReaderMode.WEBTOON || !isWebtoonPullGestureEnabled.value\n            if (autoLoadAllowed) {\n                if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {\n                    loadPrevNextChapter(pages.last().chapterId, isNext = true)\n                }\n                if (lowerPos <= BOUNDS_PAGE_OFFSET) {\n                    loadPrevNextChapter(pages.first().chapterId, isNext = false)\n                }\n            }\n            if (pageLoader.isPrefetchApplicable()) {\n                pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT))\n            }\n        }\n    }\n\n    fun toggleBookmark() {\n        if (bookmarkJob?.isActive == true) {\n            return\n        }\n        bookmarkJob = launchJob(Dispatchers.Default) {\n            loadingJob?.join()\n            val state = checkNotNull(getCurrentState())\n            if (isBookmarkAdded.value) {\n                val manga = requireManga()\n                bookmarksRepository.removeBookmark(manga.id, state.chapterId, state.page)\n                onShowToast.call(R.string.bookmark_removed)\n            } else {\n                val page = checkNotNull(getCurrentPage()) { \"Page not found\" }\n                val bookmark = Bookmark(\n                    manga = requireManga(),\n                    pageId = page.id,\n                    chapterId = state.chapterId,\n                    page = state.page,\n                    scroll = state.scroll,\n                    imageUrl = page.preview.ifNullOrEmpty { page.url },\n                    createdAt = Instant.now(),\n                    percent = computePercent(state.chapterId, state.page),\n                )\n                bookmarksRepository.addBookmark(bookmark)\n                onShowToast.call(R.string.bookmark_added)\n            }\n        }\n    }\n\n    fun setIncognitoMode(value: Boolean, dontAskAgain: Boolean) {\n        isIncognitoMode.value = value\n        if (dontAskAgain) {\n            settings.incognitoModeForNsfw = if (value) TriStateOption.ENABLED else TriStateOption.DISABLED\n        }\n    }\n\n    private fun loadImpl() {\n        loadingJob = launchLoadingJob(Dispatchers.Default + EventExceptionHandler(onLoadingError)) {\n            var exception: Exception? = null\n            var loadedDetails: MangaDetails? = null\n            try {\n                detailsLoadUseCase(intent, force = false)\n                    .collect { details ->\n                        loadedDetails = details\n                        if (mangaDetails.value == null) {\n                            mangaDetails.value = details\n                        }\n                        chaptersLoader.init(details)\n                        val manga = details.toManga()\n                        // obtain state\n                        if (readingState.value == null) {\n                            val newState = getStateFromIntent(manga)\n                            if (newState == null) {\n                                return@collect // manga not loaded yet if cannot get state\n                            }\n                            readingState.value = newState\n                            val mode = runCatchingCancellable {\n                                detectReaderModeUseCase(manga, newState)\n                            }.getOrDefault(settings.defaultReaderMode)\n                            val branch = chaptersLoader.peekChapter(newState.chapterId)?.branch\n                            selectedBranch.value = branch\n                            readerMode.value = mode\n                            try {\n                                chaptersLoader.loadSingleChapter(newState.chapterId)\n                            } catch (e: Exception) {\n                                readingState.value = null // try next time\n                                exception = e.mergeWith(exception)\n                                return@collect\n                            }\n                        }\n                        mangaDetails.value = details.filterChapters(selectedBranch.value)\n\n                        // save state\n                        if (!isIncognitoMode.firstNotNull()) {\n                            readingState.value?.let {\n                                val percent = computePercent(it.chapterId, it.page)\n                                historyUpdateUseCase(manga, it, percent)\n                            }\n                        }\n                        notifyStateChanged()\n                        content.value = ReaderContent(chaptersLoader.snapshot(), readingState.value)\n                    }\n            } catch (e: CancellationException) {\n                throw e\n            } catch (e: Exception) {\n                exception = e.mergeWith(exception)\n            }\n            if (readingState.value == null) {\n                val loadedManga = loadedDetails // for smart cast\n                if (loadedManga != null) {\n                    mangaDetails.value = loadedManga.filterChapters(selectedBranch.value)\n                }\n                val loadingError = when {\n                    exception != null -> exception\n                    loadedManga == null || !loadedManga.isLoaded -> null\n                    loadedManga.isRestricted -> EmptyMangaException(\n                        EmptyMangaReason.RESTRICTED,\n                        loadedManga.toManga(),\n                        null,\n                    )\n\n                    loadedManga.allChapters.isEmpty() -> EmptyMangaException(\n                        EmptyMangaReason.NO_CHAPTERS,\n                        loadedManga.toManga(),\n                        null,\n                    )\n\n                    else -> null\n                } ?: IllegalStateException(\"Unable to load manga. This should never happen. Please report\")\n                onLoadingError.call(loadingError)\n            } else exception?.let { e ->\n                // manga has been loaded but error occurred\n                errorEvent.call(e)\n            }\n        }\n    }\n\n    @AnyThread\n    private fun loadPrevNextChapter(currentId: Long, isNext: Boolean) {\n        val prevJob = loadingJob\n        loadingJob = launchLoadingJob(Dispatchers.Default) {\n            prevJob?.join()\n            chaptersLoader.loadPrevNextChapter(mangaDetails.requireValue(), currentId, isNext)\n            content.value = ReaderContent(chaptersLoader.snapshot(), null)\n        }\n    }\n\n    private fun <T> List<T>.trySublist(fromIndex: Int, toIndex: Int): List<T> {\n        val fromIndexBounded = fromIndex.coerceAtMost(lastIndex)\n        val toIndexBounded = toIndex.coerceIn(fromIndexBounded, lastIndex)\n        return if (fromIndexBounded == toIndexBounded) {\n            emptyList()\n        } else {\n            subList(fromIndexBounded, toIndexBounded)\n        }\n    }\n\n    @WorkerThread\n    private fun notifyStateChanged() {\n        val state = getCurrentState() ?: return\n        val chapter = chaptersLoader.peekChapter(state.chapterId) ?: return\n        val m = mangaDetails.value ?: return\n        val chapterIndex = m.chapters[chapter.branch]?.indexOfFirst { it.id == chapter.id } ?: -1\n        val newState = ReaderUiState(\n            mangaName = m.toManga().title,\n            chapter = chapter,\n            chapterIndex = chapterIndex,\n            chaptersTotal = m.chapters[chapter.branch].sizeOrZero(),\n            totalPages = chaptersLoader.getPagesCount(chapter.id),\n            currentPage = state.page,\n            percent = computePercent(state.chapterId, state.page),\n            incognito = isIncognitoMode.value == true,\n        )\n        uiState.value = newState\n        if (isIncognitoMode.value == false) {\n            statsCollector.onStateChanged(m.id, state)\n            discordRpc.updateRpc(m.toManga(), newState)\n        }\n    }\n\n    private fun computePercent(chapterId: Long, pageIndex: Int): Float {\n        val branch = chaptersLoader.peekChapter(chapterId)?.branch\n        val chapters = mangaDetails.value?.chapters?.get(branch) ?: return PROGRESS_NONE\n        val chaptersCount = chapters.size\n        val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId }\n        val pagesCount = chaptersLoader.getPagesCount(chapterId)\n        if (chaptersCount == 0 || pagesCount == 0) {\n            return PROGRESS_NONE\n        }\n        val pagePercent = (pageIndex + 1) / pagesCount.toFloat()\n        val ppc = 1f / chaptersCount\n        return ppc * chapterIndex + ppc * pagePercent\n    }\n\n    private fun observeIsWebtoonZoomEnabled() = settings.observeAsFlow(\n        key = AppSettings.KEY_WEBTOON_ZOOM,\n        valueProducer = { isWebtoonZoomEnabled },\n    )\n\n    private fun observeWebtoonZoomOut() = settings.observeAsFlow(\n        key = AppSettings.KEY_WEBTOON_ZOOM_OUT,\n        valueProducer = { defaultWebtoonZoomOut },\n    )\n\n    private fun getObserveIsZoomControlEnabled() = settings.observeAsFlow(\n        key = AppSettings.KEY_READER_ZOOM_BUTTONS,\n        valueProducer = { isReaderZoomButtonsEnabled },\n    )\n\n    private fun initIncognitoMode() {\n        if (isIncognitoMode.value != null) {\n            return\n        }\n        launchJob(Dispatchers.Default) {\n            interactor.observeIncognitoMode(manga)\n                .collect {\n                    when (it) {\n                        TriStateOption.ENABLED -> isIncognitoMode.value = true\n                        TriStateOption.ASK -> {\n                            onAskNsfwIncognito.call(Unit)\n                            return@collect\n                        }\n\n                        TriStateOption.DISABLED -> isIncognitoMode.value = false\n                    }\n                }\n        }\n    }\n\n    private suspend fun getStateFromIntent(manga: Manga): ReaderState? {\n        // check if we have at least some chapters loaded\n        if (manga.chapters.isNullOrEmpty()) {\n            return null\n        }\n        // specific state is requested\n        val requestedState: ReaderState? = savedStateHandle[ReaderIntent.EXTRA_STATE]\n        if (requestedState != null) {\n            return if (manga.findChapterById(requestedState.chapterId) != null) {\n                requestedState\n            } else {\n                null\n            }\n        }\n\n        val requestedBranch: String? = savedStateHandle[ReaderIntent.EXTRA_BRANCH]\n        // continue reading\n        val history = historyRepository.getOne(manga)\n        if (history != null) {\n            val chapter = manga.findChapterById(history.chapterId) ?: return null\n            // specified branch is requested\n            return if (ReaderIntent.EXTRA_BRANCH in savedStateHandle) {\n                if (chapter.branch == requestedBranch) {\n                    ReaderState(history)\n                } else {\n                    ReaderState(manga, requestedBranch)\n                }\n            } else {\n                ReaderState(history)\n            }\n        }\n\n        // start from beginning\n        val preferredBranch = requestedBranch ?: manga.getPreferredBranch(null)\n        return ReaderState(manga, preferredBranch)\n    }\n\n    private fun Exception.mergeWith(other: Exception?): Exception = if (other == null) {\n        this\n    } else {\n        other.addSuppressed(this)\n        other\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ScreenOrientationHelper.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.app.Activity\nimport android.content.pm.ActivityInfo\nimport android.content.res.Configuration\nimport android.database.ContentObserver\nimport android.os.Handler\nimport android.provider.Settings\nimport dagger.hilt.android.scopes.ActivityScoped\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.channels.trySendBlocking\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.onStart\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport javax.inject.Inject\n\n@ActivityScoped\nclass ScreenOrientationHelper @Inject constructor(\n\tprivate val activity: Activity,\n\tprivate val settings: AppSettings,\n) {\n\n\tval isAutoRotationEnabled: Boolean\n\t\tget() = Settings.System.getInt(\n\t\t\tactivity.contentResolver,\n\t\t\tSettings.System.ACCELEROMETER_ROTATION,\n\t\t\t0,\n\t\t) == 1\n\n\tvar isLandscape: Boolean\n\t\tget() = activity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE\n\t\tset(value) {\n\t\t\tactivity.requestedOrientation = if (value) {\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE\n\t\t\t} else {\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT\n\t\t\t}\n\t\t}\n\n\tvar isLocked: Boolean\n\t\tget() = activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED\n\t\tset(value) {\n\t\t\tactivity.requestedOrientation = if (value) {\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_LOCKED\n\t\t\t} else {\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED\n\t\t\t}\n\t\t}\n\n\tfun applySettings() {\n\t\tif (activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {\n\t\t\t// https://developer.android.com/reference/android/R.attr.html#screenOrientation\n\t\t\tactivity.requestedOrientation = settings.readerScreenOrientation\n\t\t}\n\t}\n\n\tfun observeAutoOrientation() = callbackFlow {\n\t\tval observer = object : ContentObserver(Handler(activity.mainLooper)) {\n\t\t\toverride fun onChange(selfChange: Boolean) {\n\t\t\t\ttrySendBlocking(isAutoRotationEnabled)\n\t\t\t}\n\t\t}\n\t\tactivity.contentResolver.registerContentObserver(\n\t\t\tSettings.System.CONTENT_URI, true, observer,\n\t\t)\n\t\tawaitClose {\n\t\t\tactivity.contentResolver.unregisterContentObserver(observer)\n\t\t}\n\t}.onStart {\n\t\temit(isAutoRotationEnabled)\n\t}.distinctUntilChanged()\n\t\t.conflate()\n\n\tfun toggleScreenOrientation(): Boolean = if (isAutoRotationEnabled) {\n\t\tval newValue = !isLocked\n\t\tisLocked = newValue\n\t\ttrue\n\t} else {\n\t\tisLandscape = !isLandscape\n\t\tfalse\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ScrollTimer.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.content.res.Resources\nimport android.os.SystemClock\nimport android.view.MotionEvent\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.yield\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\nimport kotlin.math.roundToLong\n\nprivate const val MAX_DELAY = 32L\nprivate const val MAX_SWITCH_DELAY = 10_000L\nprivate const val INTERACTION_SKIP_MS = 2_000L\nprivate const val SPEED_FACTOR_DELTA = 0.02f\n\nclass ScrollTimer @AssistedInject constructor(\n\t@Assisted resources: Resources,\n\t@Assisted private val listener: ReaderControlDelegate.OnInteractionListener,\n\t@Assisted lifecycleOwner: LifecycleOwner,\n\tsettings: AppSettings,\n) {\n\n\tprivate val coroutineScope = lifecycleOwner.lifecycleScope\n\tprivate var job: Job? = null\n\tprivate var delayMs: Long = 10L\n\tvar pageSwitchDelay: Long = 100L\n\t\tprivate set\n\tprivate var resumeAt = 0L\n\tprivate var isTouchDown = MutableStateFlow(false)\n\tprivate val isRunning = MutableStateFlow(false)\n\tprivate val scrollDelta = resources.resolveDp(1)\n\n\tval isActive: StateFlow<Boolean>\n\t\tget() = isRunning\n\n\tinit {\n\t\tsettings.observeAsFlow(AppSettings.KEY_READER_AUTOSCROLL_SPEED) {\n\t\t\treaderAutoscrollSpeed\n\t\t}.flowOn(Dispatchers.Default)\n\t\t\t.onEach {\n\t\t\t\tonSpeedChanged(it)\n\t\t\t}.launchIn(coroutineScope)\n\t}\n\n\tfun setActive(value: Boolean) {\n\t\tif (isRunning.value != value) {\n\t\t\tisRunning.value = value\n\t\t\trestartJob()\n\t\t}\n\t}\n\n\tfun onUserInteraction() {\n\t\tresumeAt = SystemClock.elapsedRealtime() + INTERACTION_SKIP_MS\n\t}\n\n\tfun onTouchEvent(event: MotionEvent) {\n\t\twhen (event.actionMasked) {\n\t\t\tMotionEvent.ACTION_DOWN -> {\n\t\t\t\tisTouchDown.value = true\n\t\t\t}\n\n\t\t\tMotionEvent.ACTION_UP,\n\t\t\tMotionEvent.ACTION_CANCEL -> {\n\t\t\t\tisTouchDown.value = false\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onSpeedChanged(speed: Float) {\n\t\tif (speed <= 0f) {\n\t\t\tdelayMs = 0L\n\t\t\tpageSwitchDelay = 0L\n\t\t} else {\n\t\t\tval speedFactor = 1f - speed\n\t\t\tdelayMs = (MAX_DELAY * speedFactor).roundToLong()\n\t\t\tpageSwitchDelay = (MAX_SWITCH_DELAY * speedFactor).roundToLong()\n\t\t}\n\t\tif ((job == null) != (delayMs == 0L)) {\n\t\t\trestartJob()\n\t\t}\n\t}\n\n\tprivate fun restartJob() {\n\t\tjob?.cancel()\n\t\tresumeAt = 0L\n\t\tif (!isRunning.value || delayMs == 0L) {\n\t\t\tjob = null\n\t\t\treturn\n\t\t}\n\t\tjob = coroutineScope.launch {\n\t\t\tvar accumulator = 0L\n\t\t\tvar speedFactor = 1f\n\t\t\twhile (isActive) {\n\t\t\t\tif (isPaused()) {\n\t\t\t\t\tspeedFactor = (speedFactor - SPEED_FACTOR_DELTA).coerceAtLeast(0f)\n\t\t\t\t} else if (speedFactor < 1f) {\n\t\t\t\t\tspeedFactor = (speedFactor + SPEED_FACTOR_DELTA).coerceAtMost(1f)\n\t\t\t\t}\n\t\t\t\tif (speedFactor == 1f) {\n\t\t\t\t\tdelay(delayMs)\n\t\t\t\t} else if (speedFactor == 0f) {\n\t\t\t\t\tdelayUntilResumed()\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\tdelay((delayMs * (1f + speedFactor * 2)).toLong())\n\t\t\t\t}\n\t\t\t\tif (!listener.isReaderResumed()) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (!listener.scrollBy(scrollDelta, false)) {\n\t\t\t\t\taccumulator += delayMs\n\t\t\t\t}\n\t\t\t\tif (accumulator >= pageSwitchDelay) {\n\t\t\t\t\tlistener.switchPageBy(1)\n\t\t\t\t\taccumulator -= pageSwitchDelay\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun isPaused(): Boolean {\n\t\treturn isTouchDown.value || resumeAt > SystemClock.elapsedRealtime()\n\t}\n\n\tprivate suspend fun delayUntilResumed() {\n\t\twhile (isPaused()) {\n\t\t\tval delayTime = resumeAt - SystemClock.elapsedRealtime()\n\t\t\tif (delayTime > 0) {\n\t\t\t\tdelay(delayTime)\n\t\t\t} else {\n\t\t\t\tyield()\n\t\t\t}\n\t\t\tisTouchDown.first { !it }\n\t\t}\n\t}\n\n\t@AssistedFactory\n\tinterface Factory {\n\n\t\tfun create(\n\t\t\tresources: Resources,\n\t\t\tlifecycleOwner: LifecycleOwner,\n\t\t\tlistener: ReaderControlDelegate.OnInteractionListener,\n\t\t): ScrollTimer\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ScrollTimerControlView.kt",
    "content": "package org.koitharu.kotatsu.reader.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.CompoundButton\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport androidx.transition.Slide\nimport androidx.transition.TransitionManager\nimport com.google.android.material.slider.LabelFormatter\nimport com.google.android.material.slider.Slider\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.parentView\nimport org.koitharu.kotatsu.databinding.ViewScrollTimerBinding\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport kotlin.math.abs\n\n@AndroidEntryPoint\nclass ScrollTimerControlView @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null\n) : ConstraintLayout(context, attrs), CompoundButton.OnCheckedChangeListener, Slider.OnChangeListener,\n\tView.OnClickListener, LabelFormatter {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tvar onVisibilityChangeListener: OnVisibilityChangeListener? = null\n\n\tprivate val binding = ViewScrollTimerBinding.inflate(LayoutInflater.from(context), this)\n\n\tprivate var scrollTimer: ScrollTimer? = null\n\tprivate var labelPattern = context.getString(R.string.speed_value)\n\tprivate var readerMode: ReaderMode = ReaderMode.STANDARD\n\n\tinit {\n\t\tbinding.switchScrollTimer.setOnCheckedChangeListener(this)\n\t\tbinding.sliderTimer.addOnChangeListener(this)\n\t\tbinding.buttonFab.setOnClickListener(this)\n\t\tbinding.sliderTimer.setLabelFormatter(this)\n\t\tbinding.buttonClose.setOnClickListener(this)\n\t\tbinding.buttonFab.isGone = resources.getBoolean(R.bool.is_tablet)\n\t\tsetPadding(0, 0, 0, context.resources.getDimensionPixelOffset(R.dimen.margin_normal))\n\t}\n\n\tfun attach(timer: ScrollTimer, lifecycleOwner: LifecycleOwner) {\n\t\tscrollTimer = timer\n\t\ttimer.isActive.observe(lifecycleOwner) {\n\t\t\tbinding.switchScrollTimer.setOnCheckedChangeListener(null)\n\t\t\tbinding.switchScrollTimer.isChecked = it\n\t\t\tbinding.switchScrollTimer.setOnCheckedChangeListener(this)\n\t\t}\n\t\tsettings.observeAsStateFlow(\n\t\t\tscope = lifecycleOwner.lifecycleScope + Dispatchers.Default,\n\t\t\tkey = AppSettings.KEY_READER_AUTOSCROLL_SPEED,\n\t\t\tvalueProducer = { readerAutoscrollSpeed },\n\t\t).observe(lifecycleOwner) {\n\t\t\tif (abs(it - binding.sliderTimer.value) > 0.0001) {\n\t\t\t\tbinding.sliderTimer.value = it.coerceIn(\n\t\t\t\t\tbinding.sliderTimer.valueFrom,\n\t\t\t\t\tbinding.sliderTimer.valueTo,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tsettings.observeAsStateFlow(\n\t\t\tscope = lifecycleOwner.lifecycleScope + Dispatchers.Default,\n\t\t\tkey = AppSettings.KEY_READER_AUTOSCROLL_FAB,\n\t\t\tvalueProducer = { isReaderAutoscrollFabVisible },\n\t\t).observe(lifecycleOwner) {\n\t\t\tbinding.buttonFab.isChecked = it\n\t\t}\n\t\tupdateDescription()\n\t}\n\n\tfun onReaderModeChanged(mode: ReaderMode) {\n\t\treaderMode = mode\n\t\tupdateDescription()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_close -> hide()\n\t\t\tR.id.button_fab -> settings.isReaderAutoscrollFabVisible = !settings.isReaderAutoscrollFabVisible\n\t\t}\n\t}\n\n\toverride fun getFormattedValue(value: Float): String {\n\t\tval valueFrom = binding.sliderTimer.valueFrom\n\t\tval valueTo = binding.sliderTimer.valueTo\n\t\tval percent = (value - valueFrom) / (valueTo - valueFrom)\n\t\treturn labelPattern.format(0.1 + percent * 10) // just something to display\n\t}\n\n\toverride fun onValueChange(\n\t\tslider: Slider,\n\t\tvalue: Float,\n\t\tfromUser: Boolean\n\t) {\n\t\tif (fromUser) {\n\t\t\tsettings.readerAutoscrollSpeed = value\n\t\t}\n\t\tupdateDescription()\n\t}\n\n\toverride fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {\n\t\tscrollTimer?.setActive(isChecked)\n\t}\n\n\toverride fun setVisibility(visibility: Int) {\n\t\tsuper.setVisibility(visibility)\n\t\tonVisibilityChangeListener?.onVisibilityChanged(this, visibility)\n\t}\n\n\tfun show() {\n\t\tsetupVisibilityTransition()\n\t\tisVisible = true\n\t}\n\n\tfun hide() {\n\t\tsetupVisibilityTransition()\n\t\tisVisible = false\n\t}\n\n\tfun showOrHide() {\n\t\tsetupVisibilityTransition()\n\t\tisVisible = !isVisible\n\t}\n\n\tprivate fun setupVisibilityTransition() {\n\t\tif (context.isAnimationsEnabled) {\n\t\t\tval sceneRoot = parentView ?: return\n\t\t\tval transition = Slide()\n\t\t\ttransition.addTarget(this)\n\t\t\tTransitionManager.beginDelayedTransition(sceneRoot, transition)\n\t\t}\n\t}\n\n\tprivate fun updateDescription() {\n\t\tval timePerPage = scrollTimer?.pageSwitchDelay ?: 0L\n\t\tif (timePerPage <= 0L || readerMode == ReaderMode.WEBTOON) {\n\t\t\tbinding.textViewDescription.isVisible = false\n\t\t} else {\n\t\t\tbinding.textViewDescription.text = context.getString(\n\t\t\t\tR.string.page_switch_timer,\n\t\t\t\tTimeUnit.MILLISECONDS.toSeconds((scrollTimer ?: return).pageSwitchDelay),\n\t\t\t)\n\t\t\tbinding.textViewDescription.isVisible = true\n\t\t}\n\t}\n\n\tfun interface OnVisibilityChangeListener {\n\n\t\tfun onVisibilityChanged(v: View, visibility: Int)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.colorfilter\n\nimport android.content.res.Resources\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.CompoundButton\nimport android.widget.ImageView\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport coil3.ImageLoader\nimport coil3.asDrawable\nimport coil3.request.ErrorResult\nimport coil3.request.ImageRequest\nimport coil3.request.SuccessResult\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.slider.LabelFormatter\nimport com.google.android.material.slider.Slider\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.setChecked\nimport org.koitharu.kotatsu.core.util.ext.setValueRounded\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener\nimport org.koitharu.kotatsu.databinding.ActivityColorFilterBinding\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.util.format\nimport org.koitharu.kotatsu.reader.domain.ReaderColorFilter\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ColorFilterConfigActivity :\n\tBaseActivity<ActivityColorFilterBinding>(),\n\tSlider.OnChangeListener,\n\tView.OnClickListener, CompoundButton.OnCheckedChangeListener {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate val viewModel: ColorFilterConfigViewModel by viewModels()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityColorFilterBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tviewBinding.sliderBrightness.addOnChangeListener(this)\n\t\tviewBinding.sliderContrast.addOnChangeListener(this)\n\t\tval formatter = PercentLabelFormatter(resources)\n\t\tviewBinding.sliderContrast.setLabelFormatter(formatter)\n\t\tviewBinding.sliderBrightness.setLabelFormatter(formatter)\n\t\tviewBinding.switchInvert.setOnCheckedChangeListener(this)\n\t\tviewBinding.switchGrayscale.setOnCheckedChangeListener(this)\n\t\tviewBinding.switchBook.setOnCheckedChangeListener(this)\n\t\tviewBinding.buttonDone.setOnClickListener(this)\n\t\tviewBinding.buttonReset.setOnClickListener(this)\n\n\t\tonBackPressedDispatcher.addCallback(ColorFilterConfigBackPressedDispatcher(this, viewModel))\n\n\t\tviewModel.colorFilter.observe(this, this::onColorFilterChanged)\n\t\tviewModel.isLoading.observe(this, this::onLoadingChanged)\n\t\tviewModel.onDismiss.observeEvent(this) {\n\t\t\tfinishAfterTransition()\n\t\t}\n\t\tloadPreview(viewModel.preview)\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.root.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {\n\t\tif (fromUser) {\n\t\t\twhen (slider.id) {\n\t\t\t\tR.id.slider_brightness -> viewModel.setBrightness(value)\n\t\t\t\tR.id.slider_contrast -> viewModel.setContrast(value)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {\n\t\twhen (buttonView.id) {\n\t\t\tR.id.switch_invert -> viewModel.setInversion(isChecked)\n\t\t\tR.id.switch_grayscale -> viewModel.setGrayscale(isChecked)\n\t\t\tR.id.switch_book -> viewModel.setBookEffect(isChecked)\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_done -> showSaveConfirmation()\n\t\t\tR.id.button_reset -> viewModel.reset()\n\t\t}\n\t}\n\n\tfun showSaveConfirmation() {\n\t\tMaterialAlertDialogBuilder(this)\n\t\t\t.setTitle(R.string.apply)\n\t\t\t.setMessage(R.string.color_correction_apply_text)\n\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t\t\t.setPositiveButton(R.string.this_manga) { _, _ ->\n\t\t\t\tviewModel.save()\n\t\t\t}.setNeutralButton(R.string.globally) { _, _ ->\n\t\t\t\tviewModel.saveGlobally()\n\t\t\t}.show()\n\t}\n\n\tprivate fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) {\n\t\tviewBinding.sliderBrightness.setValueRounded(readerColorFilter?.brightness ?: 0f)\n\t\tviewBinding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f)\n\t\tviewBinding.switchInvert.setChecked(readerColorFilter?.isInverted == true, false)\n\t\tviewBinding.switchGrayscale.setChecked(readerColorFilter?.isGrayscale == true, false)\n\t\tviewBinding.switchBook.setChecked(readerColorFilter?.isBookBackground == true, false)\n\t\tviewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()\n\t}\n\n\tprivate fun loadPreview(page: MangaPage) = with(viewBinding.imageViewBefore) {\n\t\taddImageRequestListener(\n\t\t\tImageRequestIndicatorListener(\n\t\t\t\tlistOf(\n\t\t\t\t\tviewBinding.progressBefore,\n\t\t\t\t\tviewBinding.progressAfter,\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t\taddImageRequestListener(ShadowImageListener(viewBinding.imageViewAfter))\n\t\tsetImageAsync(page)\n\t}\n\n\tprivate fun onLoadingChanged(isLoading: Boolean) {\n\t\tviewBinding.sliderContrast.isEnabled = !isLoading\n\t\tviewBinding.sliderBrightness.isEnabled = !isLoading\n\t\tviewBinding.switchInvert.isEnabled = !isLoading\n\t\tviewBinding.switchGrayscale.isEnabled = !isLoading\n\t\tviewBinding.buttonDone.isEnabled = !isLoading\n\t}\n\n\tprivate class PercentLabelFormatter(resources: Resources) : LabelFormatter {\n\n\t\tprivate val pattern = resources.getString(R.string.percent_string_pattern)\n\n\t\toverride fun getFormattedValue(value: Float): String {\n\t\t\tval percent = ((value + 1f) * 100).format(0)\n\t\t\treturn pattern.format(percent)\n\t\t}\n\t}\n\n\tprivate class ShadowImageListener(\n\t\tprivate val imageView: ImageView\n\t) : ImageRequest.Listener {\n\n\t\toverride fun onError(request: ImageRequest, result: ErrorResult) {\n\t\t\tsuper.onError(request, result)\n\t\t\timageView.setImageDrawable(result.image?.asDrawable(imageView.resources))\n\t\t}\n\n\t\toverride fun onStart(request: ImageRequest) {\n\t\t\tsuper.onStart(request)\n\t\t\timageView.setImageDrawable(request.placeholder()?.asDrawable(imageView.resources))\n\t\t}\n\n\t\toverride fun onSuccess(request: ImageRequest, result: SuccessResult) {\n\t\t\tsuper.onSuccess(request, result)\n\t\t\timageView.setImageDrawable(result.image.asDrawable(imageView.resources))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigBackPressedDispatcher.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.colorfilter\n\nimport android.content.DialogInterface\nimport androidx.activity.OnBackPressedCallback\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.call\n\nclass ColorFilterConfigBackPressedDispatcher(\n\tprivate val activity: ColorFilterConfigActivity,\n\tprivate val viewModel: ColorFilterConfigViewModel,\n) : OnBackPressedCallback(true), DialogInterface.OnClickListener {\n\n\toverride fun handleOnBackPressed() {\n\t\tif (viewModel.isChanged) {\n\t\t\tshowConfirmation()\n\t\t} else {\n\t\t\tviewModel.onDismiss.call(Unit)\n\t\t}\n\t}\n\n\toverride fun onClick(dialog: DialogInterface, which: Int) {\n\t\twhen (which) {\n\t\t\tDialogInterface.BUTTON_NEGATIVE -> viewModel.onDismiss.call(Unit)\n\t\t\tDialogInterface.BUTTON_NEUTRAL -> dialog.dismiss()\n\t\t\tDialogInterface.BUTTON_POSITIVE -> activity.showSaveConfirmation()\n\t\t}\n\t}\n\n\tprivate fun showConfirmation() {\n\t\tMaterialAlertDialogBuilder(activity)\n\t\t\t.setTitle(R.string.color_correction)\n\t\t\t.setMessage(R.string.text_unsaved_changes_prompt)\n\t\t\t.setNegativeButton(R.string.discard, this)\n\t\t\t.setNeutralButton(android.R.string.cancel, this)\n\t\t\t.setPositiveButton(R.string.save, this)\n\t\t\t.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.colorfilter\n\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPage\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.reader.domain.ReaderColorFilter\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ColorFilterConfigViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val settings: AppSettings,\n\tprivate val mangaDataRepository: MangaDataRepository,\n) : BaseViewModel() {\n\n\tprivate val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga\n\n\tprivate var initialColorFilter: ReaderColorFilter? = null\n\tval colorFilter = MutableStateFlow<ReaderColorFilter?>(null)\n\tval onDismiss = MutableEventFlow<Unit>()\n\tval preview = savedStateHandle.require<ParcelableMangaPage>(AppRouter.KEY_PAGES).page\n\n\tval isChanged: Boolean\n\t\tget() = colorFilter.value != initialColorFilter\n\n\tinit {\n\t\tlaunchLoadingJob {\n\t\t\tinitialColorFilter = mangaDataRepository.getColorFilter(manga.id) ?: settings.readerColorFilter\n\t\t\tcolorFilter.value = initialColorFilter\n\t\t}\n\t}\n\n\tfun setBrightness(brightness: Float) {\n\t\tupdateColorFilter { it.copy(brightness = brightness) }\n\t}\n\n\tfun setContrast(contrast: Float) {\n\t\tupdateColorFilter { it.copy(contrast = contrast) }\n\t}\n\n\tfun setInversion(invert: Boolean) {\n\t\tupdateColorFilter { it.copy(isInverted = invert) }\n\t}\n\n\tfun setGrayscale(grayscale: Boolean) {\n\t\tupdateColorFilter { it.copy(isGrayscale = grayscale) }\n\t}\n\n\tfun setBookEffect(book: Boolean) {\n\t\tupdateColorFilter { it.copy(isBookBackground = book) }\n\t}\n\n\tfun reset() {\n\t\tcolorFilter.value = null\n\t}\n\n\tfun save() {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tmangaDataRepository.saveColorFilter(manga, colorFilter.value)\n\t\t\tonDismiss.call(Unit)\n\t\t}\n\t}\n\n\tfun saveGlobally() {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tsettings.readerColorFilter = colorFilter.value\n\t\t\tmangaDataRepository.resetColorFilters()\n\t\t\tonDismiss.call(Unit)\n\t\t}\n\t}\n\n\tprivate inline fun updateColorFilter(block: (ReaderColorFilter) -> ReaderColorFilter) {\n\t\tcolorFilter.value = block(\n\t\t\tcolorFilter.value ?: ReaderColorFilter.EMPTY,\n\t\t).takeUnless { it.isEmpty }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ImageServerDelegate.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.config\n\nimport android.content.Context\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.parsers.config.ConfigKey\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.mapToArray\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull\nimport org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy\nimport kotlin.coroutines.resume\n\nclass ImageServerDelegate(\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val mangaSource: MangaSource?,\n) {\n\n\tprivate val repositoryLazy = suspendLazy {\n\t\tmangaRepositoryFactory.create(checkNotNull(mangaSource)) as ParserMangaRepository\n\t}\n\n\tsuspend fun isAvailable() = withContext(Dispatchers.Default) {\n\t\trepositoryLazy.getOrNull()?.let { repository ->\n\t\t\trepository.getConfigKeys().any { it is ConfigKey.PreferredImageServer }\n\t\t} == true\n\t}\n\n\tsuspend fun getValue(): String? = withContext(Dispatchers.Default) {\n\t\trepositoryLazy.getOrNull()?.let { repository ->\n\t\t\tval key = repository.getConfigKeys().firstNotNullOfOrNull { it as? ConfigKey.PreferredImageServer }\n\t\t\tif (key != null) {\n\t\t\t\tkey.presetValues[repository.getConfig()[key]]\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun showDialog(context: Context): Boolean {\n\t\tval repository = withContext(Dispatchers.Default) {\n\t\t\trepositoryLazy.getOrNull()\n\t\t} ?: return false\n\t\tval key = repository.getConfigKeys().firstNotNullOfOrNull {\n\t\t\tit as? ConfigKey.PreferredImageServer\n\t\t} ?: return false\n\t\tval entries = key.presetValues.values.mapToArray {\n\t\t\tit ?: context.getString(R.string.automatic)\n\t\t}\n\t\tval entryValues = key.presetValues.keys.toTypedArray()\n\t\tval config = repository.getConfig()\n\t\tval initialValue = config[key]\n\t\tvar currentValue = initialValue\n\t\tval changed = suspendCancellableCoroutine { cont ->\n\t\t\tval dialog = MaterialAlertDialogBuilder(context)\n\t\t\t\t.setTitle(R.string.image_server)\n\t\t\t\t.setCancelable(true)\n\t\t\t\t.setSingleChoiceItems(entries, entryValues.indexOf(initialValue)) { _, i ->\n\t\t\t\t\tcurrentValue = entryValues[i]\n\t\t\t\t}.setNegativeButton(android.R.string.cancel) { dialog, _ ->\n\t\t\t\t\tdialog.cancel()\n\t\t\t\t}.setPositiveButton(android.R.string.ok) { _, _ ->\n\t\t\t\t\tif (currentValue != initialValue) {\n\t\t\t\t\t\tconfig[key] = currentValue\n\t\t\t\t\t\tcont.resume(true)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcont.resume(false)\n\t\t\t\t\t}\n\t\t\t\t}.setOnCancelListener {\n\t\t\t\t\tcont.resume(false)\n\t\t\t\t}.create()\n\t\t\tdialog.show()\n\t\t\tcont.invokeOnCancellation {\n\t\t\t\tdialog.cancel()\n\t\t\t}\n\t\t}\n\t\tif (changed) {\n\t\t\trepository.invalidateCache()\n\t\t}\n\t\treturn changed\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.config\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.CompoundButton\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.activityViewModels\nimport androidx.transition.TransitionManager\nimport com.google.android.material.button.MaterialButtonToggleGroup\nimport com.google.android.material.slider.Slider\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.findParentCallback\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.setValueRounded\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\nimport org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter\nimport org.koitharu.kotatsu.databinding.SheetReaderConfigBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.ReaderViewModel\nimport org.koitharu.kotatsu.reader.ui.ScreenOrientationHelper\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ReaderConfigSheet :\n    BaseAdaptiveSheet<SheetReaderConfigBinding>(),\n    View.OnClickListener,\n    MaterialButtonToggleGroup.OnButtonCheckedListener,\n    CompoundButton.OnCheckedChangeListener,\n    Slider.OnChangeListener {\n\n    private val viewModel by activityViewModels<ReaderViewModel>()\n\n    @Inject\n    lateinit var orientationHelper: ScreenOrientationHelper\n\n    @Inject\n    lateinit var mangaRepositoryFactory: MangaRepository.Factory\n\n    @Inject\n    lateinit var pageLoader: PageLoader\n\n    private lateinit var mode: ReaderMode\n    private lateinit var imageServerDelegate: ImageServerDelegate\n\n    @Inject\n    lateinit var settings: AppSettings\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        mode = arguments?.getInt(AppRouter.KEY_READER_MODE)\n            ?.let { ReaderMode.valueOf(it) }\n            ?: ReaderMode.STANDARD\n        imageServerDelegate = ImageServerDelegate(\n            mangaRepositoryFactory = mangaRepositoryFactory,\n            mangaSource = viewModel.getMangaOrNull()?.source,\n        )\n    }\n\n    override fun onCreateViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n    ): SheetReaderConfigBinding {\n        return SheetReaderConfigBinding.inflate(inflater, container, false)\n    }\n\n    override fun onViewBindingCreated(\n        binding: SheetReaderConfigBinding,\n        savedInstanceState: Bundle?,\n    ) {\n        super.onViewBindingCreated(binding, savedInstanceState)\n        observeScreenOrientation()\n        binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD\n        binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED\n        binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON\n        binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL\n        binding.switchDoubleReader.isChecked = settings.isReaderDoubleOnLandscape\n        binding.switchDoubleReader.isEnabled = mode == ReaderMode.STANDARD || mode == ReaderMode.REVERSED\n        binding.switchDoubleFoldable.isChecked = settings.isReaderDoubleOnFoldable\n        binding.switchDoubleFoldable.isEnabled = binding.switchDoubleReader.isEnabled\n        binding.sliderDoubleSensitivity.setValueRounded(settings.readerDoublePagesSensitivity * 100f)\n        binding.sliderDoubleSensitivity.setLabelFormatter(IntPercentLabelFormatter(binding.root.context))\n        binding.adjustSensitivitySlider(withAnimation = false)\n\n        binding.checkableGroup.addOnButtonCheckedListener(this)\n        binding.buttonSavePage.setOnClickListener(this)\n        binding.buttonScreenRotate.setOnClickListener(this)\n        binding.buttonSettings.setOnClickListener(this)\n        binding.buttonImageServer.setOnClickListener(this)\n        binding.buttonColorFilter.setOnClickListener(this)\n        binding.buttonScrollTimer.setOnClickListener(this)\n        binding.buttonBookmark.setOnClickListener(this)\n        binding.switchDoubleReader.setOnCheckedChangeListener(this)\n        binding.switchDoubleFoldable.setOnCheckedChangeListener(this)\n        binding.sliderDoubleSensitivity.addOnChangeListener(this)\n\n        viewModel.isBookmarkAdded.observe(viewLifecycleOwner) {\n            binding.buttonBookmark.setText(if (it) R.string.bookmark_remove else R.string.bookmark_add)\n            binding.buttonBookmark.setCompoundDrawablesRelativeWithIntrinsicBounds(\n                if (it) R.drawable.ic_bookmark_checked else R.drawable.ic_bookmark, 0, 0, 0,\n            )\n        }\n\n        viewLifecycleScope.launch {\n            val isAvailable = imageServerDelegate.isAvailable()\n            if (isAvailable) {\n                bindImageServerTitle()\n            }\n            binding.buttonImageServer.isVisible = isAvailable\n        }\n    }\n\n    override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n        val typeMask = WindowInsetsCompat.Type.systemBars()\n        viewBinding?.scrollView?.updatePadding(\n            bottom = insets.getInsets(typeMask).bottom,\n        )\n        return insets.consume(v, typeMask, bottom = true)\n    }\n\n    override fun onClick(v: View) {\n        when (v.id) {\n            R.id.button_settings -> {\n                router.openReaderSettings()\n                dismissAllowingStateLoss()\n            }\n\n            R.id.button_scroll_timer -> {\n                findParentCallback(Callback::class.java)?.onScrollTimerClick(false) ?: return\n                dismissAllowingStateLoss()\n            }\n\n            R.id.button_save_page -> {\n                findParentCallback(Callback::class.java)?.onSavePageClick() ?: return\n                dismissAllowingStateLoss()\n            }\n\n            R.id.button_screen_rotate -> {\n                orientationHelper.isLandscape = !orientationHelper.isLandscape\n            }\n\n            R.id.button_bookmark -> {\n                viewModel.toggleBookmark()\n            }\n\n            R.id.button_color_filter -> {\n                val page = viewModel.getCurrentPage() ?: return\n                val manga = viewModel.getMangaOrNull() ?: return\n                router.openColorFilterConfig(manga, page)\n            }\n\n            R.id.button_image_server -> viewLifecycleScope.launch {\n                if (imageServerDelegate.showDialog(v.context)) {\n                    bindImageServerTitle()\n                    pageLoader.invalidate(clearCache = true)\n                    viewModel.switchChapterBy(0)\n                }\n            }\n        }\n    }\n\n    override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {\n        when (buttonView.id) {\n            R.id.switch_screen_lock_rotation -> {\n                orientationHelper.isLocked = isChecked\n            }\n\n            R.id.switch_double_reader -> {\n                settings.isReaderDoubleOnLandscape = isChecked\n                viewBinding?.adjustSensitivitySlider(withAnimation = true)\n                findParentCallback(Callback::class.java)?.onDoubleModeChanged(isChecked)\n            }\n\n            R.id.switch_double_foldable -> {\n                settings.isReaderDoubleOnFoldable = isChecked\n                // Re-evaluate double-page considering foldable state and current manual toggle\n                findParentCallback(Callback::class.java)?.onDoubleModeChanged(settings.isReaderDoubleOnLandscape)\n            }\n        }\n    }\n\n    override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {\n        settings.readerDoublePagesSensitivity = value / 100f\n    }\n\n    override fun onButtonChecked(\n        group: MaterialButtonToggleGroup?,\n        checkedId: Int,\n        isChecked: Boolean,\n    ) {\n        if (!isChecked) {\n            return\n        }\n        val newMode = when (checkedId) {\n            R.id.button_standard -> ReaderMode.STANDARD\n            R.id.button_webtoon -> ReaderMode.WEBTOON\n            R.id.button_reversed -> ReaderMode.REVERSED\n            R.id.button_vertical -> ReaderMode.VERTICAL\n            else -> return\n        }\n        viewBinding?.run {\n            switchDoubleReader.isEnabled = newMode == ReaderMode.STANDARD || newMode == ReaderMode.REVERSED\n            switchDoubleFoldable.isEnabled = switchDoubleReader.isEnabled\n            adjustSensitivitySlider(withAnimation = true)\n        }\n        if (newMode == mode) {\n            return\n        }\n        findParentCallback(Callback::class.java)?.onReaderModeChanged(newMode) ?: return\n        mode = newMode\n    }\n\n    private fun observeScreenOrientation() {\n        orientationHelper.observeAutoOrientation()\n            .onEach {\n                with(requireViewBinding()) {\n                    buttonScreenRotate.isGone = it\n                    switchScreenLockRotation.isVisible = it\n                    updateOrientationLockSwitch()\n                }\n            }.launchIn(viewLifecycleScope)\n    }\n\n    private fun updateOrientationLockSwitch() {\n        val switch = viewBinding?.switchScreenLockRotation ?: return\n        switch.setOnCheckedChangeListener(null)\n        switch.isChecked = orientationHelper.isLocked\n        switch.setOnCheckedChangeListener(this)\n    }\n\n    private suspend fun bindImageServerTitle() {\n        viewBinding?.buttonImageServer?.text = getString(\n            R.string.inline_preference_pattern,\n            getString(R.string.image_server),\n            imageServerDelegate.getValue() ?: getString(R.string.automatic),\n        )\n    }\n\n    private fun SheetReaderConfigBinding.adjustSensitivitySlider(withAnimation: Boolean) {\n        val isSubOptionsVisible = switchDoubleReader.isEnabled && switchDoubleReader.isChecked\n        val needTransition = withAnimation && (\n            (isSubOptionsVisible != sliderDoubleSensitivity.isVisible) ||\n                (isSubOptionsVisible != textDoubleSensitivity.isVisible) ||\n                (isSubOptionsVisible != switchDoubleFoldable.isVisible)\n            )\n        if (needTransition) {\n            TransitionManager.beginDelayedTransition(layoutMain)\n        }\n        sliderDoubleSensitivity.isVisible = isSubOptionsVisible\n        textDoubleSensitivity.isVisible = isSubOptionsVisible\n        switchDoubleFoldable.isVisible = isSubOptionsVisible\n    }\n\n    interface Callback {\n\n        fun onReaderModeChanged(mode: ReaderMode)\n\n        fun onDoubleModeChanged(isEnabled: Boolean)\n\n        fun onSavePageClick()\n\n        fun onScrollTimerClick(isLongClick: Boolean)\n\n        fun onBookmarkClick()\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.config\n\nimport android.graphics.Bitmap\nimport android.view.View\nimport androidx.annotation.CheckResult\nimport androidx.collection.scatterSetOf\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder\nimport com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder\nimport com.davemorrissey.labs.subscaleview.decoder.SkiaPooledImageRegionDecoder\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.filter\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.core.model.ZoomMode\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderBackground\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.util.MediatorStateFlow\nimport org.koitharu.kotatsu.core.util.ext.isLowRamDevice\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.reader.domain.ReaderColorFilter\n\ndata class ReaderSettings(\n\tval zoomMode: ZoomMode,\n\tval background: ReaderBackground,\n\tval colorFilter: ReaderColorFilter?,\n\tval isReaderOptimizationEnabled: Boolean,\n\tval bitmapConfig: Bitmap.Config,\n\tval isPagesNumbersEnabled: Boolean,\n\tval isPagesCropEnabledStandard: Boolean,\n\tval isPagesCropEnabledWebtoon: Boolean,\n) {\n\n\tprivate constructor(settings: AppSettings, colorFilterOverride: ReaderColorFilter?) : this(\n\t\tzoomMode = settings.zoomMode,\n\t\tbackground = settings.readerBackground,\n\t\tcolorFilter = colorFilterOverride?.takeUnless { it.isEmpty } ?: settings.readerColorFilter,\n\t\tisReaderOptimizationEnabled = settings.isReaderOptimizationEnabled,\n\t\tbitmapConfig = if (settings.is32BitColorsEnabled) {\n\t\t\tBitmap.Config.ARGB_8888\n\t\t} else {\n\t\t\tBitmap.Config.RGB_565\n\t\t},\n\t\tisPagesNumbersEnabled = settings.isPagesNumbersEnabled,\n\t\tisPagesCropEnabledStandard = settings.isPagesCropEnabled(ReaderMode.STANDARD),\n\t\tisPagesCropEnabledWebtoon = settings.isPagesCropEnabled(ReaderMode.WEBTOON),\n\t)\n\n\tfun applyBackground(view: View) {\n\t\tview.background = background.resolve(view.context)\n\t\tview.backgroundTintList = if (background.isLight(view.context)) {\n\t\t\tcolorFilter?.getBackgroundTint()\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tfun isPagesCropEnabled(isWebtoon: Boolean) = if (isWebtoon) {\n\t\tisPagesCropEnabledWebtoon\n\t} else {\n\t\tisPagesCropEnabledStandard\n\t}\n\n\t@CheckResult\n\tfun applyBitmapConfig(ssiv: SubsamplingScaleImageView): Boolean {\n\t\tval config = bitmapConfig\n\t\treturn if (ssiv.regionDecoderFactory.bitmapConfig != config) {\n\t\t\tssiv.regionDecoderFactory = if (ssiv.context.isLowRamDevice()) {\n\t\t\t\tSkiaImageRegionDecoder.Factory(config)\n\t\t\t} else {\n\t\t\t\tSkiaPooledImageRegionDecoder.Factory(config)\n\t\t\t}\n\t\t\tssiv.bitmapDecoderFactory = SkiaImageDecoder.Factory(config)\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tclass Producer @AssistedInject constructor(\n\t\t@Assisted private val mangaId: Flow<Long>,\n\t\tprivate val settings: AppSettings,\n\t\tprivate val mangaDataRepository: MangaDataRepository,\n\t) : MediatorStateFlow<ReaderSettings>(ReaderSettings(settings, null)) {\n\n\t\tprivate val settingsKeys = scatterSetOf(\n\t\t\tAppSettings.KEY_ZOOM_MODE,\n\t\t\tAppSettings.KEY_PAGES_NUMBERS,\n\t\t\tAppSettings.KEY_READER_BACKGROUND,\n\t\t\tAppSettings.KEY_32BIT_COLOR,\n\t\t\tAppSettings.KEY_READER_OPTIMIZE,\n\t\t\tAppSettings.KEY_CF_CONTRAST,\n\t\t\tAppSettings.KEY_CF_BRIGHTNESS,\n\t\t\tAppSettings.KEY_CF_INVERTED,\n\t\t\tAppSettings.KEY_CF_GRAYSCALE,\n\t\t\tAppSettings.KEY_READER_CROP,\n\t\t)\n\t\tprivate var job: Job? = null\n\n\t\toverride fun onActive() {\n\t\t\tassert(job?.isActive != true)\n\t\t\tjob?.cancel()\n\t\t\tjob = processLifecycleScope.launch(Dispatchers.Default) {\n\t\t\t\tobserveImpl()\n\t\t\t}\n\t\t}\n\n\t\toverride fun onInactive() {\n\t\t\tjob?.cancel()\n\t\t\tjob = null\n\t\t}\n\n\t\tprivate suspend fun observeImpl() {\n\t\t\tcombine(\n\t\t\t\tmangaId.flatMapLatest { mangaDataRepository.observeColorFilter(it) },\n\t\t\t\tsettings.observeChanges().filter { x -> x == null || x in settingsKeys }.onStart { emit(null) },\n\t\t\t) { mangaCf, settingsKey ->\n\t\t\t\tReaderSettings(settings, mangaCf)\n\t\t\t}.collect {\n\t\t\t\tpublishValue(it)\n\t\t\t}\n\t\t}\n\n\t\t@AssistedFactory\n\t\tinterface Factory {\n\n\t\t\tfun create(mangaId: Flow<Long>): Producer\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager\n\nimport android.content.ComponentCallbacks2\nimport android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE\nimport android.content.Context\nimport android.content.res.Configuration\nimport android.view.View\nimport androidx.annotation.CallSuper\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport androidx.viewbinding.ViewBinding\nimport com.davemorrissey.labs.subscaleview.DefaultOnImageEventListener\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.ui.list.lifecycle.LifecycleAwareViewHolder\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.isLowRamDevice\nimport org.koitharu.kotatsu.core.util.ext.isSerializable\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.databinding.LayoutPageInfoBinding\nimport org.koitharu.kotatsu.parsers.util.ifZero\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.vm.PageState\nimport org.koitharu.kotatsu.reader.ui.pager.vm.PageViewModel\nimport org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonHolder\n\nabstract class BasePageHolder<B : ViewBinding>(\n\tprotected val binding: B,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n\tlifecycleOwner: LifecycleOwner,\n) : LifecycleAwareViewHolder(binding.root, lifecycleOwner), DefaultOnImageEventListener, ComponentCallbacks2 {\n\n\tprotected val viewModel = PageViewModel(\n\t\tloader = loader,\n\t\tsettingsProducer = readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t\tisWebtoon = this is WebtoonHolder,\n\t)\n\tprotected val bindingInfo = LayoutPageInfoBinding.bind(binding.root)\n\tprotected abstract val ssiv: SubsamplingScaleImageView\n\n\tprotected val settings: ReaderSettings\n\t\tget() = viewModel.settingsProducer.value\n\n\tval context: Context\n\t\tget() = itemView.context\n\n\tvar boundData: ReaderPage? = null\n\t\tprivate set\n\n\tinit {\n\t\tlifecycleScope.launch(Dispatchers.Main) {\n\t\t\tssiv.bindToLifecycle(this@BasePageHolder)\n\t\t\tssiv.isEagerLoadingEnabled = !context.isLowRamDevice()\n\t\t\tssiv.addOnImageEventListener(viewModel)\n\t\t\tssiv.addOnImageEventListener(this@BasePageHolder)\n\t\t}\n\t\tval clickListener = View.OnClickListener { v ->\n\t\t\twhen (v.id) {\n\t\t\t\tR.id.button_retry -> viewModel.retry(\n\t\t\t\t\tpage = boundData?.toMangaPage() ?: return@OnClickListener,\n\t\t\t\t\tisFromUser = true,\n\t\t\t\t)\n\n\t\t\t\tR.id.button_error_details -> viewModel.showErrorDetails(boundData?.url)\n\t\t\t}\n\t\t}\n\t\tbindingInfo.buttonRetry.setOnClickListener(clickListener)\n\t\tbindingInfo.buttonErrorDetails.setOnClickListener(clickListener)\n\t}\n\n\t@CallSuper\n\tprotected open fun onConfigChanged(settings: ReaderSettings) {\n\t\tsettings.applyBackground(itemView)\n\t\tif (settings.applyBitmapConfig(ssiv)) {\n\t\t\treloadImage()\n\t\t} else if (viewModel.state.value is PageState.Shown) {\n\t\t\tonReady()\n\t\t}\n\t\tssiv.applyDownSampling(isResumed())\n\t}\n\n\tfun reloadImage() {\n\t\tval source = (viewModel.state.value as? PageState.Shown)?.source ?: return\n\t\tssiv.setImage(source)\n\t}\n\n\tfun bind(data: ReaderPage) {\n\t\tboundData = data\n\t\tviewModel.onBind(data.toMangaPage())\n\t\tonBind(data)\n\t}\n\n\t@CallSuper\n\tprotected open fun onBind(data: ReaderPage) = Unit\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tcontext.registerComponentCallbacks(this)\n\t\tviewModel.state.observe(this, ::onStateChanged)\n\t\tviewModel.settingsProducer.observe(this, ::onConfigChanged)\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tssiv.applyDownSampling(isForeground = true)\n\t\tif (viewModel.state.value is PageState.Error && !viewModel.isLoading()) {\n\t\t\tboundData?.let { viewModel.retry(it.toMangaPage(), isFromUser = false) }\n\t\t}\n\t}\n\n\toverride fun onPause() {\n\t\tsuper.onPause()\n\t\tssiv.applyDownSampling(isForeground = false)\n\t}\n\n\toverride fun onDestroy() {\n\t\tcontext.unregisterComponentCallbacks(this)\n\t\tsuper.onDestroy()\n\t}\n\n\topen fun onAttachedToWindow() = Unit\n\n\topen fun onDetachedFromWindow() = Unit\n\n\t@CallSuper\n\topen fun onRecycled() {\n\t\tviewModel.onRecycle()\n\t\tssiv.recycle()\n\t}\n\n\toverride fun onTrimMemory(level: Int) {\n\t\t// TODO\n\t}\n\n\toverride fun onConfigurationChanged(newConfig: Configuration) = Unit\n\n\t@Deprecated(\"Deprecated in Java\")\n\tfinal override fun onLowMemory() = onTrimMemory(TRIM_MEMORY_COMPLETE)\n\n\tprotected open fun onStateChanged(state: PageState) {\n\t\tbindingInfo.layoutError.isVisible = state is PageState.Error\n\t\tbindingInfo.layoutProgress.isGone = state.isFinalState()\n\t\tval progress = (state as? PageState.Loading)?.progress ?: -1\n\t\tif (progress in 0..100) {\n\t\t\tbindingInfo.progressBar.isIndeterminate = false\n\t\t\tbindingInfo.progressBar.setProgressCompat(progress, true)\n\t\t\tbindingInfo.textViewStatus.text = context.getString(R.string.percent_string_pattern, progress.toString())\n\t\t} else {\n\t\t\tbindingInfo.progressBar.isIndeterminate = true\n\t\t\tbindingInfo.textViewStatus.setText(R.string.loading_)\n\t\t}\n\t\twhen (state) {\n\t\t\tis PageState.Converting -> {\n\t\t\t\tbindingInfo.textViewStatus.setText(R.string.processing_)\n\t\t\t}\n\n\t\t\tis PageState.Empty -> Unit\n\n\t\t\tis PageState.Error -> {\n\t\t\t\tval e = state.error\n\t\t\t\tbindingInfo.textViewError.text = e.getDisplayMessage(context.resources)\n\t\t\t\tbindingInfo.buttonRetry.setText(\n\t\t\t\t\tExceptionResolver.getResolveStringId(e).ifZero { R.string.try_again },\n\t\t\t\t)\n\t\t\t\tbindingInfo.buttonErrorDetails.isVisible = e.isSerializable()\n\t\t\t\tbindingInfo.layoutError.isVisible = true\n\t\t\t\tbindingInfo.progressBar.hide()\n\t\t\t}\n\n\t\t\tis PageState.Loaded -> {\n\t\t\t\tbindingInfo.textViewStatus.setText(R.string.preparing_)\n\t\t\t\tssiv.setImage(state.source)\n\t\t\t}\n\n\t\t\tis PageState.Loading -> {\n\t\t\t\tif (state.preview != null && ssiv.getState() == null) {\n\t\t\t\t\tssiv.setImage(state.preview)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tis PageState.Shown -> Unit\n\t\t}\n\t}\n\n\tprotected fun SubsamplingScaleImageView.applyDownSampling(isForeground: Boolean) {\n\t\tdownSampling = when {\n\t\t\tisForeground || !settings.isReaderOptimizationEnabled -> 1\n\t\t\tBuildConfig.DEBUG -> 32\n\t\t\tcontext.isLowRamDevice() -> 8\n\t\t\telse -> 4\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePagerReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager\n\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.InputDevice\nimport android.view.KeyEvent\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.children\nimport androidx.viewpager2.widget.ViewPager2\nimport androidx.viewpager2.widget.ViewPager2.PageTransformer\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.yield\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.prefs.ReaderAnimation\nimport org.koitharu.kotatsu.core.ui.list.lifecycle.PagerLifecycleDispatcher\nimport org.koitharu.kotatsu.core.util.ext.doOnPageChanged\nimport org.koitharu.kotatsu.core.util.ext.findCurrentViewHolder\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.recyclerView\nimport org.koitharu.kotatsu.core.util.ext.resetTransformations\nimport org.koitharu.kotatsu.databinding.FragmentReaderPagerBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.pager.standard.NoAnimPageTransformer\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PageAnimTransformer\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PagerEventSupplier\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PagesAdapter\nimport javax.inject.Inject\nimport kotlin.math.absoluteValue\nimport kotlin.math.sign\n\n@AndroidEntryPoint\nabstract class BasePagerReaderFragment : BaseReaderFragment<FragmentReaderPagerBinding>(),\n\tView.OnGenericMotionListener {\n\n\t@Inject\n\tlateinit var networkState: NetworkState\n\n\t@Inject\n\tlateinit var pageLoader: PageLoader\n\n\tprivate var pagerLifecycleDispatcher: PagerLifecycleDispatcher? = null\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentReaderPagerBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(\n\t\tbinding: FragmentReaderPagerBinding,\n\t\tsavedInstanceState: Bundle?,\n\t) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\twith(binding.pager) {\n\t\t\tonInitPager(this)\n\t\t\tdoOnPageChanged(::notifyPageChanged)\n\t\t\tsetOnGenericMotionListener(this@BasePagerReaderFragment)\n\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\t\trecyclerView?.defaultFocusHighlightEnabled = false\n\t\t\t}\n\t\t\tPagerEventSupplier(this).attach()\n\t\t\tpagerLifecycleDispatcher = PagerLifecycleDispatcher(this).also {\n\t\t\t\tregisterOnPageChangeCallback(it)\n\t\t\t}\n\t\t\tadapter = readerAdapter\n\t\t}\n\n\t\tviewModel.pageAnimation.observe(viewLifecycleOwner) {\n\t\t\tval transformer = when (it) {\n\t\t\t\tReaderAnimation.NONE -> NoAnimPageTransformer(binding.pager.orientation)\n\t\t\t\tReaderAnimation.DEFAULT -> null\n\t\t\t\tReaderAnimation.ADVANCED -> onCreateAdvancedTransformer()\n\t\t\t}\n\t\t\tbinding.pager.setPageTransformer(transformer)\n\t\t\tif (transformer == null) {\n\t\t\t\tbinding.pager.recyclerView?.children?.forEach { view ->\n\t\t\t\t\tview.resetTransformations()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\tpagerLifecycleDispatcher = null\n\t\trequireViewBinding().pager.adapter = null\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onZoomIn() {\n\t\t(viewBinding?.pager?.findCurrentViewHolder() as? PageHolder)?.onZoomIn()\n\t}\n\n\toverride fun onZoomOut() {\n\t\t(viewBinding?.pager?.findCurrentViewHolder() as? PageHolder)?.onZoomOut()\n\t}\n\n\toverride fun onGenericMotion(v: View?, event: MotionEvent): Boolean {\n\t\tif (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {\n\t\t\tif (event.actionMasked == MotionEvent.ACTION_SCROLL) {\n\t\t\t\tval axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)\n\t\t\t\tval withCtrl = event.metaState and KeyEvent.META_CTRL_MASK != 0\n\t\t\t\tif (!withCtrl) {\n\t\t\t\t\tonWheelScroll(axisValue)\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\toverride suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {\n\t\tval items = launch {\n\t\t\trequireAdapter().setItems(pages)\n\t\t\tyield()\n\t\t\tpagerLifecycleDispatcher?.postInvalidate()\n\t\t}\n\t\tif (pendingState != null) {\n\t\t\tval position = pages.indexOfFirst {\n\t\t\t\tit.chapterId == pendingState.chapterId && it.index == pendingState.page\n\t\t\t}\n\t\t\titems.join()\n\t\t\tif (position != -1) {\n\t\t\t\trequireViewBinding().pager.setCurrentItem(position, false)\n\t\t\t\tnotifyPageChanged(position)\n\t\t\t} else {\n\t\t\t\tSnackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)\n\t\t\t\t\t.show()\n\t\t\t}\n\t\t} else {\n\t\t\titems.join()\n\t\t}\n\t}\n\n\toverride fun onCreateAdapter(): BaseReaderAdapter<*> = PagesAdapter(\n\t\tlifecycleOwner = viewLifecycleOwner,\n\t\tloader = pageLoader,\n\t\treaderSettingsProducer = viewModel.readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n\n\toverride fun switchPageBy(delta: Int) {\n\t\twith(requireViewBinding().pager) {\n\t\t\tsetCurrentItem(currentItem + delta, isAnimationEnabled())\n\t\t}\n\t}\n\n\toverride fun switchPageTo(position: Int, smooth: Boolean) {\n\t\twith(requireViewBinding().pager) {\n\t\t\tsetCurrentItem(\n\t\t\t\tposition,\n\t\t\t\tsmooth && isAnimationEnabled() && (currentItem - position).absoluteValue < SMOOTH_SCROLL_LIMIT,\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun getCurrentState(): ReaderState? = viewBinding?.run {\n\t\tval adapter = pager.adapter as? BaseReaderAdapter<*>\n\t\tval page = adapter?.getItemOrNull(pager.currentItem) ?: return@run null\n\t\tReaderState(\n\t\t\tchapterId = page.chapterId,\n\t\t\tpage = page.index,\n\t\t\tscroll = 0,\n\t\t)\n\t}\n\n\tprotected open fun onWheelScroll(axisValue: Float) {\n\t\tswitchPageBy(-axisValue.sign.toInt())\n\t}\n\n\tprotected open fun onCreateAdvancedTransformer(): PageTransformer = PageAnimTransformer()\n\n\tprotected open fun onInitPager(pager: ViewPager2) {\n\t\tpager.offscreenPageLimit = 2\n\t}\n\n\tprotected open fun notifyPageChanged(page: Int) {\n\t\tviewModel.onCurrentPageChanged(page, page)\n\t}\n\n\tcompanion object {\n\n\t\tconst val SMOOTH_SCROLL_LIMIT = 3\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BaseReaderAdapter.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager\n\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.util.ext.resetTransformations\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n@Suppress(\"LeakingThis\")\nabstract class BaseReaderAdapter<H : BasePageHolder<*>>(\n\tprivate val loader: PageLoader,\n\tprivate val readerSettingsProducer: ReaderSettings.Producer,\n\tprivate val networkState: NetworkState,\n\tprivate val exceptionResolver: ExceptionResolver,\n) : RecyclerView.Adapter<H>() {\n\n\tprivate val differ = AsyncListDiffer(this, DiffCallback())\n\n\tval hasItems: Boolean\n\t\tget() = itemCount != 0\n\n\tinit {\n\t\tstateRestorationPolicy = StateRestorationPolicy.PREVENT\n\t}\n\n\toverride fun onBindViewHolder(holder: H, position: Int) {\n\t\tholder.bind(differ.currentList[position])\n\t}\n\n\toverride fun onViewRecycled(holder: H) {\n\t\tholder.onRecycled()\n\t\tholder.itemView.resetTransformations()\n\t\tsuper.onViewRecycled(holder)\n\t}\n\n\toverride fun onViewAttachedToWindow(holder: H) {\n\t\tsuper.onViewAttachedToWindow(holder)\n\t\tholder.onAttachedToWindow()\n\t}\n\n\toverride fun onViewDetachedFromWindow(holder: H) {\n\t\tholder.onDetachedFromWindow()\n\t\tsuper.onViewDetachedFromWindow(holder)\n\t}\n\n\topen fun getItem(position: Int): ReaderPage = differ.currentList[position]\n\n\topen fun getItemOrNull(position: Int) = differ.currentList.getOrNull(position)\n\n\tfinal override fun getItemCount() = differ.currentList.size\n\n\tfinal override fun onCreateViewHolder(\n\t\tparent: ViewGroup,\n\t\tviewType: Int,\n\t): H = onCreateViewHolder(parent, loader, readerSettingsProducer, networkState, exceptionResolver)\n\n\tsuspend fun setItems(items: List<ReaderPage>) = suspendCoroutine { cont ->\n\t\tdiffer.submitList(items) {\n\t\t\tcont.resume(Unit)\n\t\t}\n\t}\n\n\tprotected abstract fun onCreateViewHolder(\n\t\tparent: ViewGroup,\n\t\tloader: PageLoader,\n\t\treaderSettingsProducer: ReaderSettings.Producer,\n\t\tnetworkState: NetworkState,\n\t\texceptionResolver: ExceptionResolver,\n\t): H\n\n\tprivate class DiffCallback : DiffUtil.ItemCallback<ReaderPage>() {\n\n\t\toverride fun areItemsTheSame(oldItem: ReaderPage, newItem: ReaderPage): Boolean {\n\t\t\treturn oldItem.id == newItem.id && oldItem.chapterId == newItem.chapterId\n\t\t}\n\n\t\toverride fun areContentsTheSame(oldItem: ReaderPage, newItem: ReaderPage): Boolean {\n\t\t\treturn oldItem == newItem\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BaseReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.activityViewModels\nimport androidx.viewbinding.ViewBinding\nimport org.koitharu.kotatsu.core.prefs.ReaderAnimation\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.widgets.ZoomControl\nimport org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.ReaderViewModel\n\nabstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>(), ZoomControl.ZoomControlListener {\n\n\tprotected val viewModel by activityViewModels<ReaderViewModel>()\n\n\tprotected var readerAdapter: BaseReaderAdapter<*>? = null\n\t\tprivate set\n\n\toverride fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\treaderAdapter = onCreateAdapter()\n\n\t\tviewModel.content.observe(viewLifecycleOwner) {\n\t\t\t// Determine which state to use for restoring position:\n\t\t\t// - content.state: explicitly set state (e.g., after mode switch or chapter change)\n\t\t\t// - getCurrentState(): current reading position saved in SavedStateHandle\n\t\t\tval currentState = viewModel.getCurrentState()\n\t\t\tval pendingState = when {\n\t\t\t\t// If content.state is null and we have pages, use getCurrentState\n\t\t\t\tit.state == null\n\t\t\t\t\t&& it.pages.isNotEmpty()\n\t\t\t\t\t&& readerAdapter?.hasItems != true -> currentState\n\n\t\t\t\t// use currentState only if it matches the current pages (to avoid the error message)\n\t\t\t\treaderAdapter?.hasItems != true\n\t\t\t\t\t&& it.state != currentState\n\t\t\t\t\t&& currentState != null\n\t\t\t\t\t&& it.pages.any { page -> page.chapterId == currentState.chapterId } -> currentState\n\n\t\t\t\t// Otherwise, use content.state (normal flow, mode switch, chapter change)\n\t\t\t\telse -> it.state\n\t\t\t}\n\t\t\tonPagesChanged(it.pages, pendingState)\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat = insets\n\n\toverride fun onPause() {\n\t\tsuper.onPause()\n\t\tviewModel.saveCurrentState(getCurrentState())\n\t}\n\n\toverride fun onDestroyView() {\n\t\tviewModel.saveCurrentState(getCurrentState())\n\t\treaderAdapter = null\n\t\tsuper.onDestroyView()\n\t}\n\n\tprotected fun requireAdapter() = checkNotNull(readerAdapter) {\n\t\t\"Adapter was not created or already destroyed\"\n\t}\n\n\tprotected fun isAnimationEnabled(): Boolean {\n\t\treturn context?.isAnimationsEnabled == true && viewModel.pageAnimation.value != ReaderAnimation.NONE\n\t}\n\n\tabstract fun switchPageBy(delta: Int)\n\n\tabstract fun switchPageTo(position: Int, smooth: Boolean)\n\n\topen fun scrollBy(delta: Int, smooth: Boolean): Boolean = false\n\n\tabstract fun getCurrentState(): ReaderState?\n\n\tprotected abstract fun onCreateAdapter(): BaseReaderAdapter<*>\n\n\tprotected abstract suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderPage.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\nimport kotlinx.parcelize.TypeParceler\nimport org.koitharu.kotatsu.core.model.parcelable.MangaSourceParceler\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\n@Parcelize\n@TypeParceler<MangaSource, MangaSourceParceler>\ndata class ReaderPage(\n\tval id: Long,\n\tval url: String,\n\tval preview: String?,\n\tval chapterId: Long,\n\tval index: Int,\n\tval source: MangaSource,\n) : Parcelable {\n\n\tconstructor(page: MangaPage, index: Int, chapterId: Long) : this(\n\t\tid = page.id,\n\t\turl = page.url,\n\t\tpreview = page.preview,\n\t\tchapterId = chapterId,\n\t\tindex = index,\n\t\tsource = page.source,\n\t)\n\n\tfun toMangaPage() = MangaPage(\n\t\tid = id,\n\t\turl = url,\n\t\tpreview = preview,\n\t\tsource = source,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager\n\nimport android.content.res.Resources\nimport org.koitharu.kotatsu.core.model.getLocalizedTitle\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\n\ndata class ReaderUiState(\n\tval mangaName: String?,\n\tval chapter: MangaChapter,\n\tval chapterIndex: Int,\n\tval chaptersTotal: Int,\n\tval currentPage: Int,\n\tval totalPages: Int,\n\tval percent: Float,\n\tval incognito: Boolean,\n) {\n\n\tval chapterNumber: Int\n\t\tget() = chapterIndex + 1\n\n\tfun hasNextChapter(): Boolean = chapterNumber < chaptersTotal\n\n\tfun hasPreviousChapter(): Boolean = chapterIndex > 0\n\n\tfun isSliderAvailable(): Boolean = totalPages > 1 && currentPage < totalPages\n\n\tfun getChapterTitle(resources: Resources) = chapter.getLocalizedTitle(resources, chapterIndex)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageHolder.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.doublepage\n\nimport android.graphics.PointF\nimport android.view.Gravity\nimport android.widget.FrameLayout\nimport androidx.lifecycle.LifecycleOwner\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.databinding.ItemPageBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder\n\nclass DoublePageHolder(\n\towner: LifecycleOwner,\n\tbinding: ItemPageBinding,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : PageHolder(\n\towner = owner,\n\tbinding = binding,\n\tloader = loader,\n\treaderSettingsProducer = readerSettingsProducer,\n\tnetworkState = networkState,\n\texceptionResolver = exceptionResolver,\n) {\n\n\tprivate val isEven: Boolean\n\t\tget() = bindingAdapterPosition and 1 == 0\n\n\tinit {\n\t\tbinding.ssiv.panLimit = SubsamplingScaleImageView.PAN_LIMIT_INSIDE\n\t}\n\n\toverride fun onBind(data: ReaderPage) {\n\t\tsuper.onBind(data)\n\t\t(binding.textViewNumber.layoutParams as FrameLayout.LayoutParams)\n\t\t\t.gravity = (if (isEven) Gravity.START else Gravity.END) or Gravity.BOTTOM\n\t}\n\n\toverride fun onReady() {\n\t\twith(binding.ssiv) {\n\t\t\tmaxScale = 2f * maxOf(\n\t\t\t\twidth / sWidth.toFloat(),\n\t\t\t\theight / sHeight.toFloat(),\n\t\t\t)\n\t\t\tbinding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()\n\t\t\tminimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE\n\t\t\tsetScaleAndCenter(\n\t\t\t\tminScale,\n\t\t\t\tPointF(if (isEven) 0f else sWidth.toFloat(), sHeight / 2f),\n\t\t\t)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageLayoutManager.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.doublepage\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\nclass DoublePageLayoutManager(\n\tcontext: Context,\n\tattrs: AttributeSet?,\n\tdefStyleAttr: Int,\n\tdefStyleRes: Int,\n) : LinearLayoutManager(context, attrs, defStyleAttr, defStyleRes) {\n\n\toverride fun checkLayoutParams(lp: RecyclerView.LayoutParams?): Boolean {\n\t\tlp?.width = width / 2\n\t\treturn super.checkLayoutParams(lp)\n\t}\n\n\toverride fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {\n\t\tval offscreenSpace = width / 2\n\t\textraLayoutSpace[0] = offscreenSpace\n\t\textraLayoutSpace[1] = offscreenSpace\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageSnapHelper.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.doublepage\n\nimport android.util.DisplayMetrics\nimport android.view.View\nimport android.view.animation.Interpolator\nimport android.widget.Scroller\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearSmoothScroller\nimport androidx.recyclerview.widget.OrientationHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider\nimport androidx.recyclerview.widget.SnapHelper\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport kotlin.math.abs\nimport kotlin.math.absoluteValue\nimport kotlin.math.max\nimport kotlin.math.roundToInt\nimport kotlin.math.sign\n\nclass DoublePageSnapHelper(private val settings: AppSettings) : SnapHelper() {\n\n\tprivate lateinit var recyclerView: RecyclerView\n\n\t// Total number of items in a block of view in the RecyclerView\n\tprivate var blockSize = 2\n\n\t// Maximum number of positions to move on a fling.\n\tprivate var maxPositionsToMove = 0\n\n\t// Width of a RecyclerView item if orientation is horizontal; height of the item if vertical\n\tprivate var itemDimension = 0\n\n\t// Maxim blocks to move during most vigorous fling.\n\tprivate val maxFlingBlocks = 2\n\n\t// When snapping, used to determine direction of snap.\n\tprivate var priorFirstPosition = RecyclerView.NO_POSITION\n\n\t// Our private scroller\n\tprivate var scroller: Scroller? = null\n\n\t// Horizontal/vertical layout helper\n\tprivate lateinit var orientationHelper: OrientationHelper\n\n\t// LTR/RTL helper\n\tprivate lateinit var layoutDirectionHelper: LayoutDirectionHelper\n\n\tprivate val snapInterpolator = Interpolator { input ->\n\t\tvar t = input\n\t\tt -= 1.0f\n\t\tt * t * t + 1.0f\n\t}\n\n\t@Throws(IllegalStateException::class)\n\toverride fun attachToRecyclerView(target: RecyclerView?) {\n\t\tif (target != null) {\n\t\t\trecyclerView = target\n\t\t\tval layoutManager = recyclerView.layoutManager as LinearLayoutManager\n\t\t\tcheck(layoutManager.canScrollHorizontally()) { \"RecyclerView must be scrollable\" }\n\t\t\torientationHelper = OrientationHelper.createHorizontalHelper(layoutManager)\n\t\t\tlayoutDirectionHelper = LayoutDirectionHelper(recyclerView.layoutDirection)\n\t\t\tscroller = Scroller(target.context, snapInterpolator)\n\t\t\tinitItemDimensionIfNeeded(layoutManager)\n\t\t}\n\t\tsuper.attachToRecyclerView(recyclerView)\n\t}\n\n\toverride fun calculateDistanceToFinalSnap(\n\t\tlayoutManager: RecyclerView.LayoutManager,\n\t\ttargetView: View\n\t): IntArray {\n\t\tval out = IntArray(2)\n\t\tif (layoutManager.canScrollHorizontally()) {\n\t\t\tout[0] = layoutDirectionHelper.getScrollToAlignView(targetView)\n\t\t}\n\t\tif (layoutManager.canScrollVertically()) {\n\t\t\tout[1] = layoutDirectionHelper.getScrollToAlignView(targetView)\n\t\t}\n\t\treturn out\n\t}\n\n\t// We are flinging and need to know where we are heading.\n\toverride fun findTargetSnapPosition(\n\t\tlayoutManager: RecyclerView.LayoutManager,\n\t\tvelocityX: Int, velocityY: Int\n\t): Int {\n\t\tval lm = layoutManager as LinearLayoutManager\n\t\tinitItemDimensionIfNeeded(layoutManager)\n\t\tscroller!!.fling(0, 0, velocityX, velocityY, Int.MIN_VALUE, Int.MAX_VALUE, Int.MIN_VALUE, Int.MAX_VALUE)\n\t\tif (velocityX != 0) {\n\t\t\treturn layoutDirectionHelper\n\t\t\t\t.getPositionsToMove(lm, scroller!!.finalX, itemDimension)\n\t\t}\n\t\treturn if (velocityY != 0) {\n\t\t\tlayoutDirectionHelper\n\t\t\t\t.getPositionsToMove(lm, scroller!!.finalY, itemDimension)\n\t\t} else RecyclerView.NO_POSITION\n\t}\n\n\t// We have scrolled to the neighborhood where we will snap. Determine the snap position.\n\toverride fun findSnapView(layoutManager: RecyclerView.LayoutManager): View? {\n\t\t// Snap to a view that is either 1) toward the bottom of the data and therefore on screen,\n\t\t// or, 2) toward the top of the data and may be off-screen.\n\t\tval snapPos: Int = calcTargetPosition(layoutManager as LinearLayoutManager)\n\t\treturn if (snapPos == RecyclerView.NO_POSITION) null else layoutManager.findViewByPosition(snapPos)\n\t}\n\n\t// Does the heavy lifting for findSnapView.\n\tprivate fun calcTargetPosition(layoutManager: LinearLayoutManager): Int {\n\t\tval snapPos: Int\n\t\tval firstVisiblePos = layoutManager.findFirstVisibleItemPosition()\n\t\tif (firstVisiblePos == RecyclerView.NO_POSITION) {\n\t\t\treturn RecyclerView.NO_POSITION\n\t\t}\n\t\tinitItemDimensionIfNeeded(layoutManager)\n\t\tif (firstVisiblePos >= priorFirstPosition) {\n\t\t\t// Scrolling toward bottom of data\n\t\t\tval firstCompletePosition = layoutManager.findFirstCompletelyVisibleItemPosition()\n\t\t\tsnapPos = if (firstCompletePosition != RecyclerView.NO_POSITION\n\t\t\t\t&& firstCompletePosition % blockSize == 0\n\t\t\t) {\n\t\t\t\tfirstCompletePosition\n\t\t\t} else {\n\t\t\t\troundDownToBlockSize(firstVisiblePos + blockSize)\n\t\t\t}\n\t\t} else {\n\t\t\t// Scrolling toward top of data\n\t\t\tsnapPos = roundDownToBlockSize(firstVisiblePos)\n\t\t\t// Check to see if target view exists. If it doesn't, force a smooth scroll.\n\t\t\t// SnapHelper only snaps to existing views and will not scroll to a non-existent one.\n\t\t\t// If limiting fling to single block, then the following is not needed since the\n\t\t\t// views are likely to be in the RecyclerView pool.\n\t\t\tif (layoutManager.findViewByPosition(snapPos) == null) {\n\t\t\t\tval toScroll: IntArray = layoutDirectionHelper.calculateDistanceToScroll(layoutManager, snapPos)\n\t\t\t\trecyclerView.smoothScrollBy(toScroll[0], toScroll[1], snapInterpolator)\n\t\t\t}\n\t\t}\n\t\tpriorFirstPosition = firstVisiblePos\n\t\treturn snapPos\n\t}\n\n\tprivate fun initItemDimensionIfNeeded(layoutManager: RecyclerView.LayoutManager) {\n\t\tif (itemDimension != 0) {\n\t\t\treturn\n\t\t}\n\t\tval child: View = layoutManager.getChildAt(0) ?: return\n\t\tif (layoutManager.canScrollHorizontally()) {\n\t\t\titemDimension = child.width\n\t\t\tblockSize = getSpanCount(layoutManager) * (recyclerView.width / itemDimension)\n\t\t} else if (layoutManager.canScrollVertically()) {\n\t\t\titemDimension = child.height\n\t\t\tblockSize = getSpanCount(layoutManager) * (recyclerView.height / itemDimension)\n\t\t}\n\t\tmaxPositionsToMove = blockSize * maxFlingBlocks\n\t}\n\n\tprivate fun getSpanCount(layoutManager: RecyclerView.LayoutManager): Int {\n\t\treturn if (layoutManager is GridLayoutManager) layoutManager.spanCount else 1\n\t}\n\n\tprivate fun roundDownToBlockSize(trialPosition: Int): Int {\n\t\treturn trialPosition and 1.inv()\n\t}\n\n\tprivate fun roundUpToBlockSize(trialPosition: Int): Int {\n\t\treturn roundDownToBlockSize(trialPosition + blockSize - 1)\n\t}\n\n\toverride fun createScroller(layoutManager: RecyclerView.LayoutManager): RecyclerView.SmoothScroller? {\n\t\treturn if (layoutManager !is ScrollVectorProvider) {\n\t\t\tnull\n\t\t} else object : LinearSmoothScroller(recyclerView.context) {\n\t\t\toverride fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {\n\t\t\t\tval snapDistances = calculateDistanceToFinalSnap(\n\t\t\t\t\trecyclerView.layoutManager!!,\n\t\t\t\t\ttargetView,\n\t\t\t\t)\n\t\t\t\tval dx = snapDistances[0]\n\t\t\t\tval dy = snapDistances[1]\n\t\t\t\tval time = calculateTimeForDeceleration(\n\t\t\t\t\tmax(abs(dx.toDouble()), abs(dy.toDouble()))\n\t\t\t\t\t\t.toInt(),\n\t\t\t\t)\n\t\t\t\tif (time > 0) {\n\t\t\t\t\taction.update(dx, dy, time, snapInterpolator)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toverride fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {\n\t\t\t\treturn 40f / displayMetrics.densityDpi\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t\tHelper class that handles calculations for LTR and RTL layouts.\n\t */\n\tprivate inner class LayoutDirectionHelper(direction: Int) {\n\n\t\t// Is the layout an RTL one?\n\t\tprivate val isRTL = direction == View.LAYOUT_DIRECTION_RTL\n\n\t\t/*\n\t\t\tCalculate the amount of scroll needed to align the target view with the layout edge.\n\t\t */\n\t\tfun getScrollToAlignView(targetView: View?): Int {\n\t\t\treturn if (isRTL) {\n\t\t\t\torientationHelper.getDecoratedEnd(targetView) - recyclerView.width\n\t\t\t} else {\n\t\t\t\torientationHelper.getDecoratedStart(targetView)\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Calculate the distance to final snap position when the view corresponding to the snap\n\t\t * position is not currently available.\n\t\t *\n\t\t * @param layoutManager LinearLayoutManager or descendant class\n\t\t * @param targetPos     - Adapter position to snap to\n\t\t * @return int[2] {x-distance in pixels, y-distance in pixels}\n\t\t */\n\t\tfun calculateDistanceToScroll(layoutManager: LinearLayoutManager, targetPos: Int): IntArray {\n\t\t\tval out = IntArray(2)\n\t\t\tval firstVisiblePos = layoutManager.findFirstVisibleItemPosition()\n\t\t\tif (layoutManager.canScrollHorizontally()) {\n\t\t\t\tif (targetPos <= firstVisiblePos) { // scrolling toward top of data\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tval lastView = layoutManager.findViewByPosition(layoutManager.findLastVisibleItemPosition())\n\t\t\t\t\t\tout[0] = (orientationHelper.getDecoratedEnd(lastView)\n\t\t\t\t\t\t\t+ (firstVisiblePos - targetPos) * itemDimension)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval firstView = layoutManager.findViewByPosition(firstVisiblePos)\n\t\t\t\t\t\tout[0] = (orientationHelper.getDecoratedStart(firstView)\n\t\t\t\t\t\t\t- (firstVisiblePos - targetPos) * itemDimension)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (layoutManager.canScrollVertically()) {\n\t\t\t\tif (targetPos <= firstVisiblePos) { // scrolling toward top of data\n\t\t\t\t\tval firstView = layoutManager.findViewByPosition(firstVisiblePos)\n\t\t\t\t\tout[1] = firstView!!.top - (firstVisiblePos - targetPos) * itemDimension\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t}\n\n\t\t/*\n\t\t\tCalculate the number of positions to move in the RecyclerView given a scroll amount\n\t\t\tand the size of the items to be scrolled. Return integral multiple of mBlockSize not\n\t\t\tequal to zero.\n\t\t */\n\t\tfun getPositionsToMove(llm: LinearLayoutManager, scroll: Int, itemSize: Int): Int {\n\t\t\tval sensitivity = settings.readerDoublePagesSensitivity.coerceIn(0f, 1f) * 2.5\n\t\t\tvar positionsToMove = (scroll.toDouble() / (itemSize * (2.5 - sensitivity))).roundToInt()\n\n\t\t\t// Apply a maximum threshold\n\t\t\tval maxPages = (4 * sensitivity).roundToInt().coerceAtLeast(1)\n\t\t\tif (positionsToMove.absoluteValue > maxPages) {\n\t\t\t\tpositionsToMove = maxPages * positionsToMove.sign\n\t\t\t}\n\n\t\t\t// Apply a minimum threshold\n\t\t\tif (positionsToMove == 0 && scroll.absoluteValue > itemSize * 0.2) {\n\t\t\t\tpositionsToMove = 1 * scroll.sign\n\t\t\t}\n\n\t\t\tval currentPosition = if (layoutDirectionHelper.isDirectionToBottom(scroll < 0)) {\n\t\t\t\tllm.findFirstVisibleItemPosition()\n\t\t\t} else {\n\t\t\t\tllm.findLastVisibleItemPosition()\n\t\t\t}\n\t\t\tval targetPos = currentPosition + positionsToMove * 2\n\t\t\treturn roundDownToBlockSize(targetPos)\n\t\t}\n\n\t\tfun isDirectionToBottom(velocityNegative: Boolean): Boolean {\n\t\t\treturn if (isRTL) velocityNegative else !velocityNegative\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePagesAdapter.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.doublepage\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.databinding.ItemPageBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter\n\nclass DoublePagesAdapter(\n\tprivate val lifecycleOwner: LifecycleOwner,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : BaseReaderAdapter<DoublePageHolder>(loader, readerSettingsProducer, networkState, exceptionResolver) {\n\n\toverride fun onCreateViewHolder(\n\t\tparent: ViewGroup,\n\t\tloader: PageLoader,\n\t\treaderSettingsProducer: ReaderSettings.Producer,\n\t\tnetworkState: NetworkState,\n\t\texceptionResolver: ExceptionResolver,\n\t) = DoublePageHolder(\n\t\towner = lifecycleOwner,\n\t\tbinding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),\n\t\tloader = loader,\n\t\treaderSettingsProducer = readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoubleReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.doublepage\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.view.animation.AccelerateDecelerateInterpolator\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.yield\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.list.lifecycle.RecyclerViewLifecycleDispatcher\nimport org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition\nimport org.koitharu.kotatsu.databinding.FragmentReaderDoubleBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport javax.inject.Inject\nimport kotlin.math.absoluteValue\n\n@AndroidEntryPoint\nopen class DoubleReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding>() {\n\n\t@Inject\n\tlateinit var networkState: NetworkState\n\n\t@Inject\n\tlateinit var pageLoader: PageLoader\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate var recyclerLifecycleDispatcher: RecyclerViewLifecycleDispatcher? = null\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentReaderDoubleBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(\n\t\tbinding: FragmentReaderDoubleBinding,\n\t\tsavedInstanceState: Bundle?,\n\t) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\twith(binding.recyclerView) {\n\t\t\tadapter = readerAdapter\n\t\t\trecyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {\n\t\t\t\taddOnScrollListener(it)\n\t\t\t}\n\t\t\taddOnScrollListener(PageScrollListener())\n\t\t\tDoublePageSnapHelper(settings).attachToRecyclerView(this)\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\trecyclerLifecycleDispatcher = null\n\t\trequireViewBinding().recyclerView.adapter = null\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {\n\t\tval items = launch {\n\t\t\trequireAdapter().setItems(pages)\n\t\t\tyield()\n\t\t\tviewBinding?.recyclerView?.let { rv ->\n\t\t\t\trecyclerLifecycleDispatcher?.invalidate(rv)\n\t\t\t}\n\t\t}\n\t\tif (pendingState != null) {\n\t\t\tvar position = pages.indexOfFirst {\n\t\t\t\tit.chapterId == pendingState.chapterId && it.index == pendingState.page\n\t\t\t}\n\t\t\titems.join()\n\t\t\tif (position != -1) {\n\t\t\t\tposition = position.toPagePosition()\n\t\t\t\trequireViewBinding().recyclerView.firstVisibleItemPosition = position\n\t\t\t\tnotifyPageChanged(position, position + 1)\n\t\t\t} else {\n\t\t\t\tSnackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)\n\t\t\t\t\t.show()\n\t\t\t}\n\t\t} else {\n\t\t\titems.join()\n\t\t}\n\t}\n\n\toverride fun onCreateAdapter() = DoublePagesAdapter(\n\t\tlifecycleOwner = viewLifecycleOwner,\n\t\tloader = pageLoader,\n\t\treaderSettingsProducer = viewModel.readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n\n\toverride fun onZoomIn() {\n\t\t(viewBinding ?: return).recyclerView.visiblePageHolders()\n\t\t\t.forEach { it.onZoomIn() }\n\t}\n\n\toverride fun onZoomOut() {\n\t\t(viewBinding ?: return).recyclerView.visiblePageHolders()\n\t\t\t.forEach { it.onZoomOut() }\n\t}\n\n\toverride fun switchPageBy(delta: Int) {\n\t\tif (delta.absoluteValue > 1 || !isAnimationEnabled()) {\n\t\t\tswitchPageTo(getCurrentItem() + delta + delta, false)\n\t\t\treturn\n\t\t}\n\t\tval rv = viewBinding?.recyclerView ?: return\n\t\tval distance = rv.width * delta\n\t\trv.smoothScrollBy(distance, 0, AccelerateDecelerateInterpolator())\n\t}\n\n\toverride fun switchPageTo(position: Int, smooth: Boolean) {\n\t\tval lm = viewBinding?.recyclerView?.layoutManager as? LinearLayoutManager ?: return\n\t\tval targetPosition = position.toPagePosition()\n\t\tlm.scrollToPositionWithOffset(targetPosition, 0)\n\t}\n\n\toverride fun getCurrentState(): ReaderState? = viewBinding?.run {\n\t\tval adapter = recyclerView.adapter as? BaseReaderAdapter<*>\n\t\tval page = adapter?.getItemOrNull(getCurrentItem()) ?: return@run null\n\t\tReaderState(\n\t\t\tchapterId = page.chapterId,\n\t\t\tpage = page.index,\n\t\t\tscroll = 0,\n\t\t)\n\t}\n\n\tprotected open fun notifyPageChanged(lowerPos: Int, upperPos: Int) {\n\t\tviewModel.onCurrentPageChanged(lowerPos, upperPos)\n\t}\n\n\tprivate fun getCurrentItem() = (requireViewBinding().recyclerView.layoutManager as LinearLayoutManager)\n\t\t.findFirstCompletelyVisibleItemPosition().toPagePosition()\n\n\tprivate fun Int.toPagePosition() = this and 1.inv()\n\n\tprivate inner class PageScrollListener : RecyclerView.OnScrollListener() {\n\n\t\tprivate var firstPos = RecyclerView.NO_POSITION\n\t\tprivate var lastPos = RecyclerView.NO_POSITION\n\n\t\toverride fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n\t\t\tsuper.onScrolled(recyclerView, dx, dy)\n\t\t\tval lm = recyclerView.layoutManager as? LinearLayoutManager\n\t\t\tif (lm == null) {\n\t\t\t\tfirstPos = RecyclerView.NO_POSITION\n\t\t\t\tlastPos = RecyclerView.NO_POSITION\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval newFirstPos = lm.findFirstVisibleItemPosition()\n\t\t\tval newLastPos = lm.findLastVisibleItemPosition()\n\t\t\tif (newFirstPos != firstPos || newLastPos != lastPos) {\n\t\t\t\tfirstPos = newFirstPos\n\t\t\t\tlastPos = newLastPos\n\t\t\t\tnotifyPageChanged(newFirstPos, newLastPos)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/Utils.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.doublepage\n\nimport androidx.core.view.children\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder\n\nfun RecyclerView.visiblePageHolders(): Sequence<PageHolder> {\n\tval lm = layoutManager as? LinearLayoutManager ?: return emptySequence()\n\treturn (lm.findFirstVisibleItemPosition()..lm.findLastVisibleItemPosition()).asSequence()\n\t\t.mapNotNull { findViewHolderForAdapterPosition(it) as? PageHolder }\n}\n\nfun RecyclerView.allPageHolders(): Sequence<PageHolder> {\n\treturn children.mapNotNull {\n\t\tfindContainingViewHolder(it) as? PageHolder\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublereversed/ReversedDoubleReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.doublereversed\n\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport org.koitharu.kotatsu.reader.ui.pager.doublepage.DoubleReaderFragment\n\nclass ReversedDoubleReaderFragment : DoubleReaderFragment() {\n\n\toverride fun switchPageBy(delta: Int) {\n\t\tsuper.switchPageBy(-delta)\n\t}\n\n\toverride fun switchPageTo(position: Int, smooth: Boolean) {\n\t\tsuper.switchPageTo(reversed(position), smooth)\n\t}\n\n\toverride suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {\n\t\tsuper.onPagesChanged(pages.reversed(), pendingState)\n\t}\n\n\toverride fun notifyPageChanged(lowerPos: Int, upperPos: Int) {\n\t\tviewModel.onCurrentPageChanged(reversed(upperPos), reversed(lowerPos))\n\t}\n\n\tprivate fun reversed(position: Int): Int {\n\t\treturn ((readerAdapter?.itemCount ?: 0) - position - 1).coerceAtLeast(0)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedPageAnimTransformer.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.reversed\n\nimport android.view.View\nimport androidx.viewpager2.widget.ViewPager2\n\nclass ReversedPageAnimTransformer : ViewPager2.PageTransformer {\n\n\toverride fun transformPage(page: View, position: Float) = with(page) {\n\t\ttranslationX = -position * width\n\t\tpivotX = width.toFloat()\n\t\tpivotY = height / 2f\n\t\tcameraDistance = 20000f\n\t\twhen {\n\t\t\tposition < -1f || position > 1f -> {\n\t\t\t\talpha = 0f\n\t\t\t\trotationY = 0f\n\t\t\t\ttranslationZ = -1f\n\t\t\t}\n\t\t\tposition <= 0f -> {\n\t\t\t\talpha = 1f\n\t\t\t\trotationY = 0f\n\t\t\t\ttranslationZ = 0f\n\t\t\t}\n\t\t\tposition > 0f -> {\n\t\t\t\talpha = 1f\n\t\t\t\trotationY = 120 * position\n\t\t\t\ttranslationZ = 2f\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedPageHolder.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.reversed\n\nimport android.graphics.PointF\nimport android.view.Gravity\nimport android.widget.FrameLayout\nimport androidx.lifecycle.LifecycleOwner\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.model.ZoomMode\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.databinding.ItemPageBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder\n\nclass ReversedPageHolder(\n\towner: LifecycleOwner,\n\tbinding: ItemPageBinding,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : PageHolder(\n\towner = owner,\n\tbinding = binding,\n\tloader = loader,\n\treaderSettingsProducer = readerSettingsProducer,\n\tnetworkState = networkState,\n\texceptionResolver = exceptionResolver,\n) {\n\n\tinit {\n\t\t(binding.textViewNumber.layoutParams as FrameLayout.LayoutParams)\n\t\t\t.gravity = Gravity.START or Gravity.BOTTOM\n\t}\n\n\toverride fun onReady() {\n\t\twith(binding.ssiv) {\n\t\t\tmaxScale = 2f * maxOf(\n\t\t\t\twidth / sWidth.toFloat(),\n\t\t\t\theight / sHeight.toFloat(),\n\t\t\t)\n\t\t\tbinding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()\n\t\t\twhen (settings.zoomMode) {\n\t\t\t\tZoomMode.FIT_CENTER -> {\n\t\t\t\t\tminimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE\n\t\t\t\t\tresetScaleAndCenter()\n\t\t\t\t}\n\n\t\t\t\tZoomMode.FIT_HEIGHT -> {\n\t\t\t\t\tminimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM\n\t\t\t\t\tminScale = height / sHeight.toFloat()\n\t\t\t\t\tsetScaleAndCenter(\n\t\t\t\t\t\tminScale,\n\t\t\t\t\t\tPointF(sWidth.toFloat(), sHeight / 2f),\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tZoomMode.FIT_WIDTH -> {\n\t\t\t\t\tminimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM\n\t\t\t\t\tminScale = width / sWidth.toFloat()\n\t\t\t\t\tsetScaleAndCenter(\n\t\t\t\t\t\tminScale,\n\t\t\t\t\t\tPointF(sWidth / 2f, 0f),\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tZoomMode.KEEP_START -> {\n\t\t\t\t\tminimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE\n\t\t\t\t\tsetScaleAndCenter(\n\t\t\t\t\t\tmaxScale,\n\t\t\t\t\t\tPointF(sWidth.toFloat(), 0f),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedPagesAdapter.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.reversed\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.databinding.ItemPageBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter\n\nclass ReversedPagesAdapter(\n\tprivate val lifecycleOwner: LifecycleOwner,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : BaseReaderAdapter<ReversedPageHolder>(loader, readerSettingsProducer, networkState, exceptionResolver) {\n\n\toverride fun onCreateViewHolder(\n\t\tparent: ViewGroup,\n\t\tloader: PageLoader,\n\t\treaderSettingsProducer: ReaderSettings.Producer,\n\t\tnetworkState: NetworkState,\n\t\texceptionResolver: ExceptionResolver,\n\t) = ReversedPageHolder(\n\t\towner = lifecycleOwner,\n\t\tbinding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),\n\t\tloader = loader,\n\t\treaderSettingsProducer = readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.reversed\n\nimport androidx.viewpager2.widget.ViewPager2\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.pager.BasePagerReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ReversedReaderFragment : BasePagerReaderFragment() {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\toverride fun onCreateAdvancedTransformer(): ViewPager2.PageTransformer = ReversedPageAnimTransformer()\n\n\toverride fun onCreateAdapter() = ReversedPagesAdapter(\n\t\tlifecycleOwner = viewLifecycleOwner,\n\t\tloader = pageLoader,\n\t\treaderSettingsProducer = viewModel.readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n\n\toverride fun onWheelScroll(axisValue: Float) {\n\t\tval value = if (settings.isReaderControlAlwaysLTR) -axisValue else axisValue\n\t\tsuper.onWheelScroll(value)\n\t}\n\n\toverride fun switchPageBy(delta: Int) {\n\t\tsuper.switchPageBy(-delta)\n\t}\n\n\toverride fun switchPageTo(position: Int, smooth: Boolean) {\n\t\tsuper.switchPageTo(reversed(position), smooth)\n\t}\n\n\toverride suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {\n\t\tsuper.onPagesChanged(pages.reversed(), pendingState)\n\t}\n\n\toverride fun notifyPageChanged(page: Int) {\n\t\tval pos = reversed(page)\n\t\tviewModel.onCurrentPageChanged(pos, pos)\n\t}\n\n\tprivate fun reversed(position: Int): Int {\n\t\treturn ((readerAdapter?.itemCount ?: 0) - position - 1).coerceAtLeast(0)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/NoAnimPageTransformer.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.standard\n\nimport android.view.View\nimport androidx.viewpager2.widget.ViewPager2\n\nclass NoAnimPageTransformer(\n\tprivate val orientation: Int\n) : ViewPager2.PageTransformer {\n\n\toverride fun transformPage(page: View, position: Float) {\n\t\tpage.translationX = when {\n\t\t\torientation != ViewPager2.ORIENTATION_HORIZONTAL -> 0f\n\t\t\tposition in -0.5f..0.5f -> -position * page.width.toFloat()\n\t\t\tposition > 0 -> page.width.toFloat()\n\t\t\telse -> -page.width.toFloat()\n\t\t}\n\t\tpage.translationY = when {\n\t\t\torientation != ViewPager2.ORIENTATION_VERTICAL -> 0f\n\t\t\tposition in -0.5f..0.5f -> -position * page.height.toFloat()\n\t\t\tposition > 0 -> page.height.toFloat()\n\t\t\telse -> -page.height.toFloat()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageAnimTransformer.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.standard\n\nimport android.view.View\nimport androidx.viewpager2.widget.ViewPager2\n\nclass PageAnimTransformer : ViewPager2.PageTransformer {\n\n\toverride fun transformPage(page: View, position: Float) = with(page) {\n\t\ttranslationX = -position * width\n\t\tpivotX = 0f\n\t\tpivotY = height / 2f\n\t\tcameraDistance = 20000f\n\t\twhen {\n\t\t\tposition < -1f || position > 1f -> {\n\t\t\t\talpha = 0f\n\t\t\t\trotationY = 0f\n\t\t\t\ttranslationZ = -1f\n\t\t\t}\n\t\t\tposition > 0f -> {\n\t\t\t\talpha = 1f\n\t\t\t\trotationY = 0f\n\t\t\t\ttranslationZ = 0f\n\t\t\t}\n\t\t\tposition <= 0f -> {\n\t\t\t\talpha = 1f\n\t\t\t\trotationY = 120 * position\n\t\t\t\ttranslationZ = 2f\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.standard\n\nimport android.annotation.SuppressLint\nimport android.graphics.PointF\nimport android.os.Build\nimport android.view.Gravity\nimport android.view.RoundedCorner\nimport android.view.View\nimport android.view.WindowInsets\nimport android.view.animation.DecelerateInterpolator\nimport android.widget.FrameLayout\nimport androidx.annotation.RequiresApi\nimport androidx.core.view.OnApplyWindowInsetsListener\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.setMargins\nimport androidx.core.view.updateLayoutParams\nimport androidx.lifecycle.LifecycleOwner\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.model.ZoomMode\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.ui.widgets.ZoomControl\nimport org.koitharu.kotatsu.databinding.ItemPageBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.BasePageHolder\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\n\nopen class PageHolder(\n\towner: LifecycleOwner,\n\tbinding: ItemPageBinding,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : BasePageHolder<ItemPageBinding>(\n\tbinding = binding,\n\tloader = loader,\n\treaderSettingsProducer = readerSettingsProducer,\n\tnetworkState = networkState,\n\texceptionResolver = exceptionResolver,\n\tlifecycleOwner = owner,\n), ZoomControl.ZoomControlListener, OnApplyWindowInsetsListener {\n\n\toverride val ssiv = binding.ssiv\n\n\tinit {\n\t\tViewCompat.setOnApplyWindowInsetsListener(binding.root, this)\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n\t\t\tinsets.toWindowInsets()?.let {\n\t\t\t\tapplyRoundedCorners(it)\n\t\t\t}\n\t\t}\n\t\treturn insets\n\t}\n\n\toverride fun onConfigChanged(settings: ReaderSettings) {\n\t\tsuper.onConfigChanged(settings)\n\t\tbinding.textViewNumber.isVisible = settings.isPagesNumbersEnabled\n\t}\n\n\t@SuppressLint(\"SetTextI18n\")\n\toverride fun onBind(data: ReaderPage) {\n\t\tsuper.onBind(data)\n\t\tbinding.textViewNumber.text = (data.index + 1).toString()\n\t}\n\n\toverride fun onReady() {\n\t\tbinding.ssiv.maxScale = 2f * maxOf(\n\t\t\tbinding.ssiv.width / binding.ssiv.sWidth.toFloat(),\n\t\t\tbinding.ssiv.height / binding.ssiv.sHeight.toFloat(),\n\t\t)\n\t\tbinding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()\n\t\twhen (settings.zoomMode) {\n\t\t\tZoomMode.FIT_CENTER -> {\n\t\t\t\tbinding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE\n\t\t\t\tbinding.ssiv.resetScaleAndCenter()\n\t\t\t}\n\n\t\t\tZoomMode.FIT_HEIGHT -> {\n\t\t\t\tbinding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM\n\t\t\t\tbinding.ssiv.minScale = binding.ssiv.height / binding.ssiv.sHeight.toFloat()\n\t\t\t\tbinding.ssiv.setScaleAndCenter(\n\t\t\t\t\tbinding.ssiv.minScale,\n\t\t\t\t\tPointF(0f, binding.ssiv.sHeight / 2f),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tZoomMode.FIT_WIDTH -> {\n\t\t\t\tbinding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CUSTOM\n\t\t\t\tbinding.ssiv.minScale = binding.ssiv.width / binding.ssiv.sWidth.toFloat()\n\t\t\t\tbinding.ssiv.setScaleAndCenter(\n\t\t\t\t\tbinding.ssiv.minScale,\n\t\t\t\t\tPointF(binding.ssiv.sWidth / 2f, 0f),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tZoomMode.KEEP_START -> {\n\t\t\t\tbinding.ssiv.minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE\n\t\t\t\tbinding.ssiv.setScaleAndCenter(\n\t\t\t\t\tbinding.ssiv.maxScale,\n\t\t\t\t\tPointF(0f, 0f),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onZoomIn() {\n\t\tscaleBy(1.2f)\n\t}\n\n\toverride fun onZoomOut() {\n\t\tscaleBy(0.8f)\n\t}\n\n\t@SuppressLint(\"RtlHardcoded\")\n\t@RequiresApi(Build.VERSION_CODES.S)\n\tprotected open fun applyRoundedCorners(insets: WindowInsets) {\n\t\tbinding.textViewNumber.updateLayoutParams<FrameLayout.LayoutParams> {\n\t\t\tval baseMargin = context.resources.getDimensionPixelOffset(R.dimen.margin_small)\n\t\t\tval absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection)\n\t\t\tval corner = when {\n\t\t\t\tabsoluteGravity and Gravity.LEFT == Gravity.LEFT -> {\n\t\t\t\t\tinsets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT)\n\t\t\t\t}\n\n\t\t\t\tabsoluteGravity and Gravity.RIGHT == Gravity.RIGHT -> {\n\t\t\t\t\tinsets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT)\n\t\t\t\t}\n\n\t\t\t\telse -> {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetMargins(baseMargin + (corner?.radius ?: 0))\n\t\t}\n\t}\n\n\tprivate fun scaleBy(factor: Float) {\n\t\tval ssiv = binding.ssiv\n\t\tval center = ssiv.getCenter() ?: return\n\t\tval newScale = ssiv.scale * factor\n\t\tssiv.animateScaleAndCenter(newScale, center)?.apply {\n\t\t\twithDuration(ssiv.resources.getInteger(android.R.integer.config_shortAnimTime).toLong())\n\t\t\twithInterpolator(DecelerateInterpolator())\n\t\t\tstart()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerEventSupplier.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.standard\n\nimport android.view.KeyEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.children\nimport androidx.viewpager2.widget.ViewPager2\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport org.koitharu.kotatsu.core.util.ext.recyclerView\n\nclass PagerEventSupplier(private val pager: ViewPager2) : View.OnKeyListener {\n\n\tfun attach() {\n\t\tpager.recyclerView?.setOnKeyListener(this)\n\t}\n\n\toverride fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {\n\t\tval rootView = pager.recyclerView?.findViewHolderForAdapterPosition(pager.currentItem)?.itemView as? ViewGroup\n\t\t\t?: return false\n\t\treturn rootView.children.firstNotNullOfOrNull { x ->\n\t\t\tx as? SubsamplingScaleImageView\n\t\t}?.dispatchKeyEvent(event) == true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.standard\n\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.reader.ui.pager.BasePagerReaderFragment\n\n@AndroidEntryPoint\nclass PagerReaderFragment : BasePagerReaderFragment()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PagesAdapter.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.standard\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.databinding.ItemPageBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter\n\nclass PagesAdapter(\n\tprivate val lifecycleOwner: LifecycleOwner,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : BaseReaderAdapter<PageHolder>(\n\tloader = loader,\n\treaderSettingsProducer = readerSettingsProducer,\n\tnetworkState = networkState,\n\texceptionResolver = exceptionResolver,\n) {\n\n\toverride fun onCreateViewHolder(\n\t\tparent: ViewGroup,\n\t\tloader: PageLoader,\n\t\treaderSettingsProducer: ReaderSettings.Producer,\n\t\tnetworkState: NetworkState,\n\t\texceptionResolver: ExceptionResolver,\n\t) = PageHolder(\n\t\towner = lifecycleOwner,\n\t\tbinding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),\n\t\tloader = loader,\n\t\treaderSettingsProducer = readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vertical/VerticalPageAnimTransformer.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.vertical\n\nimport android.view.View\nimport androidx.viewpager2.widget.ViewPager2\n\nclass VerticalPageAnimTransformer : ViewPager2.PageTransformer {\n\n\toverride fun transformPage(page: View, position: Float) = with(page) {\n\t\ttranslationY = -position * height\n\t\tpivotX = width / 2f\n\t\tpivotY = height * 0.2f\n\t\tcameraDistance = 20000f\n\t\twhen {\n\t\t\tposition < -1f || position > 1f -> {\n\t\t\t\talpha = 0f\n\t\t\t\trotationX = 0f\n\t\t\t\ttranslationZ = -1f\n\t\t\t}\n\t\t\tposition > 0f -> {\n\t\t\t\talpha = 1f\n\t\t\t\trotationX = 0f\n\t\t\t\ttranslationZ = 0f\n\t\t\t}\n\t\t\tposition <= 0f -> {\n\t\t\t\talpha = 1f\n\t\t\t\trotationX = -120 * position\n\t\t\t\ttranslationZ = 2f\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vertical/VerticalReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.vertical\n\nimport androidx.viewpager2.widget.ViewPager2\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.reader.ui.pager.BasePagerReaderFragment\n\n@AndroidEntryPoint\nclass VerticalReaderFragment : BasePagerReaderFragment() {\n\n\toverride fun onInitPager(pager: ViewPager2) {\n\t\tsuper.onInitPager(pager)\n\t\tpager.orientation = ViewPager2.ORIENTATION_VERTICAL\n\t}\n\n\toverride fun onCreateAdvancedTransformer(): ViewPager2.PageTransformer = VerticalPageAnimTransformer()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vm/PageState.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.vm\n\nimport com.davemorrissey.labs.subscaleview.ImageSource\n\nsealed class PageState {\n\n\tdata object Empty : PageState()\n\n\tdata class Loading(\n\t\tval preview: ImageSource?,\n\t\tval progress: Int,\n\t) : PageState()\n\n\tdata class Loaded(\n\t\tval source: ImageSource,\n\t\tval isConverted: Boolean,\n\t) : PageState()\n\n\tclass Converting() : PageState()\n\n\tdata class Shown(\n\t\tval source: ImageSource,\n\t\tval isConverted: Boolean,\n\t) : PageState()\n\n\tdata class Error(\n\t\tval error: Throwable,\n\t) : PageState()\n\n\tfun isFinalState(): Boolean = this is Error || this is Shown\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vm/PageViewModel.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.vm\n\nimport android.graphics.Rect\nimport android.net.Uri\nimport androidx.annotation.WorkerThread\nimport com.davemorrissey.labs.subscaleview.DefaultOnImageEventListener\nimport com.davemorrissey.labs.subscaleview.ImageSource\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport kotlinx.coroutines.withContext\nimport okio.IOException\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.throttle\nimport org.koitharu.kotatsu.parsers.model.MangaPage\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\n\nclass PageViewModel(\n\tprivate val loader: PageLoader,\n\tval settingsProducer: ReaderSettings.Producer,\n\tprivate val networkState: NetworkState,\n\tprivate val exceptionResolver: ExceptionResolver,\n\tprivate val isWebtoon: Boolean,\n) : DefaultOnImageEventListener {\n\n\tprivate val scope = loader.loaderScope + Dispatchers.Main.immediate\n\tprivate var job: Job? = null\n\tprivate var cachedBounds: Rect? = null\n\n\tval state = MutableStateFlow<PageState>(PageState.Empty)\n\n\tfun isLoading() = job?.isActive == true\n\n\tfun onBind(page: MangaPage) {\n\t\tval prevJob = job\n\t\tjob = scope.launch(Dispatchers.Default) {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tdoLoad(page, force = false)\n\t\t}\n\t}\n\n\tfun retry(page: MangaPage, isFromUser: Boolean) {\n\t\tval prevJob = job\n\t\tjob = scope.launch {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tval e = (state.value as? PageState.Error)?.error\n\t\t\tif (e != null && ExceptionResolver.canResolve(e)) {\n\t\t\t\tif (isFromUser) {\n\t\t\t\t\texceptionResolver.resolve(e)\n\t\t\t\t}\n\t\t\t}\n\t\t\twithContext(Dispatchers.Default) {\n\t\t\t\tdoLoad(page, force = true)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun showErrorDetails(url: String?) {\n\t\tval e = (state.value as? PageState.Error)?.error ?: return\n\t\texceptionResolver.showErrorDetails(e, url)\n\t}\n\n\tfun onRecycle() {\n\t\tstate.value = PageState.Empty\n\t\tcachedBounds = null\n\t\tjob?.cancel()\n\t}\n\n\toverride fun onImageLoaded() {\n\t\tstate.update { currentState ->\n\t\t\tif (currentState is PageState.Loaded) {\n\t\t\t\tPageState.Shown(currentState.source, currentState.isConverted)\n\t\t\t} else {\n\t\t\t\tcurrentState\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onImageLoadError(e: Throwable) {\n\t\te.printStackTraceDebug()\n\n\t\tstate.update { currentState ->\n\t\t\tif (currentState is PageState.Loaded) {\n\t\t\t\tval uri = (currentState.source as? ImageSource.Uri)?.uri\n\t\t\t\tif (!currentState.isConverted && uri != null && e is IOException) {\n\t\t\t\t\ttryConvert(uri, e)\n\t\t\t\t\tPageState.Converting()\n\t\t\t\t} else {\n\t\t\t\t\tPageState.Error(e)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcurrentState\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun tryConvert(uri: Uri, e: Exception) {\n\t\tval prevJob = job\n\t\tjob = scope.launch(Dispatchers.Default) {\n\t\t\tprevJob?.join()\n\t\t\tstate.value = PageState.Converting()\n\t\t\ttry {\n\t\t\t\tval newUri = loader.convertBimap(uri)\n\t\t\t\tcachedBounds = if (settingsProducer.value.isPagesCropEnabled(isWebtoon)) {\n\t\t\t\t\tloader.getTrimmedBounds(newUri)\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t\tstate.value = PageState.Loaded(newUri.toImageSource(cachedBounds), isConverted = true)\n\t\t\t} catch (ce: CancellationException) {\n\t\t\t\tthrow ce\n\t\t\t} catch (e2: Throwable) {\n\t\t\t\te2.printStackTrace()\n\t\t\t\te.addSuppressed(e2)\n\t\t\t\tstate.value = PageState.Error(e)\n\t\t\t}\n\t\t}\n\t}\n\n\t@WorkerThread\n\tprivate suspend fun doLoad(data: MangaPage, force: Boolean) = coroutineScope {\n\t\tstate.value = PageState.Loading(null, -1)\n\t\tval previewJob = launch {\n\t\t\tval preview = loader.loadPreview(data) ?: return@launch\n\t\t\tstate.update {\n\t\t\t\tif (it is PageState.Loading) it.copy(preview = preview) else it\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tval task = loader.loadPageAsync(data, force)\n\t\t\tval progressObserver = observeProgress(this, task.progressAsFlow())\n\t\t\tval uri = task.await()\n\t\t\tprogressObserver.cancelAndJoin()\n\t\t\tpreviewJob.cancel()\n\t\t\tcachedBounds = if (settingsProducer.value.isPagesCropEnabled(isWebtoon)) {\n\t\t\t\tloader.getTrimmedBounds(uri)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t\tstate.value = PageState.Loaded(uri.toImageSource(cachedBounds), isConverted = false)\n\t\t} catch (e: CancellationException) {\n\t\t\tthrow e\n\t\t} catch (e: Throwable) {\n\t\t\te.printStackTraceDebug()\n\t\t\tstate.value = PageState.Error(e)\n\t\t\tif (e is IOException && !networkState.value) {\n\t\t\t\tnetworkState.awaitForConnection()\n\t\t\t\tretry(data, isFromUser = false)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun observeProgress(scope: CoroutineScope, progress: Flow<Float>) = progress\n\t\t.throttle(250)\n\t\t.onEach {\n\t\t\tval progressValue = (100 * it).toInt()\n\t\t\tstate.update { currentState ->\n\t\t\t\tif (currentState is PageState.Loading) {\n\t\t\t\t\tcurrentState.copy(progress = progressValue)\n\t\t\t\t} else {\n\t\t\t\t\tcurrentState\n\t\t\t\t}\n\t\t\t}\n\t\t}.launchIn(scope)\n\n\tprivate fun Uri.toImageSource(bounds: Rect?): ImageSource {\n\t\tval source = ImageSource.uri(this)\n\t\treturn if (bounds != null) {\n\t\t\tsource.region(bounds)\n\t\t} else {\n\t\t\tsource\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonAdapter.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter\n\nclass WebtoonAdapter(\n\tprivate val lifecycleOwner: LifecycleOwner,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : BaseReaderAdapter<WebtoonHolder>(loader, readerSettingsProducer, networkState, exceptionResolver) {\n\n\toverride fun onCreateViewHolder(\n\t\tparent: ViewGroup,\n\t\tloader: PageLoader,\n\t\treaderSettingsProducer: ReaderSettings.Producer,\n\t\tnetworkState: NetworkState,\n\t\texceptionResolver: ExceptionResolver,\n\t) = WebtoonHolder(\n\t\towner = lifecycleOwner,\n\t\tbinding = ItemPageWebtoonBinding.inflate(\n\t\t\tLayoutInflater.from(parent.context),\n\t\t\tparent,\n\t\t\tfalse,\n\t\t),\n\t\tloader = loader,\n\t\treaderSettingsProducer = readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonFrameLayout.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.FrameLayout\nimport androidx.annotation.AttrRes\nimport org.koitharu.kotatsu.R\n\nclass WebtoonFrameLayout @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = 0,\n) : FrameLayout(context, attrs, defStyleAttr) {\n\n\tprivate var _target: WebtoonImageView? = null\n\tval target: WebtoonImageView\n\t\tget() = _target ?: findViewById<WebtoonImageView?>(R.id.ssiv).also {\n\t\t\t_target = it\n\t\t}\n\n\tfun dispatchVerticalScroll(dy: Int): Int {\n\t\tif (dy == 0) {\n\t\t\treturn 0\n\t\t}\n\t\tval oldScroll = target.getScroll()\n\t\ttarget.scrollBy(dy)\n\t\treturn target.getScroll() - oldScroll\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonGapsDecoration.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.R\n\nclass WebtoonGapsDecoration : RecyclerView.ItemDecoration() {\n\n\tprivate var gapSize = -1\n\n\toverride fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {\n\t\tsuper.getItemOffsets(outRect, view, parent, state)\n\t\tval position = parent.getChildAdapterPosition(view)\n\t\tif (position > 0) {\n\t\t\toutRect.top = getGap(parent.context)\n\t\t}\n\t}\n\n\tprivate fun getGap(context: Context): Int {\n\t\treturn if (gapSize == -1) {\n\t\t\tcontext.resources.getDimensionPixelOffset(R.dimen.webtoon_pages_gap).also {\n\t\t\t\tgapSize = it\n\t\t\t}\n\t\t} else {\n\t\t\tgapSize\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.view.View\nimport androidx.lifecycle.LifecycleOwner\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.config.ReaderSettings\nimport org.koitharu.kotatsu.reader.ui.pager.BasePageHolder\n\nclass WebtoonHolder(\n\towner: LifecycleOwner,\n\tbinding: ItemPageWebtoonBinding,\n\tloader: PageLoader,\n\treaderSettingsProducer: ReaderSettings.Producer,\n\tnetworkState: NetworkState,\n\texceptionResolver: ExceptionResolver,\n) : BasePageHolder<ItemPageWebtoonBinding>(\n\tbinding = binding,\n\tloader = loader,\n\treaderSettingsProducer = readerSettingsProducer,\n\tnetworkState = networkState,\n\texceptionResolver = exceptionResolver,\n\tlifecycleOwner = owner,\n) {\n\n\toverride val ssiv = binding.ssiv\n\n\tprivate var scrollToRestore = 0\n\n\tinit {\n\t\tbindingInfo.progressBar.setVisibilityAfterHide(View.GONE)\n\t}\n\n\toverride fun onReady() {\n\t\tbinding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()\n\t\twith(binding.ssiv) {\n\t\t\tscrollTo(\n\t\t\t\twhen {\n\t\t\t\t\tscrollToRestore != 0 -> scrollToRestore\n\t\t\t\t\titemView.top < 0 -> getScrollRange()\n\t\t\t\t\telse -> 0\n\t\t\t\t},\n\t\t\t)\n\t\t\tscrollToRestore = 0\n\t\t}\n\t}\n\n\tfun getScrollY() = binding.ssiv.getScroll()\n\n\tfun restoreScroll(scroll: Int) {\n\t\tif (binding.ssiv.isReady) {\n\t\t\tbinding.ssiv.scrollTo(scroll)\n\t\t} else {\n\t\t\tscrollToRestore = scroll\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonImageView.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.PointF\nimport android.util.AttributeSet\nimport androidx.core.view.ancestors\nimport androidx.recyclerview.widget.RecyclerView\nimport com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\nimport kotlin.math.roundToInt\n\nclass WebtoonImageView @JvmOverloads constructor(\n\tcontext: Context,\n\tattr: AttributeSet? = null,\n) : SubsamplingScaleImageView(context, attr) {\n\n\tprivate val ct = PointF()\n\n\tprivate var scrollPos = 0\n\tprivate var debugPaint: Paint? = null\n\n\toverride fun onDraw(canvas: Canvas) {\n\t\tsuper.onDraw(canvas)\n\t\tif (isDebugDrawingEnabled) {\n\t\t\tdrawDebug(canvas)\n\t\t}\n\t}\n\n\tfun scrollBy(delta: Int) {\n\t\tval maxScroll = getScrollRange()\n\t\tif (maxScroll == 0) {\n\t\t\treturn\n\t\t}\n\t\tval newScroll = scrollPos + delta\n\t\tscrollToInternal(newScroll.coerceIn(0, maxScroll))\n\t}\n\n\tfun scrollTo(y: Int) {\n\t\tval maxScroll = getScrollRange()\n\t\tif (maxScroll == 0) {\n\t\t\tscrollToInternal(0)\n\t\t\treturn\n\t\t}\n\t\tscrollToInternal(y.coerceIn(0, maxScroll))\n\t}\n\n\tfun getScroll() = scrollPos\n\n\tfun getScrollRange(): Int {\n\t\tif (!isReady) {\n\t\t\treturn 0\n\t\t}\n\t\tval totalHeight = (sHeight * width / sWidth.toFloat()).roundToInt()\n\t\treturn (totalHeight - height).coerceAtLeast(0)\n\t}\n\n\toverride fun recycle() {\n\t\tscrollPos = 0\n\t\tsuper.recycle()\n\t}\n\n\toverride fun getSuggestedMinimumHeight(): Int {\n\t\tvar desiredHeight = super.getSuggestedMinimumHeight()\n\t\tif (sHeight == 0) {\n\t\t\tval parentHeight = parentHeight()\n\t\t\tif (desiredHeight < parentHeight) {\n\t\t\t\tdesiredHeight = parentHeight\n\t\t\t}\n\t\t}\n\t\treturn desiredHeight\n\t}\n\n\toverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\t\tval widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)\n\t\tval heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)\n\t\tval parentWidth = MeasureSpec.getSize(widthMeasureSpec)\n\t\tval parentHeight = MeasureSpec.getSize(heightMeasureSpec)\n\t\tval resizeWidth = widthSpecMode != MeasureSpec.EXACTLY\n\t\tval resizeHeight = heightSpecMode != MeasureSpec.EXACTLY\n\t\tvar desiredWidth = parentWidth\n\t\tvar desiredHeight = parentHeight\n\t\tif (sWidth > 0 && sHeight > 0) {\n\t\t\tif (resizeWidth && resizeHeight) {\n\t\t\t\tdesiredWidth = sWidth\n\t\t\t\tdesiredHeight = sHeight\n\t\t\t} else if (resizeHeight) {\n\t\t\t\tdesiredHeight = (sHeight.toDouble() / sWidth.toDouble() * desiredWidth).toInt()\n\t\t\t} else if (resizeWidth) {\n\t\t\t\tdesiredWidth = (sWidth.toDouble() / sHeight.toDouble() * desiredHeight).toInt()\n\t\t\t}\n\t\t}\n\t\tdesiredWidth = desiredWidth.coerceAtLeast(suggestedMinimumWidth)\n\t\tdesiredHeight = desiredHeight.coerceAtLeast(suggestedMinimumHeight).coerceAtMost(parentHeight())\n\t\tsetMeasuredDimension(desiredWidth, desiredHeight)\n\t}\n\n\toverride fun onDownSamplingChanged() {\n\t\tsuper.onDownSamplingChanged()\n\t\tif (isReady) {\n\t\t\tadjustScale()\n\t\t\tonImageEventListener.onReady()\n\t\t}\n\t}\n\n\toverride fun onReady() {\n\t\tsuper.onReady()\n\t\tadjustScale()\n\t}\n\n\tprivate fun scrollToInternal(pos: Int) {\n\t\tminScale = width / sWidth.toFloat()\n\t\tmaxScale = minScale\n\t\tscrollPos = pos\n\t\tct.set(sWidth / 2f, (height / 2f + pos.toFloat()) / minScale)\n\t\tsetScaleAndCenter(minScale, ct)\n\t}\n\n\tprivate fun adjustScale() {\n\t\tminScale = width / sWidth.toFloat()\n\t\tmaxScale = minScale\n\t\tminimumScaleType = SCALE_TYPE_CUSTOM\n\t\trequestLayout()\n\t}\n\n\tprivate fun parentHeight(): Int {\n\t\treturn ancestors.firstNotNullOfOrNull { it as? RecyclerView }?.height ?: 0\n\t}\n\n\tprivate fun drawDebug(canvas: Canvas) {\n\t\tval paint = debugPaint ?: Paint(Paint.ANTI_ALIAS_FLAG).apply {\n\t\t\tcolor = android.graphics.Color.RED\n\t\t\tstrokeWidth = context.resources.resolveDp(2f)\n\t\t\ttextAlign = Paint.Align.LEFT\n\t\t\ttextSize = context.resources.resolveDp(14f)\n\t\t\tdebugPaint = this\n\t\t}\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRect(1f, 1f, width.toFloat() - 1f, height.toFloat() - 1f, paint)\n\t\tpaint.style = Paint.Style.FILL\n\t\tcanvas.drawText(\"${getScroll()} / ${getScrollRange()}\", 100f, 100f, paint)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonLayoutManager.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport kotlin.math.sign\n\n@Suppress(\"unused\")\nclass WebtoonLayoutManager : LinearLayoutManager {\n\n\tprivate var scrollDirection: Int = 0\n\n\tconstructor(context: Context) : super(context)\n\tconstructor(\n\t\tcontext: Context,\n\t\torientation: Int,\n\t\treverseLayout: Boolean,\n\t) : super(context, orientation, reverseLayout)\n\n\tconstructor(\n\t\tcontext: Context,\n\t\tattrs: AttributeSet?,\n\t\tdefStyleAttr: Int,\n\t\tdefStyleRes: Int,\n\t) : super(context, attrs, defStyleAttr, defStyleRes)\n\n\toverride fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State): Int {\n\t\tscrollDirection = dy.sign\n\t\treturn super.scrollVerticallyBy(dy, recycler, state)\n\t}\n\n\toverride fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {\n\t\tif (state.hasTargetScrollPosition()) {\n\t\t\tsuper.calculateExtraLayoutSpace(state, extraLayoutSpace)\n\t\t\treturn\n\t\t}\n\t\tval pageSize = height\n\t\textraLayoutSpace[0] = if (scrollDirection < 0) pageSize else 0\n\t\textraLayoutSpace[1] = if (scrollDirection < 0) 0 else pageSize\n\t}\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.view.animation.DecelerateInterpolator\nimport android.widget.TextView\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.take\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.yield\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.os.NetworkState\nimport org.koitharu.kotatsu.core.ui.list.lifecycle.RecyclerViewLifecycleDispatcher\nimport org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.removeItemDecoration\nimport org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding\nimport org.koitharu.kotatsu.reader.domain.PageLoader\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter\nimport org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>(),\n\tWebtoonRecyclerView.OnWebtoonScrollListener,\n\tWebtoonRecyclerView.OnPullGestureListener {\n\n\t@Inject\n\tlateinit var networkState: NetworkState\n\n\t@Inject\n\tlateinit var pageLoader: PageLoader\n\n\tprivate val scrollInterpolator = DecelerateInterpolator()\n\n\tprivate var recyclerLifecycleDispatcher: RecyclerViewLifecycleDispatcher? = null\n\tprivate var canGoPrev = true\n\tprivate var canGoNext = true\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentReaderWebtoonBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: FragmentReaderWebtoonBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\twith(binding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\tadapter = readerAdapter\n\t\t\taddOnPageScrollListener(this@WebtoonReaderFragment)\n\t\t\trecyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {\n\t\t\t\taddOnScrollListener(it)\n\t\t\t}\n\t\t\tsetOnPullGestureListener(this@WebtoonReaderFragment)\n\t\t}\n\t\tviewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) {\n\t\t\tbinding.frame.isZoomEnable = it\n\t\t}\n\t\tviewModel.defaultWebtoonZoomOut.take(1).observe(viewLifecycleOwner) {\n\t\t\tbinding.frame.zoom = 1f - it\n\t\t}\n\t\tviewModel.isWebtoonGapsEnabled.observe(viewLifecycleOwner) {\n\t\t\tval rv = binding.recyclerView\n\t\t\trv.removeItemDecoration(WebtoonGapsDecoration::class.java)\n\t\t\tif (it) {\n\t\t\t\trv.addItemDecoration(WebtoonGapsDecoration())\n\t\t\t}\n\t\t}\n\t\tviewModel.readerSettingsProducer.observe(viewLifecycleOwner) {\n\t\t\tit.applyBackground(binding.root)\n\t\t}\n\t\tviewModel.isWebtoonPullGestureEnabled.observe(viewLifecycleOwner) { enabled ->\n\t\t\tbinding.recyclerView.isPullGestureEnabled = enabled\n\t\t}\n\t\tviewModel.uiState.observe(viewLifecycleOwner) { state ->\n\t\t\tif (state != null) {\n\t\t\t\tcanGoPrev = state.chapterIndex > 0\n\t\t\t\tcanGoNext = state.chapterIndex < state.chaptersTotal - 1\n\t\t\t} else {\n\t\t\t\tcanGoPrev = true\n\t\t\t\tcanGoNext = true\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\trecyclerLifecycleDispatcher = null\n\t\trequireViewBinding().recyclerView.adapter = null\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval offsetInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tviewBinding?.apply {\n\t\t\tfeedbackTop.updateLayoutParams<MarginLayoutParams> {\n\t\t\t\ttopMargin = bottomMargin + offsetInsets.top\n\t\t\t}\n\t\t\tfeedbackBottom.updateLayoutParams<MarginLayoutParams> {\n\t\t\t\tbottomMargin = topMargin + offsetInsets.bottom\n\t\t\t}\n\t\t}\n\t\treturn super.onApplyWindowInsets(v, insets)\n\t}\n\n\toverride fun onCreateAdapter() = WebtoonAdapter(\n\t\tlifecycleOwner = viewLifecycleOwner,\n\t\tloader = pageLoader,\n\t\treaderSettingsProducer = viewModel.readerSettingsProducer,\n\t\tnetworkState = networkState,\n\t\texceptionResolver = exceptionResolver,\n\t)\n\n\toverride fun onScrollChanged(\n\t\trecyclerView: WebtoonRecyclerView,\n\t\tdy: Int,\n\t\tfirstVisiblePosition: Int,\n\t\tlastVisiblePosition: Int,\n\t) {\n\t\tviewModel.onCurrentPageChanged(firstVisiblePosition, lastVisiblePosition)\n\t}\n\n\toverride suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {\n\t\tval setItems = launch {\n\t\t\trequireAdapter().setItems(pages)\n\t\t\tyield()\n\t\t\tviewBinding?.recyclerView?.let { rv ->\n\t\t\t\trecyclerLifecycleDispatcher?.invalidate(rv)\n\t\t\t}\n\t\t}\n\t\tif (pendingState != null) {\n\t\t\tval position = pages.indexOfFirst {\n\t\t\t\tit.chapterId == pendingState.chapterId && it.index == pendingState.page\n\t\t\t}\n\t\t\tsetItems.join()\n\t\t\tif (position != -1) {\n\t\t\t\twith(requireViewBinding().recyclerView) {\n\t\t\t\t\tfirstVisibleItemPosition = position\n\t\t\t\t\tpost {\n\t\t\t\t\t\t(findViewHolderForAdapterPosition(position) as? WebtoonHolder)\n\t\t\t\t\t\t\t?.restoreScroll(pendingState.scroll)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tviewModel.onCurrentPageChanged(position, position)\n\t\t\t} else {\n\t\t\t\tSnackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)\n\t\t\t\t\t.show()\n\t\t\t}\n\t\t} else {\n\t\t\tsetItems.join()\n\t\t}\n\t}\n\n\toverride fun getCurrentState(): ReaderState? = viewBinding?.run {\n\t\tval currentItem = recyclerView.findCurrentPagePosition()\n\t\tval adapter = recyclerView.adapter as? BaseReaderAdapter<*>\n\t\tval page = adapter?.getItemOrNull(currentItem) ?: return@run null\n\t\tReaderState(\n\t\t\tchapterId = page.chapterId,\n\t\t\tpage = page.index,\n\t\t\tscroll = (recyclerView.findViewHolderForAdapterPosition(currentItem) as? WebtoonHolder)?.getScrollY() ?: 0,\n\t\t)\n\t}\n\n\toverride fun onZoomIn() {\n\t\tviewBinding?.frame?.onZoomIn()\n\t}\n\n\toverride fun onZoomOut() {\n\t\tviewBinding?.frame?.onZoomOut()\n\t}\n\n\toverride fun switchPageBy(delta: Int) {\n\t\twith(requireViewBinding().recyclerView) {\n\t\t\tif (isAnimationEnabled()) {\n\t\t\t\tsmoothScrollBy(0, (height * 0.9).toInt() * delta, scrollInterpolator)\n\t\t\t} else {\n\t\t\t\tnestedScrollBy(0, (height * 0.9).toInt() * delta)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun switchPageTo(position: Int, smooth: Boolean) {\n\t\trequireViewBinding().recyclerView.firstVisibleItemPosition = position\n\t}\n\n\toverride fun scrollBy(delta: Int, smooth: Boolean): Boolean {\n\t\tif (smooth && isAnimationEnabled()) {\n\t\t\trequireViewBinding().recyclerView.smoothScrollBy(0, delta, scrollInterpolator)\n\t\t} else {\n\t\t\trequireViewBinding().recyclerView.nestedScrollBy(0, delta)\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onPullProgressTop(progress: Float) {\n\t\tval binding = viewBinding ?: return\n\t\tif (canGoPrev) {\n\t\t\tbinding.feedbackTop.setFeedbackText(getString(R.string.pull_to_prev_chapter))\n\t\t} else {\n\t\t\tbinding.feedbackTop.setFeedbackText(getString(R.string.pull_top_no_prev))\n\t\t}\n\t\tbinding.feedbackTop.updateFeedback(progress)\n\t}\n\n\toverride fun onPullProgressBottom(progress: Float) {\n\t\tval binding = viewBinding ?: return\n\t\tif (canGoNext) {\n\t\t\tbinding.feedbackBottom.setFeedbackText(getString(R.string.pull_to_next_chapter))\n\t\t} else {\n\t\t\tbinding.feedbackBottom.setFeedbackText(getString(R.string.pull_bottom_no_next))\n\t\t}\n\t\tbinding.feedbackBottom.updateFeedback(progress)\n\t}\n\n\toverride fun onPullTriggeredTop() {\n\t\t(viewBinding ?: return).feedbackTop.fadeOut()\n\t\tif (canGoPrev) {\n\t\t\tviewModel.switchChapterBy(-1)\n\t\t}\n\t}\n\n\toverride fun onPullTriggeredBottom() {\n\t\t(viewBinding ?: return).feedbackBottom.fadeOut()\n\t\tif (canGoNext) {\n\t\t\tviewModel.switchChapterBy(1)\n\t\t}\n\t}\n\n\toverride fun onPullCancelled() {\n\t\tviewBinding?.apply {\n\t\t\tfeedbackTop.fadeOut()\n\t\t\tfeedbackBottom.fadeOut()\n\t\t}\n\t}\n\n\tprivate fun RecyclerView.findCurrentPagePosition(): Int {\n\t\tval centerX = width / 2f\n\t\tval centerY = height - resources.getDimension(R.dimen.webtoon_pages_gap)\n\t\tif (centerY <= 0) {\n\t\t\treturn RecyclerView.NO_POSITION\n\t\t}\n\t\tval view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION\n\t\treturn getChildAdapterPosition(view)\n\t}\n\n\tprivate fun TextView.updateFeedback(progress: Float) {\n\t\tval clamped = progress.coerceIn(0f, 1.2f)\n\t\tthis.alpha = clamped.coerceAtMost(1f)\n\t\tthis.scaleX = 0.9f + 0.1f * clamped.coerceAtMost(1f)\n\t\tthis.scaleY = this.scaleX\n\t}\n\n\tprivate fun TextView.fadeOut() {\n\t\tanimate().alpha(0f).setDuration(150L).start()\n\t}\n\n\tprivate fun TextView.setFeedbackText(text: CharSequence) {\n\t\tif (this.alpha <= 0f && text.isNotEmpty()) {\n\t\t\tthis.alpha = 0f\n\t\t\tthis.text = text\n\t\t\tanimate().alpha(1f).setDuration(120L).start()\n\t\t} else {\n\t\t\tthis.text = text\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonRecyclerView.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.util.AttributeSet\nimport android.view.View\nimport android.widget.EdgeEffect\nimport androidx.core.view.ViewCompat.TYPE_TOUCH\nimport androidx.core.view.forEach\nimport androidx.core.view.isEmpty\nimport androidx.core.view.isNotEmpty\nimport androidx.core.view.iterator\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_BOTTOM\nimport androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_TOP\nimport java.util.Collections\nimport java.util.LinkedList\nimport java.util.WeakHashMap\n\nclass WebtoonRecyclerView @JvmOverloads constructor(\n\tcontext: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0\n) : RecyclerView(context, attrs, defStyleAttr) {\n\n\tprivate var onPageScrollListeners = LinkedList<OnWebtoonScrollListener>()\n\tprivate val scrollDispatcher = WebtoonScrollDispatcher()\n\tprivate val detachedViews = Collections.newSetFromMap(WeakHashMap<View, Boolean>())\n\tprivate var isFixingScroll = false\n\n\tvar isPullGestureEnabled: Boolean = false\n\t\tset(value) {\n\t\t\tif (field != value) {\n\t\t\t\tfield = value\n\t\t\t\tsetEdgeEffectFactory(\n\t\t\t\t\tif (value) {\n\t\t\t\t\t\tPullEffect.Factory()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tEdgeEffectFactory()\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\tvar pullThreshold: Float = 0.3f\n\tprivate var pullListener: OnPullGestureListener? = null\n\n\tfun setOnPullGestureListener(listener: OnPullGestureListener?) {\n\t\tpullListener = listener\n\t}\n\n\toverride fun onChildDetachedFromWindow(child: View) {\n\t\tsuper.onChildDetachedFromWindow(child)\n\t\tdetachedViews.add(child)\n\t}\n\n\toverride fun onChildAttachedToWindow(child: View) {\n\t\tsuper.onChildAttachedToWindow(child)\n\t\tdetachedViews.remove(child)\n\t}\n\n\toverride fun startNestedScroll(axes: Int) = startNestedScroll(axes, TYPE_TOUCH)\n\n\toverride fun startNestedScroll(axes: Int, type: Int): Boolean = isNotEmpty()\n\n\toverride fun dispatchNestedPreScroll(\n\t\tdx: Int,\n\t\tdy: Int,\n\t\tconsumed: IntArray?,\n\t\toffsetInWindow: IntArray?\n\t) = dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH)\n\n\toverride fun dispatchNestedPreScroll(\n\t\tdx: Int,\n\t\tdy: Int,\n\t\tconsumed: IntArray?,\n\t\toffsetInWindow: IntArray?,\n\t\ttype: Int\n\t): Boolean {\n\t\tval consumedY = consumeVerticalScroll(dy)\n\t\tif (consumed != null) {\n\t\t\tconsumed[0] = 0\n\t\t\tconsumed[1] = consumedY\n\t\t}\n\t\tnotifyScrollChanged(dy)\n\t\treturn consumedY != 0 || dy == 0\n\t}\n\n\tprivate fun consumeVerticalScroll(dy: Int): Int {\n\t\tif (isEmpty()) {\n\t\t\treturn 0\n\t\t}\n\t\twhen {\n\t\t\tdy > 0 -> {\n\t\t\t\tval child = getChildAt(0) as WebtoonFrameLayout\n\t\t\t\tvar consumedByChild = child.dispatchVerticalScroll(dy)\n\t\t\t\tif (consumedByChild < dy) {\n\t\t\t\t\tif (childCount > 1) {\n\t\t\t\t\t\tval nextChild = getChildAt(1) as WebtoonFrameLayout\n\t\t\t\t\t\tval unconsumed =\n\t\t\t\t\t\t\tdy - consumedByChild - nextChild.top //will be consumed by scroll\n\t\t\t\t\t\tif (unconsumed > 0) {\n\t\t\t\t\t\t\tconsumedByChild += nextChild.dispatchVerticalScroll(unconsumed)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn consumedByChild\n\t\t\t}\n\n\t\t\tdy < 0 -> {\n\t\t\t\tval child = getChildAt(childCount - 1) as WebtoonFrameLayout\n\t\t\t\tvar consumedByChild = child.dispatchVerticalScroll(dy)\n\t\t\t\tif (consumedByChild > dy) {\n\t\t\t\t\tif (childCount > 1) {\n\t\t\t\t\t\tval nextChild = getChildAt(childCount - 2) as WebtoonFrameLayout\n\t\t\t\t\t\tval unconsumed =\n\t\t\t\t\t\t\tdy - consumedByChild + (height - nextChild.bottom) //will be consumed by scroll\n\t\t\t\t\t\tif (unconsumed < 0) {\n\t\t\t\t\t\t\tconsumedByChild += nextChild.dispatchVerticalScroll(unconsumed)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn consumedByChild\n\t\t\t}\n\t\t}\n\t\treturn 0\n\t}\n\n\tfun addOnPageScrollListener(listener: OnWebtoonScrollListener) {\n\t\tonPageScrollListeners.add(listener)\n\t}\n\n\tfun removeOnPageScrollListener(listener: OnWebtoonScrollListener) {\n\t\tonPageScrollListeners.remove(listener)\n\t}\n\n\tprivate fun notifyScrollChanged(dy: Int) {\n\t\tval listeners = onPageScrollListeners\n\t\tif (listeners.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\tscrollDispatcher.dispatchScroll(this, dy)\n\t}\n\n\tfun relayoutChildren() {\n\t\tforEach { child ->\n\t\t\t(child as WebtoonFrameLayout).target.requestLayout()\n\t\t}\n\t\tdetachedViews.forEach { child ->\n\t\t\t(child as WebtoonFrameLayout).target.requestLayout()\n\t\t}\n\t}\n\n\tfun updateChildrenScroll() {\n\t\tif (isFixingScroll) {\n\t\t\treturn\n\t\t}\n\t\tisFixingScroll = true\n\t\tfor (child in this) {\n\t\t\tval ssiv = (child as WebtoonFrameLayout).target\n\t\t\tif (adjustScroll(child, ssiv)) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tisFixingScroll = false\n\t}\n\n\tprivate fun adjustScroll(child: View, ssiv: WebtoonImageView): Boolean = when {\n\t\tchild.bottom < height && ssiv.getScroll() < ssiv.getScrollRange() -> {\n\t\t\tval distance = minOf(height - child.bottom, ssiv.getScrollRange() - ssiv.getScroll())\n\t\t\tssiv.scrollBy(distance)\n\t\t\ttrue\n\t\t}\n\n\t\tchild.top > 0 && ssiv.getScroll() > 0 -> {\n\t\t\tval distance = minOf(child.top, ssiv.getScroll())\n\t\t\tssiv.scrollBy(-distance)\n\t\t\ttrue\n\t\t}\n\n\t\telse -> false\n\t}\n\n\tprivate class WebtoonScrollDispatcher {\n\n\t\tprivate var firstPos = NO_POSITION\n\t\tprivate var lastPos = NO_POSITION\n\n\t\tfun dispatchScroll(rv: WebtoonRecyclerView, dy: Int) {\n\t\t\tval lm = rv.layoutManager as? LinearLayoutManager\n\t\t\tif (lm == null) {\n\t\t\t\tfirstPos = NO_POSITION\n\t\t\t\tlastPos = NO_POSITION\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval newFirstPos = lm.findFirstVisibleItemPosition()\n\t\t\tval newLastPos = lm.findLastVisibleItemPosition()\n\t\t\tif (newFirstPos != firstPos || newLastPos != lastPos) {\n\t\t\t\tfirstPos = newFirstPos\n\t\t\t\tlastPos = newLastPos\n\t\t\t\tif (newFirstPos != NO_POSITION && newLastPos != NO_POSITION) {\n\t\t\t\t\trv.onPageScrollListeners.forEach { it.onScrollChanged(rv, dy, newFirstPos, newLastPos) }\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class PullEffect(\n\t\tview: RecyclerView,\n\t\tprivate val direction: Int,\n\t\tprivate val pullThreshold: Float,\n\t\tprivate val pullListener: OnPullGestureListener,\n\t) : EdgeEffect(view.context) {\n\n\t\tprivate var pullProgressTop: Float = 0f\n\t\tprivate var pullProgressBottom: Float = 0f\n\n\t\toverride fun onPull(deltaDistance: Float) {\n\t\t\tval sign = if (direction == DIRECTION_TOP) 1f else if (direction == DIRECTION_BOTTOM) 1f else 0f\n\t\t\tif (sign != 0f) onPull(deltaDistance, 0.5f)\n\t\t}\n\n\t\toverride fun onPull(deltaDistance: Float, displacement: Float) {\n\t\t\tif (direction == DIRECTION_TOP) {\n\t\t\t\tpullProgressTop = (pullProgressTop + deltaDistance).coerceAtLeast(0f)\n\t\t\t\tpullListener.onPullProgressTop(pullProgressTop / pullThreshold)\n\t\t\t} else if (direction == DIRECTION_BOTTOM) {\n\t\t\t\tpullProgressBottom = (pullProgressBottom + deltaDistance).coerceAtLeast(0f)\n\t\t\t\tpullListener.onPullProgressBottom(pullProgressBottom / pullThreshold)\n\t\t\t}\n\t\t}\n\n\t\toverride fun onRelease() {\n\t\t\tvar triggered = false\n\t\t\tif (direction == DIRECTION_TOP) {\n\t\t\t\tif (pullProgressTop >= pullThreshold) {\n\t\t\t\t\tpullListener.onPullTriggeredTop()\n\t\t\t\t\ttriggered = true\n\t\t\t\t}\n\t\t\t\tpullProgressTop = 0f\n\t\t\t\tpullListener.onPullProgressTop(0f)\n\t\t\t} else if (direction == DIRECTION_BOTTOM) {\n\t\t\t\tif (pullProgressBottom >= pullThreshold) {\n\t\t\t\t\tpullListener.onPullTriggeredBottom()\n\t\t\t\t\ttriggered = true\n\t\t\t\t}\n\t\t\t\tpullProgressBottom = 0f\n\t\t\t\tpullListener.onPullProgressBottom(0f)\n\t\t\t}\n\t\t\tif (!triggered) {\n\t\t\t\tpullListener.onPullCancelled()\n\t\t\t}\n\t\t}\n\n\t\toverride fun draw(canvas: Canvas?): Boolean = false\n\n\t\tclass Factory : EdgeEffectFactory() {\n\n\t\t\toverride fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {\n\t\t\t\tval pullListener = (view as? WebtoonRecyclerView)?.pullListener\n\t\t\t\treturn if (pullListener != null) {\n\t\t\t\t\tPullEffect(view, direction, view.pullThreshold, pullListener)\n\t\t\t\t} else {\n\t\t\t\t\tsuper.createEdgeEffect(view, direction)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tinterface OnWebtoonScrollListener {\n\n\t\tfun onScrollChanged(\n\t\t\trecyclerView: WebtoonRecyclerView,\n\t\t\tdy: Int,\n\t\t\tfirstVisiblePosition: Int,\n\t\t\tlastVisiblePosition: Int,\n\t\t)\n\t}\n\n\tinterface OnPullGestureListener {\n\t\tfun onPullProgressTop(progress: Float)\n\t\tfun onPullProgressBottom(progress: Float)\n\t\tfun onPullTriggeredTop()\n\t\tfun onPullTriggeredBottom()\n\t\tfun onPullCancelled()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.pager.webtoon\n\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.graphics.Matrix\nimport android.graphics.Point\nimport android.graphics.Rect\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.view.GestureDetector\nimport android.view.InputDevice\nimport android.view.KeyEvent\nimport android.view.MotionEvent\nimport android.view.ScaleGestureDetector\nimport android.view.ViewConfiguration\nimport android.view.animation.AccelerateDecelerateInterpolator\nimport android.view.animation.DecelerateInterpolator\nimport android.widget.FrameLayout\nimport android.widget.OverScroller\nimport androidx.core.animation.doOnEnd\nimport androidx.core.view.ViewConfigurationCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.widgets.ZoomControl\nimport org.koitharu.kotatsu.core.util.ext.getAnimationDuration\nimport kotlin.math.roundToInt\n\nprivate const val MAX_SCALE = 2.5f\nprivate const val MIN_SCALE = 0.5f\n\nprivate const val FLING_RANGE = 20_000\n\nclass WebtoonScalingFrame @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyles: Int = 0,\n) : FrameLayout(context, attrs, defStyles),\n\tScaleGestureDetector.OnScaleGestureListener,\n\tZoomControl.ZoomControlListener {\n\n\tprivate val scaleDetector = ScaleGestureDetector(context, this)\n\tprivate val gestureDetector = GestureDetector(context, GestureListener())\n\tprivate val overScroller = OverScroller(context, AccelerateDecelerateInterpolator())\n\n\tprivate val transformMatrix = Matrix()\n\tprivate val matrixValues = FloatArray(9)\n\tprivate val scale\n\t\tget() = matrixValues[Matrix.MSCALE_X]\n\tprivate val transX\n\t\tget() = halfWidth * (scale - 1f) + matrixValues[Matrix.MTRANS_X]\n\tprivate val transY\n\t\tget() = halfHeight * (scale - 1f) + matrixValues[Matrix.MTRANS_Y]\n\tprivate var halfWidth = 0f\n\tprivate var halfHeight = 0f\n\tprivate val translateBounds = RectF()\n\tprivate val targetHitRect = Rect()\n\tprivate var animator: ValueAnimator? = null\n\tprivate var pendingScroll = 0\n\n\tvar isZoomEnable = false\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tif (scale != 1f) {\n\t\t\t\tscaleChild(1f, halfWidth, halfHeight)\n\t\t\t}\n\t\t}\n\n\tvar zoom: Float\n\t\tget() = scale\n\t\tset(value) {\n\t\t\tif (value != scale) {\n\t\t\t\tscaleChild(value, halfWidth, halfHeight)\n\t\t\t\tonPostScale(invalidateLayout = true)\n\t\t\t}\n\t\t}\n\n\tinit {\n\t\tsyncMatrixValues()\n\t}\n\n\toverride fun dispatchTouchEvent(ev: MotionEvent?): Boolean {\n\t\tif (!isZoomEnable || ev == null) {\n\t\t\treturn super.dispatchTouchEvent(ev)\n\t\t}\n\n\t\tif (ev.action == MotionEvent.ACTION_DOWN && overScroller.computeScrollOffset()) {\n\t\t\toverScroller.forceFinished(true)\n\t\t}\n\n\t\tval consumed = gestureDetector.onTouchEvent(ev)\n\t\tscaleDetector.onTouchEvent(ev)\n\n\t\t// Offset event to inside the child view\n\t\tif (scale < 1 && !targetHitRect.contains(ev.x.toInt(), ev.y.toInt())) {\n\t\t\tev.offsetLocation(halfWidth - ev.x + targetHitRect.width() / 3, 0f)\n\t\t}\n\n\t\treturn consumed || scaleDetector.isInProgress || super.dispatchTouchEvent(ev)\n\t}\n\n\toverride fun onGenericMotionEvent(event: MotionEvent): Boolean {\n\t\tif (isZoomEnable && event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {\n\t\t\tif (event.actionMasked == MotionEvent.ACTION_SCROLL) {\n\t\t\t\tval withCtrl = event.metaState and KeyEvent.META_CTRL_MASK != 0\n\t\t\t\tif (withCtrl) {\n\t\t\t\t\tval axisValue =\n\t\t\t\t\t\tevent.getAxisValue(MotionEvent.AXIS_VSCROLL) * ViewConfigurationCompat.getScaledVerticalScrollFactor(\n\t\t\t\t\t\t\tViewConfiguration.get(context), context,\n\t\t\t\t\t\t)\n\t\t\t\t\tval newScale = (scale + axisValue).coerceIn(MIN_SCALE, MAX_SCALE)\n\t\t\t\t\tscaleChild(newScale, event.x, event.y)\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn super.onGenericMotionEvent(event)\n\t}\n\n\toverride fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n\t\tif (!isZoomEnable) {\n\t\t\treturn super.onKeyDown(keyCode, event)\n\t\t}\n\t\treturn when (keyCode) {\n\t\t\tKeyEvent.KEYCODE_ZOOM_IN,\n\t\t\tKeyEvent.KEYCODE_NUMPAD_ADD,\n\t\t\tKeyEvent.KEYCODE_PLUS -> {\n\t\t\t\tonZoomIn()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tKeyEvent.KEYCODE_ZOOM_OUT,\n\t\t\tKeyEvent.KEYCODE_NUMPAD_SUBTRACT,\n\t\t\tKeyEvent.KEYCODE_MINUS -> {\n\t\t\t\tonZoomOut()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tKeyEvent.KEYCODE_ESCAPE -> {\n\t\t\t\tsmoothScaleTo(1f)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onKeyDown(keyCode, event)\n\t\t}\n\t}\n\n\toverride fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {\n\t\treturn if (isZoomEnable) {\n\t\t\tkeyCode == KeyEvent.KEYCODE_NUMPAD_ADD\n\t\t\t\t|| keyCode == KeyEvent.KEYCODE_PLUS\n\t\t\t\t|| keyCode == KeyEvent.KEYCODE_NUMPAD_SUBTRACT\n\t\t\t\t|| keyCode == KeyEvent.KEYCODE_MINUS\n\t\t\t\t|| keyCode == KeyEvent.KEYCODE_ZOOM_IN\n\t\t\t\t|| keyCode == KeyEvent.KEYCODE_ZOOM_OUT\n\t\t\t\t|| keyCode == KeyEvent.KEYCODE_ESCAPE\n\t\t\t\t|| super.onKeyUp(keyCode, event)\n\t\t} else {\n\t\t\tsuper.onKeyUp(keyCode, event)\n\t\t}\n\t}\n\n\toverride fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n\t\tsuper.onSizeChanged(w, h, oldw, oldh)\n\t\thalfWidth = w / 2f\n\t\thalfHeight = h / 2f\n\t}\n\n\toverride fun onZoomIn() {\n\t\tsmoothScaleTo(scale * 1.1f)\n\t}\n\n\toverride fun onZoomOut() {\n\t\tsmoothScaleTo(scale * 0.9f)\n\t}\n\n\tprivate fun invalidateTarget() {\n\t\tval targetChild = findTargetChild()\n\t\tadjustBounds()\n\t\ttargetChild.run {\n\t\t\tif (!scale.isNaN()) {\n\t\t\t\tscaleX = scale\n\t\t\t\tscaleY = scale\n\t\t\t}\n\t\t\ttranslationX = transX\n\t\t\ttranslationY = transY\n\t\t\tif (pendingScroll != 0) {\n\t\t\t\tnestedScrollBy(0, pendingScroll)\n\t\t\t\tpendingScroll = 0\n\t\t\t}\n\t\t}\n\n\t\tval newHeight = if (scale < 1f) (height / scale).toInt() else height\n\t\tif (newHeight != targetChild.height) {\n\t\t\ttargetChild.layoutParams.height = newHeight\n\t\t\ttargetChild.requestLayout()\n\t\t\ttargetChild.relayoutChildren()\n\t\t}\n\n\t\tif (scale < 1) {\n\t\t\ttargetChild.getHitRect(targetHitRect)\n\t\t}\n\t}\n\n\tprivate fun syncMatrixValues() {\n\t\ttransformMatrix.getValues(matrixValues)\n\t}\n\n\tprivate fun adjustBounds() {\n\t\tsyncMatrixValues()\n\t\tval dx = when {\n\t\t\ttransX < translateBounds.left -> translateBounds.left - transX\n\t\t\ttransX > translateBounds.right -> translateBounds.right - transX\n\t\t\telse -> 0f\n\t\t}\n\n\t\tval dy = when {\n\t\t\ttransY < translateBounds.top -> translateBounds.top - transY\n\t\t\ttransY > translateBounds.bottom -> translateBounds.bottom - transY\n\t\t\telse -> 0f\n\t\t}\n\n\t\tpendingScroll = if (scale > 1) (dy / scale).roundToInt() else 0\n\t\ttransformMatrix.postTranslate(dx, dy)\n\t\tsyncMatrixValues()\n\t}\n\n\tprivate fun scaleChild(\n\t\tnewScale: Float,\n\t\tfocusX: Float,\n\t\tfocusY: Float,\n\t): Boolean {\n\t\tif (scale.isNaN() || scale == 0f) {\n\t\t\treturn false\n\t\t}\n\t\tval factor = newScale / scale\n\t\tif (newScale > 1) {\n\t\t\ttranslateBounds.set(\n\t\t\t\thalfWidth * (1 - newScale),\n\t\t\t\thalfHeight * (1 - newScale),\n\t\t\t\thalfWidth * (newScale - 1),\n\t\t\t\thalfHeight * (newScale - 1),\n\t\t\t)\n\t\t} else {\n\t\t\ttranslateBounds.set(\n\t\t\t\t0f,\n\t\t\t\thalfHeight - halfHeight / newScale,\n\t\t\t\t0f,\n\t\t\t\thalfHeight - halfHeight / newScale,\n\t\t\t)\n\t\t}\n\t\ttransformMatrix.postScale(factor, factor, focusX, focusY)\n\t\tinvalidateTarget()\n\t\treturn true\n\t}\n\n\toverride fun onScale(detector: ScaleGestureDetector): Boolean {\n\t\tval newScale = (scale * detector.scaleFactor).coerceIn(MIN_SCALE, MAX_SCALE)\n\t\treturn scaleChild(newScale, detector.focusX, detector.focusY)\n\t}\n\n\toverride fun onScaleBegin(detector: ScaleGestureDetector): Boolean {\n\t\tanimator?.cancel()\n\t\tanimator = null\n\t\treturn true\n\t}\n\n\toverride fun onScaleEnd(p0: ScaleGestureDetector) {\n\t\tonPostScale(invalidateLayout = false)\n\t}\n\n\tprivate fun onPostScale(invalidateLayout: Boolean) {\n\t\tval target = findTargetChild()\n\t\ttarget.post {\n\t\t\ttarget.updateChildrenScroll()\n\t\t\tif (invalidateLayout) {\n\t\t\t\ttarget.requestLayout()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun smoothScaleTo(target: Float) {\n\t\tval newScale = target.coerceIn(MIN_SCALE, MAX_SCALE)\n\t\tanimator?.cancel()\n\t\tanimator = ValueAnimator.ofFloat(scale, newScale).apply {\n\t\t\tsetDuration(context.getAnimationDuration(android.R.integer.config_shortAnimTime))\n\t\t\tinterpolator = DecelerateInterpolator()\n\t\t\taddUpdateListener { scaleChild(it.animatedValue as Float, halfWidth, halfHeight) }\n\t\t\tdoOnEnd { onPostScale(invalidateLayout = false) }\n\t\t\tstart()\n\t\t}\n\t}\n\n\tprivate fun findTargetChild() = getChildAt(0) as WebtoonRecyclerView\n\n\tprivate inner class GestureListener : GestureDetector.SimpleOnGestureListener(), Runnable {\n\t\tprivate val prevPos = Point()\n\n\t\toverride fun onScroll(\n\t\t\te1: MotionEvent?,\n\t\t\te2: MotionEvent,\n\t\t\tdistanceX: Float,\n\t\t\tdistanceY: Float,\n\t\t): Boolean {\n\t\t\tif (scale <= 1f || scale.isNaN()) return false\n\t\t\ttransformMatrix.postTranslate(-distanceX, -distanceY)\n\t\t\tinvalidateTarget()\n\t\t\treturn true\n\t\t}\n\n\t\toverride fun onDoubleTap(e: MotionEvent): Boolean {\n\t\t\tval newScale = if (scale != 1f) 1f else MAX_SCALE * 0.8f\n\t\t\tValueAnimator.ofFloat(scale, newScale).run {\n\t\t\t\tinterpolator = AccelerateDecelerateInterpolator()\n\t\t\t\tduration = context.getAnimationDuration(R.integer.config_defaultAnimTime)\n\t\t\t\taddUpdateListener {\n\t\t\t\t\tscaleChild(it.animatedValue as Float, e.x, e.y)\n\t\t\t\t}\n\t\t\t\tstart()\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\n\t\toverride fun onFling(\n\t\t\te1: MotionEvent?,\n\t\t\te2: MotionEvent,\n\t\t\tvelocityX: Float,\n\t\t\tvelocityY: Float,\n\t\t): Boolean {\n\t\t\tif (scale <= 1 || scale.isNaN()) return false\n\n\t\t\tprevPos.set(transX.toInt(), transY.toInt())\n\t\t\toverScroller.fling(\n\t\t\t\tprevPos.x,\n\t\t\t\tprevPos.y,\n\t\t\t\tvelocityX.toInt(),\n\t\t\t\tvelocityY.toInt(),\n\t\t\t\ttranslateBounds.left.toInt(),\n\t\t\t\ttranslateBounds.right.toInt(),\n\t\t\t\ttranslateBounds.top.toInt() - FLING_RANGE,\n\t\t\t\ttranslateBounds.bottom.toInt() + FLING_RANGE,\n\t\t\t)\n\t\t\tpostOnAnimation(this)\n\t\t\treturn true\n\t\t}\n\n\t\toverride fun run() {\n\t\t\tif (overScroller.computeScrollOffset()) {\n\t\t\t\ttransformMatrix.postTranslate(\n\t\t\t\t\toverScroller.currX.toFloat() - prevPos.x,\n\t\t\t\t\toverScroller.currY.toFloat() - prevPos.y,\n\t\t\t\t)\n\t\t\t\tprevPos.set(overScroller.currX, overScroller.currY)\n\t\t\t\tinvalidateTarget()\n\t\t\t\tpostOnAnimation(this)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapAction.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.tapgrid\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\n\nenum class TapAction(\n\t@StringRes val nameStringResId: Int,\n\tval color: Int,\n) {\n\n\tPAGE_NEXT(R.string.next_page, 0x8BFF00),\n\tPAGE_PREV(R.string.prev_page, 0xFF4700),\n\tCHAPTER_NEXT(R.string.next_chapter, 0x327E49),\n\tCHAPTER_PREV(R.string.prev_chapter, 0x7E1218),\n\tTOGGLE_UI(R.string.toggle_ui, 0x3D69C5),\n\tSHOW_MENU(R.string.show_menu, 0xAA1AC5),\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapGridDispatcher.kt",
    "content": "package org.koitharu.kotatsu.reader.ui.tapgrid\n\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimport android.view.View\nimport org.koitharu.kotatsu.reader.domain.TapGridArea\nimport kotlin.math.roundToInt\n\nclass TapGridDispatcher(\n\tprivate val rootView: View,\n\tprivate val listener: OnGridTouchListener,\n) : GestureDetector.SimpleOnGestureListener() {\n\n\tprivate val detector = GestureDetector(rootView.context, this)\n\tprivate var isDispatching = false\n\n\tinit {\n\t\tdetector.setIsLongpressEnabled(true)\n\t\tdetector.setOnDoubleTapListener(this)\n\t}\n\n\tfun dispatchTouchEvent(event: MotionEvent) {\n\t\tif (event.actionMasked == MotionEvent.ACTION_DOWN) {\n\t\t\tisDispatching = listener.onProcessTouch(event.rawX.toInt(), event.rawY.toInt())\n\t\t}\n\t\tdetector.onTouchEvent(event)\n\t}\n\n\toverride fun onSingleTapConfirmed(event: MotionEvent): Boolean {\n\t\tif (!isDispatching) {\n\t\t\treturn true\n\t\t}\n\t\tval area = getArea(event.rawX, event.rawY) ?: return false\n\t\treturn listener.onGridTouch(area)\n\t}\n\n\toverride fun onDoubleTapEvent(e: MotionEvent): Boolean {\n\t\tisDispatching = false // ignore long press after double tap\n\t\treturn super.onDoubleTapEvent(e)\n\t}\n\n\toverride fun onLongPress(event: MotionEvent) {\n\t\tif (isDispatching) {\n\t\t\tval area = getArea(event.rawX, event.rawY) ?: return\n\t\t\tlistener.onGridLongTouch(area)\n\t\t}\n\t}\n\n\tprivate fun getArea(x: Float, y: Float): TapGridArea? {\n\t\tval width = rootView.width\n\t\tval height = rootView.height\n\t\tif (height <= 0 || width <= 0) {\n\t\t\treturn null\n\t\t}\n\t\tval xIndex = (x * 2f / width).roundToInt()\n\t\tval yIndex = (y * 2f / height).roundToInt()\n\t\tval area = when (xIndex) {\n\t\t\t0 -> when (yIndex) { // LEFT\n\t\t\t\t0 -> TapGridArea.TOP_LEFT\n\t\t\t\t1 -> TapGridArea.CENTER_LEFT\n\t\t\t\t2 -> TapGridArea.BOTTOM_LEFT\n\t\t\t\telse -> null\n\t\t\t}\n\n\t\t\t1 -> when (yIndex) { // CENTER\n\t\t\t\t0 -> TapGridArea.TOP_CENTER\n\t\t\t\t1 -> TapGridArea.CENTER\n\t\t\t\t2 -> TapGridArea.BOTTOM_CENTER\n\t\t\t\telse -> null\n\t\t\t}\n\n\t\t\t2 -> when (yIndex) { // RIGHT\n\t\t\t\t0 -> TapGridArea.TOP_RIGHT\n\t\t\t\t1 -> TapGridArea.CENTER_RIGHT\n\t\t\t\t2 -> TapGridArea.BOTTOM_RIGHT\n\t\t\t\telse -> null\n\t\t\t}\n\n\t\t\telse -> null\n\t\t}\n\t\tassert(area != null) { \"Invalid area ($xIndex, $yIndex)\" }\n\t\treturn area\n\t}\n\n\tinterface OnGridTouchListener {\n\n\t\tfun onGridTouch(area: TapGridArea): Boolean\n\n\t\tfun onGridLongTouch(area: TapGridArea)\n\n\t\tfun onProcessTouch(rawX: Int, rawY: Int): Boolean\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/MangaSearchMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.remotelist.ui\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.inputmethod.EditorInfoCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\n\nclass MangaSearchMenuProvider(\n\tprivate val filter: FilterCoordinator,\n\tprivate val viewModel: MangaListViewModel,\n) : MenuProvider, MenuItem.OnActionExpandListener, SearchView.OnQueryTextListener {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_search, menu)\n\t\tval menuItem = menu.findItem(R.id.action_search)\n\t\tmenuItem.setOnActionExpandListener(this)\n\t\tval searchView = menuItem.actionView as SearchView\n\t\tsearchView.setOnQueryTextListener(this)\n\t\tsearchView.queryHint = menuItem.title\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tmenu.findItem(R.id.action_search)?.isVisible = filter.capabilities.isSearchSupported\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = false\n\n\toverride fun onQueryTextSubmit(query: String?): Boolean {\n\t\tval snapshot = filter.snapshot()\n\t\tif (!query.isNullOrEmpty() && !filter.capabilities.isSearchWithFiltersSupported && snapshot.listFilter.hasNonSearchOptions()) {\n\t\t\tfilter.set(MangaListFilter(query = query))\n\t\t\tviewModel.onActionDone.call(\n\t\t\t\tReversibleAction(R.string.filter_search_warning) { filter.set(snapshot.listFilter) },\n\t\t\t)\n\t\t} else {\n\t\t\tfilter.setQuery(query)\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onQueryTextChange(newText: String?): Boolean = false\n\n\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\t(item.actionView as? SearchView)?.run {\n\t\t\tpost { adjustSearchView() }\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean = true\n\n\tprivate fun SearchView.adjustSearchView() {\n\t\timeOptions = if (viewModel.isIncognitoModeEnabled) {\n\t\t\timeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING\n\t\t} else {\n\t\t\timeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()\n\t\t}\n\t\tsetQuery(filter.query.value, false)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt",
    "content": "package org.koitharu.kotatsu.remotelist.ui\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.distinctUntilChangedBy\nimport kotlinx.coroutines.flow.drop\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.getCauseUrl\nimport org.koitharu.kotatsu.core.util.ext.isHttpUrl\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.withArgs\nimport org.koitharu.kotatsu.databinding.FragmentListBinding\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.search.domain.SearchKind\n\n@AndroidEntryPoint\nclass RemoteListFragment : MangaListFragment(), FilterCoordinator.Owner, View.OnClickListener {\n\n    override val viewModel by viewModels<RemoteListViewModel>()\n\n    override val filterCoordinator: FilterCoordinator\n        get() = viewModel.filterCoordinator\n\n    override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {\n        super.onViewBindingCreated(binding, savedInstanceState)\n        addMenuProvider(RemoteListMenuProvider())\n        addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel))\n        viewModel.isRandomLoading.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))\n        viewModel.onOpenManga.observeEvent(viewLifecycleOwner) { router.openDetails(it) }\n        viewModel.onSourceBroken.observeEvent(viewLifecycleOwner) { showSourceBrokenWarning() }\n        filterCoordinator.observe().distinctUntilChangedBy { it.listFilter.isEmpty() }\n            .drop(1)\n            .observe(viewLifecycleOwner) {\n                activity?.invalidateMenu()\n            }\n    }\n\n    override fun onScrolledToEnd() {\n        viewModel.loadNextPage()\n    }\n\n    override fun onCreateActionMode(\n        controller: ListSelectionController,\n        menuInflater: MenuInflater,\n        menu: Menu\n    ): Boolean {\n        menuInflater.inflate(R.menu.mode_remote, menu)\n        return super.onCreateActionMode(controller, menuInflater, menu)\n    }\n\n    override fun onFilterClick(view: View?) {\n        router.showFilterSheet()\n    }\n\n    override fun onEmptyActionClick() {\n        if (filterCoordinator.isFilterApplied) {\n            filterCoordinator.reset()\n        } else {\n            openInBrowser(null) // should never be called\n        }\n    }\n\n    override fun onFooterButtonClick() {\n        val filter = filterCoordinator.snapshot().listFilter\n        when {\n            !filter.query.isNullOrEmpty() -> router.openSearch(filter.query.orEmpty(), SearchKind.SIMPLE)\n            !filter.author.isNullOrEmpty() -> router.openSearch(filter.author.orEmpty(), SearchKind.AUTHOR)\n            filter.tags.size == 1 -> router.openSearch(filter.tags.singleOrNull()?.title.orEmpty(), SearchKind.TAG)\n        }\n    }\n\n    override fun onSecondaryErrorActionClick(error: Throwable) {\n        openInBrowser(error.getCauseUrl())\n    }\n\n    override fun onClick(v: View?) = Unit // from Snackbar, do nothing\n\n    private fun openInBrowser(url: String?) {\n        if (url?.isHttpUrl() == true) {\n            router.openBrowser(\n                url = url,\n                source = viewModel.source,\n                title = viewModel.source.getTitle(requireContext()),\n            )\n        } else {\n            Snackbar.make(requireViewBinding().recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT)\n                .show()\n        }\n    }\n\n    private fun showSourceBrokenWarning() {\n        val snackbar = Snackbar.make(\n            viewBinding?.recyclerView ?: return,\n            R.string.source_broken_warning,\n            Snackbar.LENGTH_INDEFINITE,\n        )\n        snackbar.setAction(R.string.got_it, this)\n        snackbar.show()\n    }\n\n    private inner class RemoteListMenuProvider : MenuProvider {\n\n        override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n            menuInflater.inflate(R.menu.opt_list_remote, menu)\n        }\n\n        override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n            R.id.action_source_settings -> {\n                router.openSourceSettings(viewModel.source)\n                true\n            }\n\n            R.id.action_random -> {\n                viewModel.openRandom()\n                true\n            }\n\n            R.id.action_filter -> {\n                onFilterClick(null)\n                true\n            }\n\n            R.id.action_filter_reset -> {\n                filterCoordinator.reset()\n                true\n            }\n\n            else -> false\n        }\n\n        override fun onPrepareMenu(menu: Menu) {\n            super.onPrepareMenu(menu)\n            menu.findItem(R.id.action_random)?.isEnabled = !viewModel.isRandomLoading.value\n            menu.findItem(R.id.action_filter_reset)?.isVisible = filterCoordinator.isFilterApplied\n        }\n    }\n\n    companion object {\n\n        const val ARG_SOURCE = \"provider\"\n\n        fun newInstance(source: MangaSource) = RemoteListFragment().withArgs(1) {\n            putString(ARG_SOURCE, source.name)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt",
    "content": "package org.koitharu.kotatsu.remotelist.ui\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.model.distinctById\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.getCauseUrl\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.explore.domain.ExploreRepository\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.list.ui.model.ButtonFooter\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingFooter\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorFooter\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.util.sizeOrZero\nimport javax.inject.Inject\n\nprivate const val FILTER_MIN_INTERVAL = 250L\n\n@HiltViewModel\nopen class RemoteListViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n\tfinal override val filterCoordinator: FilterCoordinator,\n\tsettings: AppSettings,\n\tprotected val mangaListMapper: MangaListMapper,\n\tprivate val exploreRepository: ExploreRepository,\n\tsourcesRepository: MangaSourcesRepository,\n\tmangaDataRepository: MangaDataRepository,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>\n) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), FilterCoordinator.Owner {\n\n\tval source = MangaSource(savedStateHandle[RemoteListFragment.ARG_SOURCE])\n\tval isRandomLoading = MutableStateFlow(false)\n\tval onOpenManga = MutableEventFlow<Manga>()\n    val onSourceBroken = MutableEventFlow<Unit>()\n\n\tprotected val repository = mangaRepositoryFactory.create(source)\n\tprivate val mangaList = MutableStateFlow<List<Manga>?>(null)\n\tprivate val hasNextPage = MutableStateFlow(false)\n\tprivate val listError = MutableStateFlow<Throwable?>(null)\n\tprivate var loadingJob: Job? = null\n\tprivate var randomJob: Job? = null\n\n\toverride val content = combine(\n\t\tmangaList.map { it?.skipNsfwIfNeeded() },\n\t\tobserveListModeWithTriggers(),\n\t\tlistError,\n\t\thasNextPage,\n\t) { list, mode, error, hasNext ->\n\t\tbuildList(list?.size?.plus(2) ?: 2) {\n\t\t\twhen {\n\t\t\t\tlist.isNullOrEmpty() && error != null -> add(\n\t\t\t\t\terror.toErrorState(\n\t\t\t\t\t\tcanRetry = true,\n\t\t\t\t\t\tsecondaryAction = if (error.getCauseUrl().isNullOrEmpty()) 0 else R.string.open_in_browser,\n\t\t\t\t\t),\n\t\t\t\t)\n\n\t\t\t\tlist == null -> add(LoadingState)\n\t\t\t\tlist.isEmpty() -> add(createEmptyState(canResetFilter = filterCoordinator.isFilterApplied))\n\t\t\t\telse -> {\n\t\t\t\t\tmapMangaList(this, list, mode)\n\t\t\t\t\twhen {\n\t\t\t\t\t\terror != null -> add(error.toErrorFooter())\n\t\t\t\t\t\thasNext -> add(LoadingFooter())\n\t\t\t\t\t\telse -> getFooter()?.let(::add)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tonBuildList(this)\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, listOf(LoadingState))\n\n\tinit {\n\t\tfilterCoordinator.observe()\n\t\t\t.debounce(FILTER_MIN_INTERVAL)\n\t\t\t.onEach { filterState ->\n\t\t\t\tloadingJob?.cancelAndJoin()\n\t\t\t\tmangaList.value = null\n\t\t\t\tloadList(filterState, false)\n\t\t\t}.catch { error ->\n\t\t\t\tlistError.value = error\n\t\t\t}.launchIn(viewModelScope)\n\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tsourcesRepository.trackUsage(source)\n\t\t}\n\n        if (source is MangaParserSource && source.isBroken) {\n            // Just notify one. Will show reason in future\n            onSourceBroken.call(Unit)\n        }\n\t}\n\n\toverride fun onRefresh() {\n\t\tloadList(filterCoordinator.snapshot(), append = false)\n\t}\n\n\toverride fun onRetry() {\n\t\tloadList(filterCoordinator.snapshot(), append = !mangaList.value.isNullOrEmpty())\n\t}\n\n\tfun loadNextPage() {\n\t\tif (hasNextPage.value && listError.value == null) {\n\t\t\tloadList(filterCoordinator.snapshot(), append = true)\n\t\t}\n\t}\n\n\tprotected fun loadList(filterState: FilterCoordinator.Snapshot, append: Boolean): Job {\n\t\tloadingJob?.let {\n\t\t\tif (it.isActive) return it\n\t\t}\n\t\treturn launchLoadingJob(Dispatchers.Default) {\n\t\t\ttry {\n\t\t\t\tlistError.value = null\n\t\t\t\tval list = repository.getList(\n\t\t\t\t\toffset = if (append) mangaList.value.sizeOrZero() else 0,\n\t\t\t\t\torder = filterState.sortOrder,\n\t\t\t\t\tfilter = filterState.listFilter,\n\t\t\t\t)\n\t\t\t\tval prevList = mangaList.value.orEmpty()\n\t\t\t\tif (!append) {\n\t\t\t\t\tmangaList.value = list.distinctById()\n\t\t\t\t} else if (list.isNotEmpty()) {\n\t\t\t\t\tmangaList.value = (prevList + list).distinctById()\n\t\t\t\t}\n\t\t\t\thasNextPage.value = if (append) {\n\t\t\t\t\tprevList != mangaList.value\n\t\t\t\t} else {\n\t\t\t\t\tlist.size > prevList.size || hasNextPage.value\n\t\t\t\t}\n\t\t\t} catch (e: CancellationException) {\n\t\t\t\tthrow e\n\t\t\t} catch (e: Throwable) {\n\t\t\t\te.printStackTraceDebug()\n\t\t\t\tlistError.value = e\n\t\t\t\tif (!mangaList.value.isNullOrEmpty()) {\n\t\t\t\t\terrorEvent.call(e)\n\t\t\t\t}\n\t\t\t\thasNextPage.value = false\n\t\t\t}\n\t\t}.also { loadingJob = it }\n\t}\n\n\tprotected open fun createEmptyState(canResetFilter: Boolean) = EmptyState(\n\t\ticon = R.drawable.ic_empty_common,\n\t\ttextPrimary = R.string.nothing_found,\n\t\ttextSecondary = 0,\n\t\tactionStringRes = if (canResetFilter) R.string.reset_filter else 0,\n\t)\n\n\tprotected open suspend fun onBuildList(list: MutableList<ListModel>) = Unit\n\n\tprotected open suspend fun mapMangaList(\n\t\tdestination: MutableCollection<in ListModel>,\n\t\tmanga: Collection<Manga>,\n\t\tmode: ListMode\n\t) = mangaListMapper.toListModelList(destination, manga, mode)\n\n\tprotected open fun getFooter(): ButtonFooter? {\n\t\tval filter = filterCoordinator.snapshot().listFilter\n\t\tval hasQuery = !filter.query.isNullOrEmpty()\n\t\tval hasAuthor = !filter.author.isNullOrEmpty()\n\t\tval isOneTag = filter.tags.size == 1\n\t\treturn if ((hasQuery xor isOneTag xor hasAuthor) && !(hasQuery && isOneTag && hasAuthor)) {\n\t\t\tButtonFooter(R.string.global_search)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n\n\tfun openRandom() {\n\t\tif (randomJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\trandomJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tisRandomLoading.value = true\n\t\t\tval manga = exploreRepository.findRandomManga(source, 16)\n\t\t\tonOpenManga.call(manga)\n\t\t\tisRandomLoading.value = false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt",
    "content": "package org.koitharu.kotatsu.scrobbling\n\nimport android.content.Context\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport dagger.multibindings.ElementsIntoSet\nimport okhttp3.OkHttpClient\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.network.CurlLoggingInterceptor\nimport org.koitharu.kotatsu.scrobbling.anilist.data.AniListAuthenticator\nimport org.koitharu.kotatsu.scrobbling.anilist.data.AniListInterceptor\nimport org.koitharu.kotatsu.scrobbling.anilist.domain.AniListScrobbler\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuAuthenticator\nimport org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuInterceptor\nimport org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuRepository\nimport org.koitharu.kotatsu.scrobbling.kitsu.domain.KitsuScrobbler\nimport org.koitharu.kotatsu.scrobbling.mal.data.MALAuthenticator\nimport org.koitharu.kotatsu.scrobbling.mal.data.MALInterceptor\nimport org.koitharu.kotatsu.scrobbling.mal.domain.MALScrobbler\nimport org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriAuthenticator\nimport org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriInterceptor\nimport org.koitharu.kotatsu.scrobbling.shikimori.domain.ShikimoriScrobbler\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject ScrobblingModule {\n\n\t@Provides\n\t@Singleton\n\t@ScrobblerType(ScrobblerService.SHIKIMORI)\n\tfun provideShikimoriHttpClient(\n\t\t@BaseHttpClient baseHttpClient: OkHttpClient,\n\t\tauthenticator: ShikimoriAuthenticator,\n\t\t@ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage,\n\t): OkHttpClient = baseHttpClient.newBuilder().apply {\n\t\tauthenticator(authenticator)\n\t\taddInterceptor(ShikimoriInterceptor(storage))\n\t}.build()\n\n\t@Provides\n\t@Singleton\n\t@ScrobblerType(ScrobblerService.MAL)\n\tfun provideMALHttpClient(\n\t\t@BaseHttpClient baseHttpClient: OkHttpClient,\n\t\tauthenticator: MALAuthenticator,\n\t\t@ScrobblerType(ScrobblerService.MAL) storage: ScrobblerStorage,\n\t): OkHttpClient = baseHttpClient.newBuilder().apply {\n\t\tauthenticator(authenticator)\n\t\taddInterceptor(MALInterceptor(storage))\n\t}.build()\n\n\t@Provides\n\t@Singleton\n\t@ScrobblerType(ScrobblerService.ANILIST)\n\tfun provideAniListHttpClient(\n\t\t@BaseHttpClient baseHttpClient: OkHttpClient,\n\t\tauthenticator: AniListAuthenticator,\n\t\t@ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage,\n\t): OkHttpClient = baseHttpClient.newBuilder().apply {\n\t\tauthenticator(authenticator)\n\t\taddInterceptor(AniListInterceptor(storage))\n\t}.build()\n\n\t@Provides\n\t@Singleton\n\tfun provideKitsuRepository(\n\t\t@ApplicationContext context: Context,\n\t\t@ScrobblerType(ScrobblerService.KITSU) storage: ScrobblerStorage,\n\t\tdatabase: MangaDatabase,\n\t\tauthenticator: KitsuAuthenticator,\n\t): KitsuRepository {\n\t\tval okHttp = OkHttpClient.Builder().apply {\n\t\t\tauthenticator(authenticator)\n\t\t\taddInterceptor(KitsuInterceptor(storage))\n\t\t\tif (BuildConfig.DEBUG) {\n\t\t\t\taddInterceptor(CurlLoggingInterceptor())\n\t\t\t}\n\t\t}.build()\n\t\treturn KitsuRepository(context, okHttp, storage, database)\n\t}\n\n\t@Provides\n\t@Singleton\n\t@ScrobblerType(ScrobblerService.ANILIST)\n\tfun provideAniListStorage(\n\t\t@ApplicationContext context: Context,\n\t): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.ANILIST)\n\n\t@Provides\n\t@Singleton\n\t@ScrobblerType(ScrobblerService.SHIKIMORI)\n\tfun provideShikimoriStorage(\n\t\t@ApplicationContext context: Context,\n\t): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.SHIKIMORI)\n\n\t@Provides\n\t@Singleton\n\t@ScrobblerType(ScrobblerService.MAL)\n\tfun provideMALStorage(\n\t\t@ApplicationContext context: Context,\n\t): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.MAL)\n\n\t@Provides\n\t@Singleton\n\t@ScrobblerType(ScrobblerService.KITSU)\n\tfun provideKitsuStorage(\n\t\t@ApplicationContext context: Context,\n\t): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.KITSU)\n\n\t@Provides\n\t@ElementsIntoSet\n\tfun provideScrobblers(\n\t\tshikimoriScrobbler: ShikimoriScrobbler,\n\t\taniListScrobbler: AniListScrobbler,\n\t\tmalScrobbler: MALScrobbler,\n\t\tkitsuScrobbler: KitsuScrobbler\n\t): Set<@JvmSuppressWildcards Scrobbler> = setOf(shikimoriScrobbler, aniListScrobbler, malScrobbler, kitsuScrobbler)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/anilist/data/AniListAuthenticator.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.anilist.data\n\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.Authenticator\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Route\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport javax.inject.Inject\nimport javax.inject.Provider\n\nclass AniListAuthenticator @Inject constructor(\n\t@ScrobblerType(ScrobblerService.ANILIST) private val storage: ScrobblerStorage,\n\tprivate val repositoryProvider: Provider<AniListRepository>,\n) : Authenticator {\n\n\toverride fun authenticate(route: Route?, response: Response): Request? {\n\t\tval accessToken = storage.accessToken ?: return null\n\t\tif (!isRequestWithAccessToken(response)) {\n\t\t\treturn null\n\t\t}\n\t\tsynchronized(this) {\n\t\t\tval newAccessToken = storage.accessToken ?: return null\n\t\t\tif (accessToken != newAccessToken) {\n\t\t\t\treturn newRequestWithAccessToken(response.request, newAccessToken)\n\t\t\t}\n\t\t\tval updatedAccessToken = refreshAccessToken() ?: return null\n\t\t\treturn newRequestWithAccessToken(response.request, updatedAccessToken)\n\t\t}\n\t}\n\n\tprivate fun isRequestWithAccessToken(response: Response): Boolean {\n\t\tval header = response.request.header(CommonHeaders.AUTHORIZATION)\n\t\treturn header?.startsWith(\"Bearer\") == true\n\t}\n\n\tprivate fun newRequestWithAccessToken(request: Request, accessToken: String): Request {\n\t\treturn request.newBuilder()\n\t\t\t.header(CommonHeaders.AUTHORIZATION, \"Bearer $accessToken\")\n\t\t\t.build()\n\t}\n\n\tprivate fun refreshAccessToken(): String? = runCatching {\n\t\tval repository = repositoryProvider.get()\n\t\trunBlocking { repository.authorize(null) }\n\t\treturn storage.accessToken\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/anilist/data/AniListInterceptor.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.anilist.data\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport java.net.HttpURLConnection\n\nprivate const val JSON = \"application/json\"\n\nclass AniListInterceptor(private val storage: ScrobblerStorage) : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval sourceRequest = chain.request()\n\t\tval request = sourceRequest.newBuilder()\n\t\trequest.header(CommonHeaders.CONTENT_TYPE, JSON)\n\t\trequest.header(CommonHeaders.ACCEPT, JSON)\n\t\tval isAuthRequest = sourceRequest.url.pathSegments.contains(\"oauth\")\n\t\tif (!isAuthRequest) {\n\t\t\tstorage.accessToken?.let {\n\t\t\t\trequest.header(CommonHeaders.AUTHORIZATION, \"Bearer $it\")\n\t\t\t}\n\t\t}\n\t\tval response = chain.proceed(request.build())\n\t\tif (!isAuthRequest && response.code == HttpURLConnection.HTTP_UNAUTHORIZED) {\n\t\t\tthrow ScrobblerAuthRequiredException(ScrobblerService.ANILIST)\n\t\t}\n\t\treturn response\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.anilist.data\n\nimport android.content.Context\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport okhttp3.FormBody\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.parsers.exception.GraphQLException\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.json.getStringOrNull\nimport org.koitharu.kotatsu.parsers.util.json.mapJSON\nimport org.koitharu.kotatsu.parsers.util.parseJson\nimport org.koitharu.kotatsu.parsers.util.toIntUp\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport kotlin.math.roundToInt\n\nprivate const val REDIRECT_URI = \"kotatsu://anilist-auth\"\nprivate const val BASE_URL = \"https://anilist.co/api/v2/\"\nprivate const val ENDPOINT = \"https://graphql.anilist.co\"\nprivate const val MANGA_PAGE_SIZE = 10\nprivate const val REQUEST_QUERY = \"query\"\nprivate const val REQUEST_MUTATION = \"mutation\"\nprivate const val KEY_SCORE_FORMAT = \"score_format\"\n\n@Singleton\nclass AniListRepository @Inject constructor(\n\t@ApplicationContext context: Context,\n\t@ScrobblerType(ScrobblerService.ANILIST) private val okHttp: OkHttpClient,\n\t@ScrobblerType(ScrobblerService.ANILIST) private val storage: ScrobblerStorage,\n\tprivate val db: MangaDatabase,\n) : ScrobblerRepository {\n\n\tprivate val clientId = context.getString(R.string.anilist_clientId)\n\tprivate val clientSecret = context.getString(R.string.anilist_clientSecret)\n\n\toverride val oauthUrl: String\n\t\tget() = \"${BASE_URL}oauth/authorize?client_id=$clientId&\" +\n\t\t\t\"redirect_uri=${REDIRECT_URI}&response_type=code\"\n\n\toverride val isAuthorized: Boolean\n\t\tget() = storage.accessToken != null\n\n\tprivate val shrinkRegex = Regex(\"\\\\t+\")\n\n\toverride suspend fun authorize(code: String?) {\n\t\tval body = FormBody.Builder()\n\t\tbody.add(\"client_id\", clientId)\n\t\tbody.add(\"client_secret\", clientSecret)\n\t\tif (code != null) {\n\t\t\tbody.add(\"grant_type\", \"authorization_code\")\n\t\t\tbody.add(\"redirect_uri\", REDIRECT_URI)\n\t\t\tbody.add(\"code\", code)\n\t\t} else {\n\t\t\tbody.add(\"grant_type\", \"refresh_token\")\n\t\t\tbody.add(\"refresh_token\", checkNotNull(storage.refreshToken))\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.post(body.build())\n\t\t\t.url(\"${BASE_URL}oauth/token\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\tstorage.accessToken = response.getString(\"access_token\")\n\t\tstorage.refreshToken = response.getString(\"refresh_token\")\n\t}\n\n\toverride suspend fun loadUser(): ScrobblerUser {\n\t\tval response = doRequest(\n\t\t\tREQUEST_QUERY,\n\t\t\t\"\"\"\n\t\t\tAniChartUser {\n\t\t\t\tuser {\n\t\t\t\t\tid\n\t\t\t\t\tname\n\t\t\t\t\tavatar {\n\t\t\t\t\t\tmedium\n\t\t\t\t\t}\n\t\t\t\t\tmediaListOptions {\n\t\t\t\t\t\tscoreFormat\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\"\"\",\n\t\t)\n\t\tval jo = response.getJSONObject(\"data\").getJSONObject(\"AniChartUser\").getJSONObject(\"user\")\n\t\tstorage[KEY_SCORE_FORMAT] = jo.getJSONObject(\"mediaListOptions\").getString(\"scoreFormat\")\n\t\treturn AniListUser(jo).also { storage.user = it }\n\t}\n\n\toverride val cachedUser: ScrobblerUser?\n\t\tget() {\n\t\t\treturn storage.user\n\t\t}\n\n\toverride suspend fun unregister(mangaId: Long) {\n\t\treturn db.getScrobblingDao().delete(ScrobblerService.ANILIST.id, mangaId)\n\t}\n\n\toverride fun logout() {\n\t\tstorage.clear()\n\t}\n\n\toverride suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {\n\t\tval page = (offset / MANGA_PAGE_SIZE.toFloat()).toIntUp() + 1\n\t\tval response = doRequest(\n\t\t\tREQUEST_QUERY,\n\t\t\t\"\"\"\n\t\t\tPage(page: $page, perPage: ${MANGA_PAGE_SIZE}) {\n\t\t\t\tmedia(type: MANGA, sort: SEARCH_MATCH, search: ${JSONObject.quote(query)}) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle {\n\t\t\t\t\t\tuserPreferred\n\t\t\t\t\t\tnative\n\t\t\t\t\t}\n\t\t\t\t\tcoverImage {\n\t\t\t\t\t\tmedium\n\t\t\t\t\t}\n\t\t\t\t\tsiteUrl\n\t\t\t\t}\n\t\t\t}\n\t\t\"\"\",\n\t\t)\n\t\tval data = response.getJSONObject(\"data\").getJSONObject(\"Page\").getJSONArray(\"media\")\n\t\treturn data.mapJSON { ScrobblerManga(it, query) }\n\t}\n\n\toverride suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) {\n\t\tval response = doRequest(\n\t\t\tREQUEST_MUTATION,\n\t\t\t\"\"\"\n\t\t\t\tSaveMediaListEntry(mediaId: $scrobblerMangaId) {\n\t\t\t\t\tid\n\t\t\t\t\tmediaId\n\t\t\t\t\tstatus\n\t\t\t\t\tnotes\n\t\t\t\t\tscore\n\t\t\t\t\tprogress\n\t\t\t\t}\n\t\t\t\"\"\",\n\t\t)\n\t\tsaveRate(response.getJSONObject(\"data\").getJSONObject(\"SaveMediaListEntry\"), mangaId)\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, chapter: Int) {\n\t\tval response = doRequest(\n\t\t\tREQUEST_MUTATION,\n\t\t\t\"\"\"\n\t\t\t\tSaveMediaListEntry(id: $rateId, progress: $chapter) {\n\t\t\t\t\tid\n\t\t\t\t\tmediaId\n\t\t\t\t\tstatus\n\t\t\t\t\tnotes\n\t\t\t\t\tscore\n\t\t\t\t\tprogress\n\t\t\t\t}\n\t\t\t\"\"\",\n\t\t)\n\t\tsaveRate(response.getJSONObject(\"data\").getJSONObject(\"SaveMediaListEntry\"), mangaId)\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) {\n\t\tval scoreRaw = (rating * 100f).roundToInt()\n\t\tval statusString = status?.let { \", status: $it\" }.orEmpty()\n\t\tval notesString = comment?.let { \", notes: ${JSONObject.quote(it)}\" }.orEmpty()\n\t\tval response = doRequest(\n\t\t\tREQUEST_MUTATION,\n\t\t\t\"\"\"\n\t\t\t\tSaveMediaListEntry(id: $rateId, scoreRaw: $scoreRaw$statusString$notesString) {\n\t\t\t\t\tid\n\t\t\t\t\tmediaId\n\t\t\t\t\tstatus\n\t\t\t\t\tnotes\n\t\t\t\t\tscore\n\t\t\t\t\tprogress\n\t\t\t\t}\n\t\t\t\"\"\",\n\t\t)\n\t\tsaveRate(response.getJSONObject(\"data\").getJSONObject(\"SaveMediaListEntry\"), mangaId)\n\t}\n\n\toverride suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {\n\t\tval response = doRequest(\n\t\t\tREQUEST_QUERY,\n\t\t\t\"\"\"\n\t\t\tMedia(id: $id) {\n\t\t\t\tid\n\t\t\t\ttitle {\n\t\t\t\t\tuserPreferred\n\t\t\t\t}\n\t\t\t\tcoverImage {\n\t\t\t\t\tlarge\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tsiteUrl\n\t\t\t}\n\t\t\t\"\"\",\n\t\t)\n\t\treturn ScrobblerMangaInfo(response.getJSONObject(\"data\").getJSONObject(\"Media\"))\n\t}\n\n\tprivate suspend fun saveRate(json: JSONObject, mangaId: Long) {\n\t\tval scoreFormat = ScoreFormat.of(storage[KEY_SCORE_FORMAT])\n\t\tval entity = ScrobblingEntity(\n\t\t\tscrobbler = ScrobblerService.ANILIST.id,\n\t\t\tid = json.getInt(\"id\"),\n\t\t\tmangaId = mangaId,\n\t\t\ttargetId = json.getLong(\"mediaId\"),\n\t\t\tstatus = json.getString(\"status\"),\n\t\t\tchapter = json.getInt(\"progress\"),\n\t\t\tcomment = json.getString(\"notes\"),\n\t\t\trating = scoreFormat.normalize(json.getDouble(\"score\").toFloat()),\n\t\t)\n\t\tdb.getScrobblingDao().upsert(entity)\n\t}\n\n\tprivate fun ScrobblerManga(json: JSONObject, sourceTitle: String): ScrobblerManga {\n\t\tval title = json.getJSONObject(\"title\")\n\t\treturn ScrobblerManga(\n\t\t\tid = json.getLong(\"id\"),\n\t\t\tname = title.getString(\"userPreferred\"),\n\t\t\taltName = title.getStringOrNull(\"native\"),\n\t\t\tcover = json.getJSONObject(\"coverImage\").getString(\"medium\"),\n\t\t\turl = json.getString(\"siteUrl\"),\n\t\t\tisBestMatch = sourceTitle.let {\n\t\t\t\ttitle.keys().forEach { key ->\n\t\t\t\t\tif (title.getStringOrNull(key)?.equals(it, ignoreCase = true) == true) {\n\t\t\t\t\t\treturn@let true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfalse\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate fun ScrobblerMangaInfo(json: JSONObject) = ScrobblerMangaInfo(\n\t\tid = json.getLong(\"id\"),\n\t\tname = json.getJSONObject(\"title\").getString(\"userPreferred\"),\n\t\tcover = json.getJSONObject(\"coverImage\").getString(\"large\"),\n\t\turl = json.getString(\"siteUrl\"),\n\t\tdescriptionHtml = json.getString(\"description\"),\n\t)\n\n\t@Suppress(\"FunctionName\")\n\tprivate fun AniListUser(json: JSONObject) = ScrobblerUser(\n\t\tid = json.getLong(\"id\"),\n\t\tnickname = json.getString(\"name\"),\n\t\tavatar = json.getJSONObject(\"avatar\").getStringOrNull(\"medium\"),\n\t\tservice = ScrobblerService.ANILIST,\n\t)\n\n\tprivate suspend fun doRequest(type: String, payload: String): JSONObject {\n\t\tval body = JSONObject()\n\t\tbody.put(\"query\", \"$type { ${payload.shrink()} }\")\n\t\tval mediaType = \"application/json; charset=utf-8\".toMediaType()\n\t\tval requestBody = body.toString().toRequestBody(mediaType)\n\t\tval request = Request.Builder()\n\t\t\t.post(requestBody)\n\t\t\t.url(ENDPOINT)\n\t\tval json = okHttp.newCall(request.build()).await().parseJson()\n\t\tjson.optJSONArray(\"errors\")?.let {\n\t\t\tif (it.length() != 0) {\n\t\t\t\tthrow GraphQLException(it)\n\t\t\t}\n\t\t}\n\t\treturn json\n\t}\n\n\tprivate fun String.shrink() = replace(shrinkRegex, \" \")\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/anilist/data/ScoreFormat.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.anilist.data\n\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\n\nenum class ScoreFormat {\n\n\tPOINT_100, POINT_10_DECIMAL, POINT_10, POINT_5, POINT_3;\n\n\tfun normalize(score: Float): Float = when (this) {\n\t\tPOINT_100 -> score / 100f\n\t\tPOINT_10_DECIMAL,\n\t\tPOINT_10 -> score / 10f\n\n\t\tPOINT_5 -> score / 5f\n\t\tPOINT_3 -> score / 3f\n\t}.coerceIn(0f, 1f)\n\n\tcompanion object {\n\n\t\tfun of(rawValue: String?): ScoreFormat {\n\t\t\trawValue ?: return POINT_10_DECIMAL\n\t\t\treturn runCatching { valueOf(rawValue) }\n\t\t\t\t.onFailure { it.printStackTraceDebug() }\n\t\t\t\t.getOrDefault(POINT_10_DECIMAL)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/anilist/domain/AniListScrobbler.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.anilist.domain\n\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass AniListScrobbler @Inject constructor(\n\tprivate val repository: AniListRepository,\n\tdb: MangaDatabase,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n) : Scrobbler(db, ScrobblerService.ANILIST, repository, mangaRepositoryFactory) {\n\n\tinit {\n\t\tstatuses[ScrobblingStatus.PLANNED] = \"PLANNING\"\n\t\tstatuses[ScrobblingStatus.READING] = \"CURRENT\"\n\t\tstatuses[ScrobblingStatus.RE_READING] = \"REPEATING\"\n\t\tstatuses[ScrobblingStatus.COMPLETED] = \"COMPLETED\"\n\t\tstatuses[ScrobblingStatus.ON_HOLD] = \"PAUSED\"\n\t\tstatuses[ScrobblingStatus.DROPPED] = \"DROPPED\"\n\t}\n\n\toverride suspend fun updateScrobblingInfo(\n\t\tmangaId: Long,\n\t\trating: Float,\n\t\tstatus: ScrobblingStatus?,\n\t\tcomment: String?,\n\t) {\n\t\tval entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)\n\t\trequireNotNull(entity) { \"Scrobbling info for manga $mangaId not found\" }\n\t\trepository.updateRate(\n\t\t\trateId = entity.id,\n\t\t\tmangaId = entity.mangaId,\n\t\t\trating = rating,\n\t\t\tstatus = statuses[status],\n\t\t\tcomment = comment,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerRepository.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.data\n\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\n\ninterface ScrobblerRepository {\n\n\tval oauthUrl: String\n\n\tval isAuthorized: Boolean\n\n\tval cachedUser: ScrobblerUser?\n\n\tsuspend fun authorize(code: String?)\n\n\tsuspend fun loadUser(): ScrobblerUser\n\n\tfun logout()\n\n\tsuspend fun unregister(mangaId: Long)\n\n\tsuspend fun findManga(query: String, offset: Int): List<ScrobblerManga>\n\n\tsuspend fun getMangaInfo(id: Long): ScrobblerMangaInfo\n\n\tsuspend fun createRate(mangaId: Long, scrobblerMangaId: Long)\n\n\tsuspend fun updateRate(rateId: Int, mangaId: Long, chapter: Int)\n\n\tsuspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.data\n\nimport android.content.Context\nimport androidx.core.content.edit\nimport org.jsoup.internal.StringUtil.StringJoiner\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\n\nprivate const val KEY_ACCESS_TOKEN = \"access_token\"\nprivate const val KEY_REFRESH_TOKEN = \"refresh_token\"\nprivate const val KEY_USER = \"user\"\n\nclass ScrobblerStorage(context: Context, service: ScrobblerService) {\n\n\tprivate val prefs = context.getSharedPreferences(service.name, Context.MODE_PRIVATE)\n\n\tvar accessToken: String?\n\t\tget() = prefs.getString(KEY_ACCESS_TOKEN, null)\n\t\tset(value) = prefs.edit { putString(KEY_ACCESS_TOKEN, value) }\n\n\tvar refreshToken: String?\n\t\tget() = prefs.getString(KEY_REFRESH_TOKEN, null)\n\t\tset(value) = prefs.edit { putString(KEY_REFRESH_TOKEN, value) }\n\n\tvar user: ScrobblerUser?\n\t\tget() = prefs.getString(KEY_USER, null)?.let {\n\t\t\tval lines = it.lines()\n\t\t\tif (lines.size != 4) {\n\t\t\t\treturn@let null\n\t\t\t}\n\t\t\tScrobblerUser(\n\t\t\t\tid = lines[0].toLong(),\n\t\t\t\tnickname = lines[1],\n\t\t\t\tavatar = lines[2].nullIfEmpty(),\n\t\t\t\tservice = ScrobblerService.valueOf(lines[3]),\n\t\t\t)\n\t\t}\n\t\tset(value) = prefs.edit {\n\t\t\tif (value == null) {\n\t\t\t\tremove(KEY_USER)\n\t\t\t\treturn@edit\n\t\t\t}\n\t\t\tval str = StringJoiner(\"\\n\")\n\t\t\t\t.add(value.id)\n\t\t\t\t.add(value.nickname)\n\t\t\t\t.add(value.avatar.orEmpty())\n\t\t\t\t.add(value.service.name)\n\t\t\t\t.complete()\n\t\t\tputString(KEY_USER, str)\n\t\t}\n\n\toperator fun get(key: String): String? = prefs.getString(key, null)\n\n\toperator fun set(key: String, value: String?) = prefs.edit { putString(key, value) }\n\n\tfun clear() = prefs.edit {\n\t\tclear()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblingDao.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.data\n\nimport androidx.room.*\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\n\n@Dao\nabstract class ScrobblingDao {\n\n\t@Query(\"SELECT * FROM scrobblings WHERE scrobbler = :scrobbler AND manga_id = :mangaId\")\n\tabstract suspend fun find(scrobbler: Int, mangaId: Long): ScrobblingEntity?\n\n\t@Query(\"SELECT * FROM scrobblings WHERE scrobbler = :scrobbler AND manga_id = :mangaId\")\n\tabstract fun observe(scrobbler: Int, mangaId: Long): Flow<ScrobblingEntity?>\n\n\t@Query(\"SELECT * FROM scrobblings WHERE scrobbler = :scrobbler\")\n\tabstract fun observe(scrobbler: Int): Flow<List<ScrobblingEntity>>\n\n\t@Upsert\n\tabstract suspend fun upsert(entity: ScrobblingEntity)\n\n\t@Query(\"DELETE FROM scrobblings WHERE scrobbler = :scrobbler AND manga_id = :mangaId\")\n\tabstract suspend fun delete(scrobbler: Int, mangaId: Long)\n\n\t@Query(\"SELECT * FROM scrobblings ORDER BY scrobbler LIMIT :limit OFFSET :offset\")\n\tprotected abstract suspend fun findAll(offset: Int, limit: Int): List<ScrobblingEntity>\n\n\tfun dumpEnabled(): Flow<ScrobblingEntity> = flow {\n\t\tval window = 10\n\t\tvar offset = 0\n\t\twhile (currentCoroutineContext().isActive) {\n\t\t\tval list = findAll(offset, window)\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset += window\n\t\t\tlist.forEach { emit(it) }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblingEntity.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.data\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\n\n@Entity(\n\ttableName = \"scrobblings\",\n\tprimaryKeys = [\"scrobbler\", \"id\", \"manga_id\"],\n)\nclass ScrobblingEntity(\n\t@ColumnInfo(name = \"scrobbler\") val scrobbler: Int,\n\t@ColumnInfo(name = \"id\") val id: Int,\n\t@ColumnInfo(name = \"manga_id\") val mangaId: Long,\n\t@ColumnInfo(name = \"target_id\") val targetId: Long,\n\t@ColumnInfo(name = \"status\") val status: String?,\n\t@ColumnInfo(name = \"chapter\") val chapter: Int,\n\t@ColumnInfo(name = \"comment\") val comment: String?,\n\t@ColumnInfo(name = \"rating\") val rating: Float,\n) {\n\n\tfun copy(\n\t\tstatus: String?,\n\t\tcomment: String?,\n\t\trating: Float,\n\t) = ScrobblingEntity(\n\t\tscrobbler = scrobbler,\n\t\tid = id,\n\t\tmangaId = mangaId,\n\t\ttargetId = targetId,\n\t\tstatus = status,\n\t\tchapter = chapter,\n\t\tcomment = comment,\n\t\trating = rating,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/Scrobbler.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain\n\nimport androidx.annotation.FloatRange\nimport androidx.collection.LongSparseArray\nimport androidx.collection.getOrElse\nimport androidx.core.text.parseAsHtml\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.ext.findKeyByValue\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.sanitize\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport java.util.EnumMap\n\nabstract class Scrobbler(\n\tprotected val db: MangaDatabase,\n\tval scrobblerService: ScrobblerService,\n\tprivate val repository: ScrobblerRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n) {\n\n\tprivate val infoCache = LongSparseArray<ScrobblerMangaInfo>()\n\tprotected val statuses = EnumMap<ScrobblingStatus, String>(ScrobblingStatus::class.java)\n\n\tval user: Flow<ScrobblerUser> = flow {\n\t\trepository.cachedUser?.let {\n\t\t\temit(it)\n\t\t}\n\t\trunCatchingCancellable {\n\t\t\trepository.loadUser()\n\t\t}.onSuccess {\n\t\t\temit(it)\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}\n\t}\n\n\tval isEnabled: Boolean\n\t\tget() = repository.isAuthorized\n\n\tsuspend fun authorize(authCode: String): ScrobblerUser {\n\t\trepository.authorize(authCode)\n\t\treturn repository.loadUser()\n\t}\n\n\tfun logout() {\n\t\trepository.logout()\n\t}\n\n\tsuspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {\n\t\treturn repository.findManga(query, offset)\n\t}\n\n\tsuspend fun linkManga(mangaId: Long, targetId: Long) {\n\t\trepository.createRate(mangaId, targetId)\n\t}\n\n\tsuspend fun scrobble(manga: Manga, chapterId: Long) {\n\t\tvar chapters = manga.chapters\n\t\tif (chapters.isNullOrEmpty()) {\n\t\t\tchapters = mangaRepositoryFactory.create(manga.source).getDetails(manga).chapters\n\t\t}\n\t\trequireNotNull(chapters)\n\t\tval chapter = checkNotNull(chapters.findById(chapterId)) {\n\t\t\t\"Chapter $chapterId not found in this manga\"\n\t\t}\n\t\tval number = if (chapter.number > 0f) {\n\t\t\tchapter.number.toInt()\n\t\t} else {\n\t\t\tchapters = chapters.filter { x -> x.branch == chapter.branch }\n\t\t\tchapters.indexOf(chapter) + 1\n\t\t}\n\t\tval entity = db.getScrobblingDao().find(scrobblerService.id, manga.id) ?: return\n\t\trepository.updateRate(entity.id, entity.mangaId, number)\n\t}\n\n\tsuspend fun getScrobblingInfoOrNull(mangaId: Long): ScrobblingInfo? {\n\t\tval entity = db.getScrobblingDao().find(scrobblerService.id, mangaId) ?: return null\n\t\treturn entity.toScrobblingInfo()\n\t}\n\n\tabstract suspend fun updateScrobblingInfo(\n\t\tmangaId: Long,\n\t\t@FloatRange(from = 0.0, to = 1.0) rating: Float,\n\t\tstatus: ScrobblingStatus?,\n\t\tcomment: String?,\n\t)\n\n\tfun observeScrobblingInfo(mangaId: Long): Flow<ScrobblingInfo?> {\n\t\treturn db.getScrobblingDao().observe(scrobblerService.id, mangaId)\n\t\t\t.map { it?.toScrobblingInfo() }\n\t}\n\n\tfun observeAllScrobblingInfo(): Flow<List<ScrobblingInfo>> {\n\t\treturn db.getScrobblingDao().observe(scrobblerService.id)\n\t\t\t.map { entities ->\n\t\t\t\tcoroutineScope {\n\t\t\t\t\tentities.map {\n\t\t\t\t\t\tasync {\n\t\t\t\t\t\t\tit.toScrobblingInfo()\n\t\t\t\t\t\t}\n\t\t\t\t\t}.awaitAll()\n\t\t\t\t}.filterNotNull()\n\t\t\t}\n\t}\n\n\tsuspend fun unregisterScrobbling(mangaId: Long) {\n\t\trepository.unregister(mangaId)\n\t}\n\n\tprotected suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {\n\t\treturn repository.getMangaInfo(id)\n\t}\n\n\tprivate suspend fun ScrobblingEntity.toScrobblingInfo(): ScrobblingInfo? {\n\t\tval mangaInfo = infoCache.getOrElse(targetId) {\n\t\t\trunCatchingCancellable {\n\t\t\t\tgetMangaInfo(targetId)\n\t\t\t}.onFailure {\n\t\t\t\tit.printStackTraceDebug()\n\t\t\t}.onSuccess {\n\t\t\t\tinfoCache.put(targetId, it)\n\t\t\t}.getOrNull() ?: return null\n\t\t}\n\t\treturn ScrobblingInfo(\n\t\t\tscrobbler = scrobblerService,\n\t\t\tmangaId = mangaId,\n\t\t\ttargetId = targetId,\n\t\t\tstatus = statuses.findKeyByValue(status),\n\t\t\tchapter = chapter,\n\t\t\tcomment = comment,\n\t\t\trating = rating,\n\t\t\ttitle = mangaInfo.name,\n\t\t\tcoverUrl = mangaInfo.cover,\n\t\t\tdescription = mangaInfo.descriptionHtml.parseAsHtml().sanitize(),\n\t\t\texternalUrl = mangaInfo.url,\n\t\t)\n\t}\n}\n\nsuspend fun Scrobbler.tryScrobble(manga: Manga, chapterId: Long): Boolean {\n\treturn runCatchingCancellable {\n\t\tscrobble(manga, chapterId)\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.isSuccess\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/ScrobblerAuthRequiredException.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain\n\nimport okio.IOException\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\n\nclass ScrobblerAuthRequiredException(\n\tval scrobbler: ScrobblerService,\n) : IOException()\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/ScrobblerRepositoryMap.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain\n\nimport org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuRepository\nimport org.koitharu.kotatsu.scrobbling.mal.data.MALRepository\nimport org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository\nimport javax.inject.Inject\nimport javax.inject.Provider\n\nclass ScrobblerRepositoryMap @Inject constructor(\n\tprivate val shikimoriRepository: Provider<ShikimoriRepository>,\n\tprivate val aniListRepository: Provider<AniListRepository>,\n\tprivate val malRepository: Provider<MALRepository>,\n\tprivate val kitsuRepository: Provider<KitsuRepository>,\n) {\n\n\toperator fun get(scrobblerService: ScrobblerService): ScrobblerRepository = when (scrobblerService) {\n\t\tScrobblerService.SHIKIMORI -> shikimoriRepository\n\t\tScrobblerService.ANILIST -> aniListRepository\n\t\tScrobblerService.MAL -> malRepository\n\t\tScrobblerService.KITSU -> kitsuRepository\n\t}.get()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerManga.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain.model\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class ScrobblerManga(\n\tval id: Long,\n\tval name: String,\n\tval altName: String?,\n\tval cover: String?,\n\tval url: String,\n\tval isBestMatch: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ScrobblerManga && other.id == id\n\t}\n\n\toverride fun toString(): String {\n\t\treturn \"ScrobblerManga #$id \\\"$name\\\" $url\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerMangaInfo.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain.model\n\nclass ScrobblerMangaInfo(\n\tval id: Long,\n\tval name: String,\n\tval cover: String,\n\tval url: String,\n\tval descriptionHtml: String,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerService.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\n\nenum class ScrobblerService(\n\tval id: Int,\n\t@StringRes val titleResId: Int,\n\t@DrawableRes val iconResId: Int,\n) {\n\n\tSHIKIMORI(1, R.string.shikimori, R.drawable.ic_shikimori),\n\tANILIST(2, R.string.anilist, R.drawable.ic_anilist),\n\tMAL(3, R.string.mal, R.drawable.ic_mal),\n\tKITSU(4, R.string.kitsu, R.drawable.ic_kitsu)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerType.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain.model\n\nimport javax.inject.Qualifier\n\n@Qualifier\nannotation class ScrobblerType(\n\tval service: ScrobblerService\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblerUser.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain.model\n\ndata class ScrobblerUser(\n\tval id: Long,\n\tval nickname: String,\n\tval avatar: String?,\n\tval service: ScrobblerService,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingInfo.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain.model\n\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class ScrobblingInfo(\n\tval scrobbler: ScrobblerService,\n\tval mangaId: Long,\n\tval targetId: Long,\n\tval status: ScrobblingStatus?,\n\tval chapter: Int,\n\tval comment: String?,\n\tval rating: Float,\n\tval title: String,\n\tval coverUrl: String,\n\tval description: CharSequence?,\n\tval externalUrl: String,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ScrobblingInfo && other.scrobbler == scrobbler\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is ScrobblingInfo -> null\n\t\tpreviousState.status != status || previousState.rating != rating -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED\n\t\telse -> super.getChangePayload(previousState)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/model/ScrobblingStatus.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.domain.model\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nenum class ScrobblingStatus : ListModel {\n\n\tPLANNED, READING, RE_READING, COMPLETED, ON_HOLD, DROPPED;\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ScrobblingStatus && other.ordinal == ordinal\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/ScrobblerAuthHelper.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerRepositoryMap\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport org.koitharu.kotatsu.scrobbling.kitsu.ui.KitsuAuthActivity\nimport javax.inject.Inject\n\nclass ScrobblerAuthHelper @Inject constructor(\n\tprivate val repositoriesMap: ScrobblerRepositoryMap,\n) {\n\n\tfun isAuthorized(scrobbler: ScrobblerService) = repositoriesMap[scrobbler].isAuthorized\n\n\tfun getCachedUser(scrobbler: ScrobblerService): ScrobblerUser? {\n\t\treturn repositoriesMap[scrobbler].cachedUser\n\t}\n\n\tsuspend fun getUser(scrobbler: ScrobblerService): ScrobblerUser {\n\t\treturn repositoriesMap[scrobbler].loadUser()\n\t}\n\n\t@SuppressLint(\"UnsafeImplicitIntentLaunch\")\n\tfun startAuth(context: Context, scrobbler: ScrobblerService) = runCatching {\n\t\tif (scrobbler == ScrobblerService.KITSU) {\n\t\t\tlaunchKitsuAuth(context)\n\t\t} else {\n\t\t\tval repository = repositoriesMap[scrobbler]\n\t\t\tval intent = Intent(Intent.ACTION_VIEW)\n\t\t\tintent.data = repository.oauthUrl.toUri()\n\t\t\tcontext.startActivity(intent)\n\t\t}\n\t}\n\n\tprivate fun launchKitsuAuth(context: Context) {\n\t\tcontext.startActivity(Intent(context, KitsuAuthActivity::class.java))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.config\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.showOrHide\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityScrobblerConfigBinding\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\nimport org.koitharu.kotatsu.scrobbling.common.ui.config.adapter.ScrobblingMangaAdapter\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\nclass ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),\n\tOnListItemClickListener<ScrobblingInfo>, View.OnClickListener {\n\n\tprivate val viewModel: ScrobblerConfigViewModel by viewModels()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityScrobblerConfigBinding.inflate(layoutInflater))\n\t\tsetTitle(viewModel.titleResId)\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\n\t\tval listAdapter = ScrobblingMangaAdapter(this)\n\t\twith(viewBinding.recyclerView) {\n\t\t\tadapter = listAdapter\n\t\t\tsetHasFixedSize(true)\n\t\t\tval decoration = TypedListSpacingDecoration(context, false)\n\t\t\taddItemDecoration(decoration)\n\t\t}\n\t\tviewBinding.imageViewAvatar.setOnClickListener(this)\n\n\t\tviewModel.content.observe(this, listAdapter)\n\t\tviewModel.user.observe(this, this::onUserChanged)\n\t\tviewModel.isLoading.observe(this, this::onLoadingStateChanged)\n\t\tviewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))\n\t\tviewModel.onLoggedOut.observeEvent(this) {\n\t\t\tfinishAfterTransition()\n\t\t}\n\n\t\tprocessIntent(intent)\n\t}\n\n\toverride fun onNewIntent(intent: Intent) {\n\t\tsuper.onNewIntent(intent)\n\t\tsetIntent(intent)\n\t\tprocessIntent(intent)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval basePadding = v.resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\ttop = barsInsets.top,\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t)\n\t\tviewBinding.recyclerView.setPadding(\n\t\t\tbarsInsets.left + basePadding,\n\t\t\tbarsInsets.top + basePadding,\n\t\t\tbarsInsets.right + basePadding,\n\t\t\tbarsInsets.bottom + basePadding,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onItemClick(item: ScrobblingInfo, view: View) {\n\t\trouter.openDetails(item.mangaId)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.imageView_avatar -> showUserDialog()\n\t\t}\n\t}\n\n\tprivate fun processIntent(intent: Intent) {\n\t\tif (intent.action == Intent.ACTION_VIEW) {\n\t\t\tval uri = intent.data ?: return\n\t\t\tval code = uri.getQueryParameter(\"code\")\n\t\t\tif (!code.isNullOrEmpty()) {\n\t\t\t\tviewModel.onAuthCodeReceived(code)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onUserChanged(user: ScrobblerUser?) {\n\t\tif (user == null) {\n\t\t\tviewBinding.imageViewAvatar.disposeImage()\n\t\t\tviewBinding.imageViewAvatar.setImageResource(appcompatR.drawable.abc_ic_menu_overflow_material)\n\t\t\treturn\n\t\t}\n\t\tviewBinding.imageViewAvatar.setImageAsync(user.avatar)\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tviewBinding.progressBar.showOrHide(isLoading)\n\t}\n\n\tprivate fun showUserDialog() {\n\t\tMaterialAlertDialogBuilder(this)\n\t\t\t.setTitle(title)\n\t\t\t.setMessage(getString(R.string.logged_in_as, viewModel.user.value?.nickname))\n\t\t\t.setNegativeButton(R.string.close, null)\n\t\t\t.setPositiveButton(R.string.logout) { _, _ ->\n\t\t\t\tviewModel.logout()\n\t\t\t}.show()\n\t}\n\n\tcompanion object {\n\t\tconst val HOST_SHIKIMORI_AUTH = \"shikimori-auth\"\n\t\tconst val HOST_ANILIST_AUTH = \"anilist-auth\"\n\t\tconst val HOST_MAL_AUTH = \"mal-auth\"\n\t\tconst val HOST_KITSU_AUTH = \"kitsu-auth\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.config\n\nimport android.net.Uri\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.onFirst\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ScrobblerConfigViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tscrobblers: Set<@JvmSuppressWildcards Scrobbler>,\n) : BaseViewModel() {\n\n\tprivate val scrobblerService = getScrobblerService(savedStateHandle)\n\tprivate val scrobbler = scrobblers.first { it.scrobblerService == scrobblerService }\n\n\tval titleResId = scrobbler.scrobblerService.titleResId\n\n\tval user = MutableStateFlow<ScrobblerUser?>(null)\n\tval onLoggedOut = MutableEventFlow<Unit>()\n\n\tval content = scrobbler.observeAllScrobblingInfo()\n\t\t.onStart { loadingCounter.increment() }\n\t\t.onFirst { loadingCounter.decrement() }\n\t\t.withErrorHandling()\n\t\t.map { buildContentList(it) }\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tinit {\n\t\tscrobbler.user\n\t\t\t.onEach { user.value = it }\n\t\t\t.launchIn(viewModelScope + Dispatchers.Default)\n\t}\n\n\tfun onAuthCodeReceived(authCode: String) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval newUser = scrobbler.authorize(authCode)\n\t\t\tuser.value = newUser\n\t\t}\n\t}\n\n\tfun logout() {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tscrobbler.logout()\n\t\t\tuser.value = null\n\t\t\tonLoggedOut.call(Unit)\n\t\t}\n\t}\n\n\tprivate fun buildContentList(list: List<ScrobblingInfo>): List<ListModel> {\n\t\tif (list.isEmpty()) {\n\t\t\treturn listOf(\n\t\t\t\tEmptyState(\n\t\t\t\t\ticon = R.drawable.ic_empty_history,\n\t\t\t\t\ttextPrimary = R.string.nothing_here,\n\t\t\t\t\ttextSecondary = R.string.scrobbling_empty_hint,\n\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tval grouped = list.groupBy { it.status }\n\t\tval statuses = ScrobblingStatus.entries\n\t\tval result = ArrayList<ListModel>(list.size + statuses.size)\n\t\tfor (st in statuses) {\n\t\t\tval subList = grouped[st]\n\t\t\tif (subList.isNullOrEmpty()) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult.add(st)\n\t\t\tresult.addAll(subList)\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate fun getScrobblerService(\n\t\tsavedStateHandle: SavedStateHandle,\n\t): ScrobblerService {\n\t\tval serviceId = savedStateHandle.get<Int>(AppRouter.KEY_ID) ?: 0\n\t\tif (serviceId != 0) {\n\t\t\treturn ScrobblerService.entries.first { it.id == serviceId }\n\t\t}\n\t\tval uri = savedStateHandle.require<Uri>(AppRouter.KEY_DATA)\n\t\treturn when (uri.host) {\n\t\t\tScrobblerConfigActivity.HOST_SHIKIMORI_AUTH -> ScrobblerService.SHIKIMORI\n\t\t\tScrobblerConfigActivity.HOST_ANILIST_AUTH -> ScrobblerService.ANILIST\n\t\t\tScrobblerConfigActivity.HOST_MAL_AUTH -> ScrobblerService.MAL\n\t\t\tScrobblerConfigActivity.HOST_KITSU_AUTH -> ScrobblerService.KITSU\n\t\t\telse -> error(\"Wrong scrobbler uri: $uri\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingHeaderAD.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.config.adapter\n\nimport androidx.core.view.isInvisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.databinding.ItemHeaderBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\n\nfun scrobblingHeaderAD() = adapterDelegateViewBinding<ScrobblingStatus, ListModel, ItemHeaderBinding>(\n\t{ inflater, parent -> ItemHeaderBinding.inflate(inflater, parent, false) },\n) {\n\n\tbinding.buttonMore.isInvisible = true\n\tval strings = context.resources.getStringArray(R.array.scrobbling_statuses)\n\n\tbind {\n\t\tbinding.textViewTitle.text = strings.getOrNull(item.ordinal)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAD.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.config.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.databinding.ItemScrobblingMangaBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\n\nfun scrobblingMangaAD(\n\tclickListener: OnListItemClickListener<ScrobblingInfo>,\n) = adapterDelegateViewBinding<ScrobblingInfo, ListModel, ItemScrobblingMangaBinding>(\n\t{ layoutInflater, parent -> ItemScrobblingMangaBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView)\n\n\tbind {\n\t\tbinding.imageViewCover.setImageAsync(item.coverUrl, null)\n\t\tbinding.textViewTitle.text = item.title\n\t\tbinding.ratingBar.rating = item.rating * binding.ratingBar.numStars\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAdapter.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.config.adapter\n\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo\n\nclass ScrobblingMangaAdapter(\n\tclickListener: OnListItemClickListener<ScrobblingInfo>,\n) : BaseListAdapter<ListModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.HEADER, scrobblingHeaderAD())\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null))\n\t\taddDelegate(ListItemType.MANGA_SCROBBLING, scrobblingMangaAD(clickListener))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.selector\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.RecyclerView.NO_ID\nimport com.google.android.material.tabs.TabLayout\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.PaginationScrollListener\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.ui.util.CollapseActionViewCallback\nimport org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.setProgressIcon\nimport org.koitharu.kotatsu.core.util.ext.setTabsEnabled\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\nimport org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingFooter\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerMangaSelectionDecoration\nimport org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerSelectorAdapter\n\n@AndroidEntryPoint\nclass ScrobblingSelectorSheet :\n\tBaseAdaptiveSheet<SheetScrobblingSelectorBinding>(),\n\tOnListItemClickListener<ScrobblerManga>,\n\tPaginationScrollListener.Callback,\n\tView.OnClickListener,\n\tMenuItem.OnActionExpandListener,\n\tSearchView.OnQueryTextListener,\n\tTabLayout.OnTabSelectedListener,\n\tListStateHolderListener,\n\tAsyncListDiffer.ListListener<ListModel> {\n\n\tprivate var collapsibleActionViewCallback: CollapseActionViewCallback? = null\n\tprivate var paginationScrollListener: PaginationScrollListener? = null\n\tprivate val viewModel by viewModels<ScrobblingSelectorViewModel>()\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetScrobblingSelectorBinding {\n\t\treturn SheetScrobblingSelectorBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: SheetScrobblingSelectorBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tdisableFitToContents()\n\t\tval listAdapter = ScrobblerSelectorAdapter(this, this)\n\t\tlistAdapter.addListListener(this)\n\t\tval decoration = ScrobblerMangaSelectionDecoration(binding.root.context)\n\t\twith(binding.recyclerView) {\n\t\t\tadapter = listAdapter\n\t\t\taddItemDecoration(decoration)\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\taddOnScrollListener(\n\t\t\t\tPaginationScrollListener(4, this@ScrobblingSelectorSheet).also {\n\t\t\t\t\tpaginationScrollListener = it\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t\tbinding.buttonDone.setOnClickListener(this)\n\t\tinitOptionsMenu()\n\t\tinitTabs()\n\n\t\tviewModel.content.observe(viewLifecycleOwner, listAdapter)\n\t\tviewModel.selectedItemId.observe(viewLifecycleOwner) {\n\t\t\tdecoration.checkedItemId = it\n\t\t\tbinding.recyclerView.invalidateItemDecorations()\n\t\t}\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, ::onError)\n\t\tviewModel.onClose.observeEvent(viewLifecycleOwner) {\n\t\t\tdismiss()\n\t\t}\n\t\tviewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->\n\t\t\tbinding.buttonDone.isEnabled = !isLoading\n\t\t\tif (isLoading) {\n\t\t\t\tbinding.buttonDone.setProgressIcon()\n\t\t\t} else {\n\t\t\t\tbinding.buttonDone.setIconResource(R.drawable.ic_check)\n\t\t\t}\n\t\t\tbinding.tabs.setTabsEnabled(!isLoading)\n\t\t}\n\t\tviewModel.selectedScrobblerIndex.observe(viewLifecycleOwner) { index ->\n\t\t\tval tab = binding.tabs.getTabAt(index)\n\t\t\tif (tab != null && !tab.isSelected) {\n\t\t\t\ttab.select()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\tcollapsibleActionViewCallback = null\n\t\tpaginationScrollListener = null\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval basePadding = v.resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)\n\t\tviewBinding?.recyclerView?.updatePadding(\n\t\t\tbottom = basePadding + insets.getInsets(typeMask).bottom,\n\t\t)\n\t\treturn insets.consume(v, typeMask, bottom = true)\n\t}\n\n\toverride fun onCurrentListChanged(previousList: MutableList<ListModel>, currentList: MutableList<ListModel>) {\n\t\tif (previousList.singleOrNull() is LoadingFooter) {\n\t\t\tval rv = viewBinding?.recyclerView ?: return\n\t\t\tval selectedId = viewModel.selectedItemId.value\n\t\t\tval target = if (selectedId == NO_ID) {\n\t\t\t\t0\n\t\t\t} else {\n\t\t\t\tcurrentList.indexOfFirst { it is ScrobblerManga && it.id == selectedId }.coerceAtLeast(0)\n\t\t\t}\n\t\t\trv.post(RecyclerViewScrollCallback(rv, target, if (target == 0) 0 else rv.height / 3))\n\t\t\tpaginationScrollListener?.postInvalidate(rv)\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_done -> viewModel.onDoneClick()\n\t\t}\n\t}\n\n\toverride fun onItemClick(item: ScrobblerManga, view: View) {\n\t\tviewModel.selectItem(item.id)\n\t}\n\n\toverride fun onRetryClick(error: Throwable) {\n\t\tif (ExceptionResolver.canResolve(error)) {\n\t\t\tviewLifecycleScope.launch {\n\t\t\t\tif (exceptionResolver.resolve(error)) {\n\t\t\t\t\tviewModel.retry()\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tviewModel.retry()\n\t\t}\n\t}\n\n\toverride fun onEmptyActionClick() {\n\t\topenSearch()\n\t}\n\n\toverride fun onScrolledToEnd() {\n\t\tviewModel.loadNextPage()\n\t}\n\n\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\tsetExpanded(isExpanded = true, isLocked = true)\n\t\tcollapsibleActionViewCallback?.onMenuItemActionExpand(item)\n\t\treturn true\n\t}\n\n\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean {\n\t\tval searchView = (item.actionView as? SearchView) ?: return false\n\t\tsearchView.setQuery(\"\", false)\n\t\tsearchView.post { setExpanded(isExpanded = false, isLocked = false) }\n\t\tcollapsibleActionViewCallback?.onMenuItemActionCollapse(item)\n\t\treturn true\n\t}\n\n\toverride fun onQueryTextSubmit(query: String?): Boolean {\n\t\tif (query == null || query.length < 3) {\n\t\t\treturn false\n\t\t}\n\t\tviewModel.search(query)\n\t\trequireViewBinding().toolbar.menu.findItem(R.id.action_search)?.collapseActionView()\n\t\treturn true\n\t}\n\n\toverride fun onQueryTextChange(newText: String?): Boolean = false\n\n\toverride fun onTabSelected(tab: TabLayout.Tab) {\n\t\tviewModel.setScrobblerIndex(tab.position)\n\t}\n\n\toverride fun onTabUnselected(tab: TabLayout.Tab?) = Unit\n\n\toverride fun onTabReselected(tab: TabLayout.Tab?) {\n\t\tif (!isExpanded) {\n\t\t\tsetExpanded(isExpanded = true, isLocked = behavior?.isDraggable == false)\n\t\t}\n\t\trequireViewBinding().recyclerView.firstVisibleItemPosition = 0\n\t}\n\n\tprivate fun openSearch() {\n\t\tval menuItem = requireViewBinding().toolbar.menu.findItem(R.id.action_search) ?: return\n\t\tmenuItem.expandActionView()\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tToast.makeText(requireContext(), e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()\n\t\tif (viewModel.isEmpty) {\n\t\t\tdismissAllowingStateLoss()\n\t\t}\n\t}\n\n\tprivate fun initOptionsMenu() {\n\t\trequireViewBinding().toolbar.inflateMenu(R.menu.opt_shiki_selector)\n\t\tval searchMenuItem = requireViewBinding().toolbar.menu.findItem(R.id.action_search)\n\t\tsearchMenuItem.setOnActionExpandListener(this)\n\t\tval searchView = searchMenuItem.actionView as SearchView\n\t\tsearchView.setOnQueryTextListener(this)\n\t\tsearchView.setIconifiedByDefault(false)\n\t\tsearchView.queryHint = searchMenuItem.title\n\t\tcollapsibleActionViewCallback = CollapseActionViewCallback(searchMenuItem).also {\n\t\t\tonBackPressedDispatcher.addCallback(it)\n\t\t}\n\t}\n\n\tprivate fun initTabs() {\n\t\tval entries = viewModel.availableScrobblers\n\t\tval tabs = requireViewBinding().tabs\n\t\tval selectedId = arguments?.getInt(AppRouter.KEY_ID, -1) ?: -1\n\t\ttabs.removeAllTabs()\n\t\ttabs.clearOnTabSelectedListeners()\n\t\ttabs.addOnTabSelectedListener(this)\n\t\tfor (entry in entries) {\n\t\t\tval tab = tabs.newTab()\n\t\t\ttab.tag = entry.scrobblerService\n\t\t\ttab.setIcon(entry.scrobblerService.iconResId)\n\t\t\ttab.setText(entry.scrobblerService.titleResId)\n\t\t\ttabs.addTab(tab)\n\t\t\tif (entry.scrobblerService.id == selectedId) {\n\t\t\t\ttab.select()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.selector\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport androidx.recyclerview.widget.RecyclerView.NO_ID\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.core.util.ext.requireValue\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.domain.ReadingProgress\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingFooter\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.parsers.util.ifZero\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ScrobblingSelectorViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tscrobblers: Set<@JvmSuppressWildcards Scrobbler>,\n\tprivate val historyRepository: HistoryRepository,\n) : BaseViewModel() {\n\n\tval manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga\n\n\tval availableScrobblers = scrobblers.filter { it.isEnabled }\n\n\tval selectedScrobblerIndex = MutableStateFlow(0)\n\n\tprivate val scrobblerMangaList = MutableStateFlow<List<ScrobblerManga>>(emptyList())\n\tprivate val hasNextPage = MutableStateFlow(true)\n\tprivate val listError = MutableStateFlow<Throwable?>(null)\n\tprivate var loadingJob: Job? = null\n\tprivate var doneJob: Job? = null\n\tprivate var initJob: Job? = null\n\n\tprivate val currentScrobbler: Scrobbler\n\t\tget() = availableScrobblers[selectedScrobblerIndex.requireValue()]\n\n\tval content: StateFlow<List<ListModel>> = combine(\n\t\tscrobblerMangaList,\n\t\tlistError,\n\t\thasNextPage,\n\t) { list, error, isHasNextPage ->\n\t\tif (list.isNotEmpty()) {\n\t\t\tif (isHasNextPage) {\n\t\t\t\tlist + LoadingFooter()\n\t\t\t} else {\n\t\t\t\tlist\n\t\t\t}\n\t\t} else {\n\t\t\tlistOf(\n\t\t\t\twhen {\n\t\t\t\t\terror != null -> errorHint(error)\n\t\t\t\t\tisHasNextPage -> LoadingFooter()\n\t\t\t\t\telse -> emptyResultsHint()\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tval selectedItemId = MutableStateFlow(NO_ID)\n\tval onClose = MutableEventFlow<Unit>()\n\tprivate val searchQuery = MutableStateFlow(manga.title)\n\n\tval isEmpty: Boolean\n\t\tget() = scrobblerMangaList.value.isEmpty()\n\n\tinit {\n\t\tinitialize()\n\t}\n\n\tfun search(query: String) {\n\t\tloadingJob?.cancel()\n\t\tsearchQuery.value = query\n\t\tloadList(append = false)\n\t}\n\n\tfun selectItem(id: Long) {\n\t\tif (doneJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tselectedItemId.value = id\n\t}\n\n\tfun loadNextPage() {\n\t\tif (scrobblerMangaList.value.isNotEmpty() && hasNextPage.value) {\n\t\t\tloadList(append = true)\n\t\t}\n\t}\n\n\tfun retry() {\n\t\tloadingJob?.cancel()\n\t\thasNextPage.value = true\n\t\tscrobblerMangaList.value = emptyList()\n\t\tloadList(append = false)\n\t}\n\n\tprivate fun loadList(append: Boolean) {\n\t\tif (loadingJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tloadingJob = launchJob(Dispatchers.Default) {\n\t\t\tlistError.value = null\n\t\t\tval offset = if (append) scrobblerMangaList.value.size else 0\n\t\t\trunCatchingCancellable {\n\t\t\t\tcurrentScrobbler.findManga(checkNotNull(searchQuery.value), offset)\n\t\t\t}.onSuccess { list ->\n\t\t\t\tval newList = (if (append) {\n\t\t\t\t\tscrobblerMangaList.value + list\n\t\t\t\t} else {\n\t\t\t\t\tlist\n\t\t\t\t}).distinctBy { x -> x.id }\n\t\t\t\tval changed = newList != scrobblerMangaList.value\n\t\t\t\tscrobblerMangaList.value = newList\n\t\t\t\thasNextPage.value = changed && newList.isNotEmpty()\n\t\t\t}.onFailure { error ->\n\t\t\t\terror.printStackTraceDebug()\n\t\t\t\thasNextPage.value = false\n\t\t\t\tlistError.value = error\n\t\t\t}\n\t\t}\n\t}\n\n\tfun onDoneClick() {\n\t\tif (doneJob?.isActive == true) {\n\t\t\treturn\n\t\t}\n\t\tval targetId = selectedItemId.value\n\t\tif (targetId == NO_ID) {\n\t\t\tonClose.call(Unit)\n\t\t}\n\t\tdoneJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tval prevInfo = currentScrobbler.getScrobblingInfoOrNull(manga.id)\n\t\t\tcurrentScrobbler.linkManga(manga.id, targetId)\n\t\t\tval history = historyRepository.getOne(manga)\n\t\t\tcurrentScrobbler.updateScrobblingInfo(\n\t\t\t\tmangaId = manga.id,\n\t\t\t\trating = prevInfo?.rating ?: 0f,\n\t\t\t\tstatus = prevInfo?.status ?: when {\n\t\t\t\t\thistory == null -> ScrobblingStatus.PLANNED\n\t\t\t\t\tReadingProgress.isCompleted(history.percent) -> ScrobblingStatus.COMPLETED\n\t\t\t\t\telse -> ScrobblingStatus.READING\n\t\t\t\t},\n\t\t\t\tcomment = prevInfo?.comment,\n\t\t\t)\n\t\t\tif (history != null) {\n\t\t\t\tcurrentScrobbler.scrobble(\n\t\t\t\t\tmanga = manga,\n\t\t\t\t\tchapterId = history.chapterId,\n\t\t\t\t)\n\t\t\t}\n\t\t\tonClose.call(Unit)\n\t\t}\n\t}\n\n\tfun setScrobblerIndex(index: Int) {\n\t\tif (index == selectedScrobblerIndex.value || index !in availableScrobblers.indices) return\n\t\tselectedScrobblerIndex.value = index\n\t\tinitialize()\n\t}\n\n\tprivate fun initialize() {\n\t\tinitJob?.cancel()\n\t\tloadingJob?.cancel()\n\t\thasNextPage.value = true\n\t\tscrobblerMangaList.value = emptyList()\n\t\tinitJob = launchJob(Dispatchers.Default) {\n\t\t\ttry {\n\t\t\t\tval info = currentScrobbler.getScrobblingInfoOrNull(manga.id)\n\t\t\t\tif (info != null) {\n\t\t\t\t\tselectedItemId.value = info.targetId\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tloadList(append = false)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun emptyResultsHint() = ScrobblerHint(\n\t\ticon = R.drawable.ic_empty_history,\n\t\ttextPrimary = R.string.nothing_found,\n\t\ttextSecondary = R.string.text_search_holder_secondary,\n\t\terror = null,\n\t\tactionStringRes = R.string.search,\n\t)\n\n\tprivate fun errorHint(e: Throwable): ScrobblerHint {\n\t\tval resolveAction = ExceptionResolver.getResolveStringId(e)\n\t\treturn ScrobblerHint(\n\t\t\ticon = R.drawable.ic_error_large,\n\t\t\ttextPrimary = R.string.error_occurred,\n\t\t\terror = e,\n\t\t\ttextSecondary = if (resolveAction == 0) 0 else R.string.try_again,\n\t\t\tactionStringRes = resolveAction.ifZero { R.string.try_again },\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerHintAD.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemEmptyHintBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint\n\nfun scrobblerHintAD(\n\tlistener: ListStateHolderListener,\n) = adapterDelegateViewBinding<ScrobblerHint, ListModel, ItemEmptyHintBinding>(\n\t{ inflater, parent -> ItemEmptyHintBinding.inflate(inflater, parent, false) },\n) {\n\n\tbinding.buttonRetry.setOnClickListener {\n\t\tval e = item.error\n\t\tif (e != null) {\n\t\t\tlistener.onRetryClick(e)\n\t\t} else {\n\t\t\tlistener.onEmptyActionClick()\n\t\t}\n\t}\n\n\tbind {\n\t\tbinding.icon.setImageResource(item.icon)\n\t\tbinding.textPrimary.setText(item.textPrimary)\n\t\tif (item.error != null) {\n\t\t\tbinding.textSecondary.textAndVisible = item.error?.getDisplayMessage(context.resources)\n\t\t} else {\n\t\t\tbinding.textSecondary.setTextAndVisible(item.textSecondary)\n\t\t}\n\t\tbinding.buttonRetry.setTextAndVisible(item.actionStringRes)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerMangaSelectionDecoration.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.NO_ID\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.list.ui.MangaSelectionDecoration\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\n\nclass ScrobblerMangaSelectionDecoration(context: Context) : MangaSelectionDecoration(context) {\n\n\tvar checkedItemId: Long\n\t\tget() = if (selection.size == 1) {\n\t\t\tselection.first()\n\t\t} else {\n\t\t\tNO_ID\n\t\t}\n\t\tset(value) {\n\t\t\tclearSelection()\n\t\t\tif (value != NO_ID) {\n\t\t\t\tselection.add(value)\n\t\t\t}\n\t\t}\n\n\toverride fun getItemId(parent: RecyclerView, child: View): Long {\n\t\tval holder = parent.getChildViewHolder(child) ?: return NO_ID\n\t\tval item = holder.getItem(ScrobblerManga::class.java) ?: return NO_ID\n\t\treturn item.id\n\t}\n\n\toverride fun onDrawForeground(\n\t\tcanvas: Canvas,\n\t\tparent: RecyclerView,\n\t\tchild: View,\n\t\tbounds: RectF,\n\t\tstate: RecyclerView.State,\n\t) {\n\t\tpaint.color = strokeColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerSelectorAdapter.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter\n\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener\nimport org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\n\nclass ScrobblerSelectorAdapter(\n\tclickListener: OnListItemClickListener<ScrobblerManga>,\n\tstateHolderListener: ListStateHolderListener,\n) : BaseListAdapter<ListModel>() {\n\n\tinit {\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.MANGA_SCROBBLING, scrobblingMangaAD(clickListener))\n\t\taddDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())\n\t\taddDelegate(ListItemType.HINT_EMPTY, scrobblerHintAD(stateHolderListener))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblingMangaAD.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemMangaListBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\n\nfun scrobblingMangaAD(\n\tclickListener: OnListItemClickListener<ScrobblerManga>,\n) = adapterDelegateViewBinding<ScrobblerManga, ListModel, ItemMangaListBinding>(\n\t{ inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) },\n) {\n\titemView.setOnClickListener {\n\t\tclickListener.onItemClick(item, it)\n\t}\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.name\n\t\tval endIcon = if (item.isBestMatch) R.drawable.ic_star_small else 0\n\t\tbinding.textViewTitle.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, endIcon, 0)\n\t\tbinding.textViewSubtitle.textAndVisible = item.altName\n\t\tbinding.imageViewCover.setImageAsync(item.cover, null)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/model/ScrobblerHint.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.common.ui.selector.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class ScrobblerHint(\n\t@DrawableRes val icon: Int,\n\t@StringRes val textPrimary: Int,\n\t@StringRes val textSecondary: Int,\n\tval error: Throwable?,\n\t@StringRes val actionStringRes: Int,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is ScrobblerHint && other.textPrimary == textPrimary\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/discord/data/DiscordRepository.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.discord.data\n\nimport android.content.Context\nimport dagger.Reusable\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.serialization.json.Json\nimport kotlinx.serialization.json.JsonArray\nimport kotlinx.serialization.json.JsonObject\nimport kotlinx.serialization.json.jsonObject\nimport kotlinx.serialization.json.jsonPrimitive\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.internal.closeQuietly\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.ensureSuccess\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.parseRaw\nimport javax.inject.Inject\n\nprivate const val SCHEME_MP = \"mp:\"\n\n@Reusable\nclass DiscordRepository @Inject constructor(\n\t@ApplicationContext context: Context,\n\tprivate val settings: AppSettings,\n\t@BaseHttpClient private val httpClient: OkHttpClient,\n) {\n\n\tprivate val appId = context.getString(R.string.discord_app_id)\n\n\tsuspend fun getMediaProxyUrl(url: String): String? {\n\t\tif (isMediaProxyUrl(url)) {\n\t\t\treturn url\n\t\t}\n\t\tval token = checkNotNull(settings.discordToken) {\n\t\t\t\"Discord token is missing\"\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.url(\"https://discord.com/api/v10/applications/${appId}/external-assets\")\n\t\t\t.header(CommonHeaders.AUTHORIZATION, token)\n\t\t\t.post(\"{\\\"urls\\\":[\\\"${url}\\\"]}\".toRequestBody(\"application/json\".toMediaType()))\n\t\t\t.build()\n\t\tval body = httpClient.newCall(request).await().parseRaw()\n\t\twhen (val json = Json.parseToJsonElement(body)) {\n\t\t\tis JsonObject -> throw RuntimeException(json.jsonObject[\"message\"]?.jsonPrimitive?.content)\n\t\t\tis JsonArray -> {\n\t\t\t\tval externalAssetPath = json.firstOrNull()\n\t\t\t\t\t?.jsonObject\n\t\t\t\t\t?.get(\"external_asset_path\")\n\t\t\t\t\t?.toString()\n\t\t\t\t\t?.replace(\"\\\"\", \"\")\n\t\t\t\treturn externalAssetPath?.let { SCHEME_MP + it }\n\t\t\t}\n\t\t\telse -> throw RuntimeException(\"Unexpected response: $json\")\n\t\t}\n\t}\n\n\tfun isMediaProxyUrl(url: String) = url.startsWith(SCHEME_MP)\n\n\tsuspend fun checkToken(token: String) {\n\t\tval request = Request.Builder()\n\t\t\t.url(\"https://discord.com/api/v10/users/@me\")\n\t\t\t.header(CommonHeaders.AUTHORIZATION, token)\n\t\t\t.get()\n\t\t\t.build()\n\t\thttpClient.newCall(request).await().ensureSuccess().closeQuietly()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/discord/ui/DiscordAuthActivity.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.discord.ui\n\nimport android.os.Bundle\nimport android.view.MenuItem\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.browser.BaseBrowserActivity\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass DiscordAuthActivity : BaseBrowserActivity(), DiscordTokenWebClient.Callback {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\toverride fun onCreate2(\n\t\tsavedInstanceState: Bundle?,\n\t\tsource: MangaSource,\n\t\trepository: ParserMangaRepository?\n\t) {\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tviewBinding.webView.settings.userAgentString = USER_AGENT\n\t\tviewBinding.webView.webViewClient = DiscordTokenWebClient(this)\n\t\tif (savedInstanceState == null) {\n\t\t\tviewBinding.webView.loadUrl(BASE_URL)\n\t\t}\n\t}\n\n\toverride fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {\n\t\tandroid.R.id.home -> {\n\t\t\tviewBinding.webView.stopLoading()\n\t\t\tfinishAfterTransition()\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onOptionsItemSelected(item)\n\t}\n\n\toverride fun onTokenObtained(token: String) {\n\t\tsettings.discordToken = token\n\t\tsetResult(RESULT_OK)\n\t\tfinish()\n\t}\n\n\tprivate companion object {\n\n\t\tconst val BASE_URL = \"https://discord.com/login\"\n\t\tprivate const val USER_AGENT = \"Mozilla/5.0 (Linux; Android 14; SM-S921U; Build/UP1A.231005.007) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.363\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/discord/ui/DiscordRpc.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.discord.ui\n\nimport android.content.Context\nimport android.os.SystemClock\nimport androidx.annotation.AnyThread\nimport androidx.collection.ArrayMap\nimport com.my.kizzyrpc.KizzyRPC\nimport com.my.kizzyrpc.entities.presence.Activity\nimport com.my.kizzyrpc.entities.presence.Assets\nimport com.my.kizzyrpc.entities.presence.Metadata\nimport com.my.kizzyrpc.entities.presence.Timestamps\nimport dagger.hilt.android.ViewModelLifecycle\nimport dagger.hilt.android.lifecycle.RetainedLifecycle\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport okio.utf8Size\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.model.appUrl\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.lifecycleScope\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderUiState\nimport org.koitharu.kotatsu.scrobbling.discord.data.DiscordRepository\nimport java.util.Collections\nimport javax.inject.Inject\n\nprivate const val STATUS_ONLINE = \"online\"\nprivate const val STATUS_IDLE = \"idle\"\nprivate const val BUTTON_TEXT_LIMIT = 32\nprivate const val DEBOUNCE_TIMEOUT = 16_000L // 16 sec\n\n@ViewModelScoped\nclass DiscordRpc @Inject constructor(\n\t@LocalizedAppContext private val context: Context,\n\tprivate val settings: AppSettings,\n\tprivate val repository: DiscordRepository,\n\tlifecycle: ViewModelLifecycle,\n) : RetainedLifecycle.OnClearedListener {\n\n\tprivate val coroutineScope = lifecycle.lifecycleScope + Dispatchers.Default\n\tprivate val appId = context.getString(R.string.discord_app_id)\n\tprivate val appName = context.getString(R.string.app_name)\n\tprivate val appIcon = context.getString(R.string.app_icon_url)\n\tprivate val mpCache = Collections.synchronizedMap(ArrayMap<String, String>())\n\tprivate var lastUpdate = 0L\n\n\tprivate var rpc: KizzyRPC? = null\n\n\tprivate var rpcUpdateJob: Job? = null\n\n\t@Volatile\n\tprivate var lastActivity: Activity? = null\n\n\tinit {\n\t\tlifecycle.addOnClearedListener(this)\n\t}\n\n\toverride fun onCleared() {\n\t\tclearRpc()\n\t}\n\n\tfun clearRpc() = synchronized(this) {\n\t\trpc?.closeRPC()\n\t\trpc = null\n\t\tlastUpdate = 0L\n\t}\n\n\tfun setIdle() {\n\t\tlastActivity?.let { activity ->\n\t\t\tgetRpc()?.updateRpcAsync(activity, idle = true)\n\t\t}\n\t}\n\n\t@AnyThread\n\tfun updateRpc(manga: Manga, state: ReaderUiState) {\n\t\tgetRpc()?.run {\n\t\t\tif (settings.isDiscordRpcSkipNsfw && manga.isNsfw()) {\n\t\t\t\tclearRpc()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tupdateRpcAsync(\n\t\t\t\tactivity = Activity(\n\t\t\t\t\tapplicationId = appId,\n\t\t\t\t\tname = appName,\n\t\t\t\t\tdetails = manga.title,\n\t\t\t\t\tstate = context.getString(R.string.chapter_d_of_d, state.chapterNumber, state.chaptersTotal),\n\t\t\t\t\ttype = 3,\n\t\t\t\t\ttimestamps = Timestamps(\n\t\t\t\t\t\tstart = lastActivity?.timestamps?.start ?: System.currentTimeMillis(),\n\t\t\t\t\t),\n\t\t\t\t\tassets = Assets(\n\t\t\t\t\t\tlargeImage = manga.coverUrl,\n\t\t\t\t\t\tlargeText = context.getString(R.string.reading_s, manga.title),\n\t\t\t\t\t\tsmallText = context.getString(R.string.discord_rpc_description),\n\t\t\t\t\t\tsmallImage = appIcon,\n\t\t\t\t\t),\n\t\t\t\t\tbuttons = listOf(\n\t\t\t\t\t\tcontext.getString(R.string.read_on_s, appName),\n\t\t\t\t\t\tcontext.getString(R.string.read_on_s, manga.source.getTitle(context)),\n\t\t\t\t\t),\n\t\t\t\t\tmetadata = Metadata(listOf(manga.appUrl.toString(), manga.publicUrl)),\n\t\t\t\t),\n\t\t\t\tidle = false,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate fun KizzyRPC.updateRpcAsync(activity: Activity, idle: Boolean) {\n\t\tval prevJob = rpcUpdateJob\n\t\trpcUpdateJob = coroutineScope.launch {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tval debounceTime = lastUpdate + DEBOUNCE_TIMEOUT - SystemClock.elapsedRealtime()\n\t\t\tif (debounceTime > 0) {\n\t\t\t\tdelay(debounceTime)\n\t\t\t}\n\t\t\tval hideButtons = activity.buttons?.any { it != null && it.utf8Size() > BUTTON_TEXT_LIMIT } ?: false\n\t\t\tval mappedActivity = activity.copy(\n\t\t\t\tassets = activity.assets?.let {\n\t\t\t\t\tit.copy(\n\t\t\t\t\t\tlargeImage = it.largeImage?.toMediaProxyUrl(),\n\t\t\t\t\t\tsmallImage = it.smallImage?.toMediaProxyUrl(),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\tbuttons = activity.buttons.takeUnless { hideButtons },\n\t\t\t\tmetadata = activity.metadata.takeUnless { hideButtons },\n\t\t\t)\n\t\t\tlastActivity = mappedActivity\n\t\t\tupdateRPC(\n\t\t\t\tactivity = mappedActivity,\n\t\t\t\tstatus = if (idle) STATUS_IDLE else STATUS_ONLINE,\n\t\t\t\tsince = activity.timestamps?.start ?: System.currentTimeMillis(),\n\t\t\t)\n\t\t\tlastUpdate = SystemClock.elapsedRealtime()\n\t\t}\n\t}\n\n\tsuspend fun String.toMediaProxyUrl(): String? {\n\t\tif (repository.isMediaProxyUrl(this)) {\n\t\t\treturn this\n\t\t}\n\t\tmpCache[this]?.let {\n\t\t\treturn it\n\t\t}\n\t\treturn runCatchingCancellable {\n\t\t\trepository.getMediaProxyUrl(this)\n\t\t}.onSuccess { url ->\n\t\t\tmpCache[this] = url\n\t\t}.onFailure {\n\t\t\tit.printStackTraceDebug()\n\t\t}.getOrNull()\n\t}\n\n\tprivate fun getRpc(): KizzyRPC? {\n\t\trpc?.let {\n\t\t\treturn it\n\t\t}\n\t\treturn synchronized(this) {\n\t\t\trpc?.let {\n\t\t\t\treturn@synchronized it\n\t\t\t}\n\t\t\tif (settings.isDiscordRpcEnabled) {\n\t\t\t\tsettings.discordToken?.let { KizzyRPC(it) }\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}.also {\n\t\t\t\trpc = it\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/discord/ui/DiscordTokenWebClient.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.discord.ui\n\nimport android.graphics.Bitmap\nimport android.webkit.WebView\nimport org.koitharu.kotatsu.browser.BrowserCallback\nimport org.koitharu.kotatsu.browser.BrowserClient\nimport org.koitharu.kotatsu.parsers.util.removeSurrounding\n\nclass DiscordTokenWebClient(private val callback: Callback) : BrowserClient(callback, null) {\n\n\toverride fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n\t\tsuper.onPageStarted(view, url, favicon)\n\t\tif (view != null) {\n\t\t\tcheckToken(view)\n\t\t}\n\t}\n\n\tprivate fun checkToken(view: WebView) {\n\t\tview.evaluateJavascript(\"window.localStorage.token\") { result ->\n\t\t\tval token = result\n\t\t\t\t?.replace(\"\\\\\\\"\", \"\")\n\t\t\t\t?.removeSurrounding('\"')\n\t\t\t\t?.takeUnless { it == \"null\" }\n\t\t\tif (!token.isNullOrEmpty()) {\n\t\t\t\tcallback.onTokenObtained(token)\n\t\t\t}\n\t\t}\n\t}\n\n\tinterface Callback : BrowserCallback {\n\n\t\tfun onTokenObtained(token: String)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuAuthenticator.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.kitsu.data\n\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.Authenticator\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Route\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport javax.inject.Inject\nimport javax.inject.Provider\n\nclass KitsuAuthenticator @Inject constructor(\n\t@ScrobblerType(ScrobblerService.KITSU) private val storage: ScrobblerStorage,\n\tprivate val repositoryProvider: Provider<KitsuRepository>,\n) : Authenticator {\n\n\toverride fun authenticate(route: Route?, response: Response): Request? {\n\t\tval accessToken = storage.accessToken ?: return null\n\t\tif (!isRequestWithAccessToken(response)) {\n\t\t\treturn null\n\t\t}\n\t\tsynchronized(this) {\n\t\t\tval newAccessToken = storage.accessToken ?: return null\n\t\t\tif (accessToken != newAccessToken) {\n\t\t\t\treturn newRequestWithAccessToken(response.request, newAccessToken)\n\t\t\t}\n\t\t\tval updatedAccessToken = refreshAccessToken() ?: return null\n\t\t\treturn newRequestWithAccessToken(response.request, updatedAccessToken)\n\t\t}\n\t}\n\n\tprivate fun isRequestWithAccessToken(response: Response): Boolean {\n\t\tval header = response.request.header(CommonHeaders.AUTHORIZATION)\n\t\treturn header?.startsWith(\"Bearer\") == true\n\t}\n\n\tprivate fun newRequestWithAccessToken(request: Request, accessToken: String): Request {\n\t\treturn request.newBuilder()\n\t\t\t.header(CommonHeaders.AUTHORIZATION, \"Bearer $accessToken\")\n\t\t\t.build()\n\t}\n\n\tprivate fun refreshAccessToken(): String? = runCatching {\n\t\tval repository = repositoryProvider.get()\n\t\trunBlocking { repository.authorize(null) }\n\t\treturn storage.accessToken\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.kitsu.data\n\nimport okhttp3.Interceptor\nimport okhttp3.MediaType.Companion.toMediaTypeOrNull\nimport okhttp3.Response\nimport okhttp3.internal.closeQuietly\nimport okio.IOException\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.parsers.util.mimeType\nimport org.koitharu.kotatsu.parsers.util.nullIfEmpty\nimport org.koitharu.kotatsu.parsers.util.parseHtml\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport java.net.HttpURLConnection\n\nclass KitsuInterceptor(private val storage: ScrobblerStorage) : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval sourceRequest = chain.request()\n\t\tval request = sourceRequest.newBuilder()\n\t\trequest.header(CommonHeaders.CONTENT_TYPE, VND_JSON)\n\t\trequest.header(CommonHeaders.ACCEPT, VND_JSON)\n\t\tval isAuthRequest = sourceRequest.url.pathSegments.contains(\"oauth\")\n\t\tif (!isAuthRequest) {\n\t\t\tstorage.accessToken?.let {\n\t\t\t\trequest.header(CommonHeaders.AUTHORIZATION, \"Bearer $it\")\n\t\t\t}\n\t\t}\n\t\tval response = chain.proceed(request.build())\n\t\tif (!isAuthRequest && response.code == HttpURLConnection.HTTP_UNAUTHORIZED) {\n\t\t\tresponse.closeQuietly()\n\t\t\tthrow ScrobblerAuthRequiredException(ScrobblerService.KITSU)\n\t\t}\n\t\tif (response.mimeType?.toMediaTypeOrNull()?.subtype == SUBTYPE_HTML) {\n\t\t\tval message = runCatchingCancellable {\n\t\t\t\tresponse.parseHtml().title().nullIfEmpty()\n\t\t\t}.onFailure {\n\t\t\t\tresponse.closeQuietly()\n\t\t\t}.getOrNull() ?: \"Invalid response (${response.code})\"\n\t\t\tthrow IOException(message)\n\t\t}\n\t\treturn response\n\t}\n\n\tcompanion object {\n\n\t\tconst val VND_JSON = \"application/vnd.api+json\"\n\t\tconst val SUBTYPE_HTML = \"html\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuRepository.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.kitsu.data\n\nimport android.content.Context\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport okhttp3.FormBody\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okio.IOException\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.util.ext.parseJsonOrNull\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault\nimport org.koitharu.kotatsu.parsers.util.json.getIntOrDefault\nimport org.koitharu.kotatsu.parsers.util.json.getStringOrNull\nimport org.koitharu.kotatsu.parsers.util.json.mapJSON\nimport org.koitharu.kotatsu.parsers.util.parseJson\nimport org.koitharu.kotatsu.parsers.util.urlEncoded\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuInterceptor.Companion.VND_JSON\n\nprivate const val BASE_WEB_URL = \"https://kitsu.app\"\n\nclass KitsuRepository(\n\t@ApplicationContext context: Context,\n\tprivate val okHttp: OkHttpClient,\n\tprivate val storage: ScrobblerStorage,\n\tprivate val db: MangaDatabase,\n) : ScrobblerRepository {\n\n\t// not in use yet\n\tprivate val clientId = context.getString(R.string.kitsu_clientId)\n\tprivate val clientSecret = context.getString(R.string.kitsu_clientSecret)\n\n\toverride val oauthUrl: String = \"kotatsu+kitsu://auth\"\n\n\toverride val isAuthorized: Boolean\n\t\tget() = storage.accessToken != null\n\n\toverride val cachedUser: ScrobblerUser?\n\t\tget() {\n\t\t\treturn storage.user\n\t\t}\n\n\toverride suspend fun authorize(code: String?) {\n\t\tval body = FormBody.Builder()\n\t\tif (code != null) {\n\t\t\tbody.add(\"grant_type\", \"password\")\n\t\t\tbody.add(\"username\", code.substringBefore(';'))\n\t\t\tbody.add(\"password\", code.substringAfter(';'))\n\t\t} else {\n\t\t\tbody.add(\"grant_type\", \"refresh_token\")\n\t\t\tbody.add(\"refresh_token\", checkNotNull(storage.refreshToken))\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.post(body.build())\n\t\t\t.url(\"$BASE_WEB_URL/api/oauth/token\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\tstorage.accessToken = response.getString(\"access_token\")\n\t\tstorage.refreshToken = response.getString(\"refresh_token\")\n\t}\n\n\toverride suspend fun loadUser(): ScrobblerUser {\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(\"$BASE_WEB_URL/api/edge/users?filter[self]=true\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\t\t.getJSONArray(\"data\")\n\t\t\t.getJSONObject(0)\n\t\treturn ScrobblerUser(\n\t\t\tid = response.getAsLong(\"id\"),\n\t\t\tnickname = response.getJSONObject(\"attributes\").getString(\"name\"),\n\t\t\tavatar = response.getJSONObject(\"attributes\").optJSONObject(\"avatar\")?.getStringOrNull(\"small\"),\n\t\t\tservice = ScrobblerService.KITSU,\n\t\t).also { storage.user = it }\n\t}\n\n\toverride fun logout() {\n\t\tstorage.clear()\n\t}\n\n\toverride suspend fun unregister(mangaId: Long) {\n\t\treturn db.getScrobblingDao().delete(ScrobblerService.KITSU.id, mangaId)\n\t}\n\n\toverride suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(\"$BASE_WEB_URL/api/edge/manga?page[limit]=20&page[offset]=$offset&filter[text]=${query.urlEncoded()}\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson().ensureSuccess()\n\t\treturn response.getJSONArray(\"data\").mapJSON { jo ->\n\t\t\tval attrs = jo.getJSONObject(\"attributes\")\n\t\t\tval titles = attrs.getJSONObject(\"titles\").valuesToStringList()\n\t\t\tScrobblerManga(\n\t\t\t\tid = jo.getAsLong(\"id\"),\n\t\t\t\tname = titles.first(),\n\t\t\t\taltName = titles.drop(1).joinToString(),\n\t\t\t\tcover = attrs.getJSONObject(\"posterImage\").getStringOrNull(\"small\").orEmpty(),\n\t\t\t\turl = \"$BASE_WEB_URL/manga/${attrs.getString(\"slug\")}\",\n\t\t\t\tisBestMatch = titles.any {\n\t\t\t\t\tit.equals(query, ignoreCase = true)\n\t\t\t\t}\n\t\t\t)\n\t\t}\n\t}\n\n\toverride suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(\"$BASE_WEB_URL/api/edge/manga/$id\")\n\t\tval data = okHttp.newCall(request.build()).await().parseJson().ensureSuccess().getJSONObject(\"data\")\n\t\tval attrs = data.getJSONObject(\"attributes\")\n\t\treturn ScrobblerMangaInfo(\n\t\t\tid = data.getAsLong(\"id\"),\n\t\t\tname = attrs.getString(\"canonicalTitle\"),\n\t\t\tcover = attrs.getJSONObject(\"posterImage\").getString(\"medium\"),\n\t\t\turl = \"$BASE_WEB_URL/manga/${attrs.getString(\"slug\")}\",\n\t\t\tdescriptionHtml = attrs.getString(\"description\").replace(\"\\\\n\", \"<br>\"),\n\t\t)\n\t}\n\n\toverride suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) {\n\t\tfindExistingRate(scrobblerMangaId)?.let {\n\t\t\tsaveRate(it, mangaId)\n\t\t\treturn\n\t\t}\n\t\tval user = cachedUser ?: loadUser()\n\t\tval payload = JSONObject()\n\t\tpayload.putJO(\"data\") {\n\t\t\tput(\"type\", \"libraryEntries\")\n\t\t\tputJO(\"attributes\") {\n\t\t\t\tput(\"status\", \"planned\") // will be updated by next call\n\t\t\t\tput(\"progress\", 0)\n\t\t\t}\n\t\t\tputJO(\"relationships\") {\n\t\t\t\tputJO(\"manga\") {\n\t\t\t\t\tputJO(\"data\") {\n\t\t\t\t\t\tput(\"type\", \"manga\")\n\t\t\t\t\t\tput(\"id\", scrobblerMangaId)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tputJO(\"user\") {\n\t\t\t\t\tputJO(\"data\") {\n\t\t\t\t\t\tput(\"type\", \"users\")\n\t\t\t\t\t\tput(\"id\", user.id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.url(\"$BASE_WEB_URL/api/edge/library-entries?include=manga\")\n\t\t\t.post(payload.toKitsuRequestBody())\n\t\tval response = okHttp.newCall(request.build()).await().parseJson().ensureSuccess().getJSONObject(\"data\")\n\t\tsaveRate(response, mangaId)\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, chapter: Int) {\n\t\tval payload = JSONObject()\n\t\tpayload.putJO(\"data\") {\n\t\t\tput(\"type\", \"libraryEntries\")\n\t\t\tput(\"id\", rateId)\n\t\t\tputJO(\"attributes\") {\n\t\t\t\tput(\"progress\", chapter)\n\t\t\t}\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.url(\"$BASE_WEB_URL/api/edge/library-entries/$rateId?include=manga\")\n\t\t\t.patch(payload.toKitsuRequestBody())\n\t\tval response = okHttp.newCall(request.build()).await().parseJson().ensureSuccess().getJSONObject(\"data\")\n\t\tsaveRate(response, mangaId)\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) {\n\t\tval payload = JSONObject()\n\t\tpayload.putJO(\"data\") {\n\t\t\tput(\"type\", \"libraryEntries\")\n\t\t\tput(\"id\", rateId)\n\t\t\tputJO(\"attributes\") {\n\t\t\t\tput(\"status\", status)\n\t\t\t\tput(\"ratingTwenty\", (rating * 20).toInt().coerceIn(2, 20))\n\t\t\t\tput(\"notes\", comment)\n\t\t\t}\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.url(\"$BASE_WEB_URL/api/edge/library-entries/$rateId?include=manga\")\n\t\t\t.patch(payload.toKitsuRequestBody())\n\t\tval response = okHttp.newCall(request.build()).await().parseJson().ensureSuccess().getJSONObject(\"data\")\n\t\tsaveRate(response, mangaId)\n\t}\n\n\tprivate fun JSONObject.valuesToStringList(): List<String> {\n\t\tval result = ArrayList<String>(length())\n\t\tfor (key in keys()) {\n\t\t\tresult.add(getStringOrNull(key) ?: continue)\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate inline fun JSONObject.putJO(name: String, init: JSONObject.() -> Unit) {\n\t\tput(name, JSONObject().apply(init))\n\t}\n\n\tprivate fun JSONObject.toKitsuRequestBody() = toString().toRequestBody(VND_JSON.toMediaType())\n\n\tprivate suspend fun findExistingRate(scrobblerMangaId: Long): JSONObject? {\n\t\tval userId = (cachedUser ?: loadUser()).id\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(\"$BASE_WEB_URL/api/edge/library-entries?filter[manga_id]=$scrobblerMangaId&filter[userId]=$userId&include=manga\")\n\t\tval data = okHttp.newCall(request.build()).await().parseJsonOrNull()?.optJSONArray(\"data\") ?: return null\n\t\treturn data.optJSONObject(0)\n\t}\n\n\tprivate suspend fun saveRate(json: JSONObject, mangaId: Long) {\n\t\tval attrs = json.getJSONObject(\"attributes\")\n\t\tval manga = json.getJSONObject(\"relationships\").getJSONObject(\"manga\").getJSONObject(\"data\")\n\t\tval entity = ScrobblingEntity(\n\t\t\tscrobbler = ScrobblerService.KITSU.id,\n\t\t\tid = json.getInt(\"id\"),\n\t\t\tmangaId = mangaId,\n\t\t\ttargetId = manga.getAsLong(\"id\"),\n\t\t\tstatus = attrs.getString(\"status\"),\n\t\t\tchapter = attrs.getIntOrDefault(\"progress\", 0),\n\t\t\tcomment = attrs.getStringOrNull(\"notes\"),\n\t\t\trating = (attrs.getFloatOrDefault(\"ratingTwenty\", 0f) / 20f).coerceIn(0f, 1f),\n\t\t)\n\t\tdb.getScrobblingDao().upsert(entity)\n\t}\n\n\tprivate fun JSONObject.ensureSuccess(): JSONObject {\n\t\tval error = optJSONArray(\"errors\")?.optJSONObject(0) ?: return this\n\t\tval title = error.getString(\"title\")\n\t\tval detail = error.getStringOrNull(\"detail\")\n\t\tthrow IOException(\"$title: $detail\")\n\t}\n\n\tprivate fun JSONObject.getAsLong(name: String): Long = when (val rawValue = opt(name)) {\n\t\tis Long -> rawValue\n\t\tis Number -> rawValue.toLong()\n\t\tis String -> rawValue.toLong()\n\t\telse -> throw IllegalArgumentException(\"Value $rawValue at \\\"$name\\\" is not of type long\")\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/domain/KitsuScrobbler.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.kitsu.domain\n\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuRepository\nimport javax.inject.Inject\n\nclass KitsuScrobbler @Inject constructor(\n\tprivate val repository: KitsuRepository,\n\tdb: MangaDatabase,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n) : Scrobbler(db, ScrobblerService.KITSU, repository, mangaRepositoryFactory) {\n\n\tinit {\n\t\tstatuses[ScrobblingStatus.PLANNED] = \"planned\"\n\t\tstatuses[ScrobblingStatus.READING] = \"current\"\n\t\tstatuses[ScrobblingStatus.COMPLETED] = \"completed\"\n\t\tstatuses[ScrobblingStatus.ON_HOLD] = \"on_hold\"\n\t\tstatuses[ScrobblingStatus.DROPPED] = \"dropped\"\n\t}\n\n\toverride suspend fun updateScrobblingInfo(\n\t\tmangaId: Long,\n\t\trating: Float,\n\t\tstatus: ScrobblingStatus?,\n\t\tcomment: String?\n\t) {\n\t\tval entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)\n\t\trequireNotNull(entity) { \"Scrobbling info for manga $mangaId not found\" }\n\t\trepository.updateRate(\n\t\t\trateId = entity.id,\n\t\t\tmangaId = entity.mangaId,\n\t\t\trating = rating,\n\t\t\tstatus = statuses[status],\n\t\t\tcomment = comment,\n\t\t)\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.kitsu.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.os.Bundle\nimport android.text.Editable\nimport android.view.KeyEvent\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.widget.TextView\nimport androidx.core.net.toUri\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.databinding.ActivityKitsuAuthBinding\nimport org.koitharu.kotatsu.parsers.util.urlEncoded\n\nclass KitsuAuthActivity : BaseActivity<ActivityKitsuAuthBinding>(),\n\tView.OnClickListener,\n\tDefaultTextWatcher,\n\tTextView.OnEditorActionListener {\n\n\tprivate val regexEmail = Regex(\"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\\\.[A-Z]{2,6}$\", RegexOption.IGNORE_CASE)\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityKitsuAuthBinding.inflate(layoutInflater))\n\t\tviewBinding.buttonCancel.setOnClickListener(this)\n\t\tviewBinding.buttonDone.setOnClickListener(this)\n\t\tviewBinding.editEmail.addTextChangedListener(this)\n\t\tviewBinding.editEmail.setOnEditorActionListener(this)\n\t\tviewBinding.editPassword.addTextChangedListener(this)\n\t\tviewBinding.editPassword.setOnEditorActionListener(this)\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval screenPadding = v.resources.getDimensionPixelOffset(R.dimen.screen_padding)\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tviewBinding.root.updatePadding(top = barsInsets.top)\n\t\tviewBinding.dockedToolbarChild.updateLayoutParams<MarginLayoutParams> {\n\t\t\tleftMargin = barsInsets.left\n\t\t\trightMargin = barsInsets.right\n\t\t\tbottomMargin = barsInsets.bottom\n\t\t}\n\t\tviewBinding.layoutEmail.updateLayoutParams<MarginLayoutParams> {\n\t\t\tleftMargin = barsInsets.left + screenPadding\n\t\t\trightMargin = barsInsets.right + screenPadding\n\t\t}\n\t\tviewBinding.layoutPassword.updateLayoutParams<MarginLayoutParams> {\n\t\t\tleftMargin = barsInsets.left + screenPadding\n\t\t\trightMargin = barsInsets.right + screenPadding\n\t\t}\n\t\treturn insets.consume(v, typeMask)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_cancel -> finish()\n\t\t\tR.id.button_done -> continueAuth()\n\t\t}\n\t}\n\n\toverride fun onEditorAction(\n\t\tv: TextView,\n\t\tactionId: Int,\n\t\tevent: KeyEvent?\n\t): Boolean = when (v.id) {\n\t\tR.id.edit_email -> {\n\t\t\tviewBinding.editPassword.requestFocus()\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.edit_password -> {\n\t\t\tif (viewBinding.buttonDone.isEnabled) {\n\t\t\t\tcontinueAuth()\n\t\t\t\ttrue\n\t\t\t} else {\n\t\t\t\tfalse\n\t\t\t}\n\t\t}\n\n\t\telse -> false\n\t}\n\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tval email = viewBinding.editEmail.text?.toString()?.trim()\n\t\tval password = viewBinding.editPassword.text?.toString()?.trim()\n\t\tviewBinding.buttonDone.isEnabled = !email.isNullOrEmpty()\n\t\t\t&& !password.isNullOrEmpty()\n\t\t\t&& regexEmail.matches(email)\n\t\t\t&& password.length >= 3\n\t}\n\n\t@SuppressLint(\"UnsafeImplicitIntentLaunch\")\n\tprivate fun continueAuth() {\n\t\tval email = viewBinding.editEmail.text?.toString()?.trim().orEmpty()\n\t\tval password = viewBinding.editPassword.text?.toString()?.trim().orEmpty()\n\t\tval url = \"kotatsu://kitsu-auth?code=\" + \"$email;$password\".urlEncoded()\n\t\tval intent = Intent(Intent.ACTION_VIEW, url.toUri())\n\t\tstartActivity(intent)\n\t\tfinishAfterTransition()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/mal/data/MALAuthenticator.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.mal.data\n\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.Authenticator\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Route\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport javax.inject.Inject\nimport javax.inject.Provider\n\nclass MALAuthenticator @Inject constructor(\n\t@ScrobblerType(ScrobblerService.MAL) private val storage: ScrobblerStorage,\n\tprivate val repositoryProvider: Provider<MALRepository>,\n) : Authenticator {\n\n\toverride fun authenticate(route: Route?, response: Response): Request? {\n\t\tval accessToken = storage.accessToken ?: return null\n\t\tif (!isRequestWithAccessToken(response)) {\n\t\t\treturn null\n\t\t}\n\t\tsynchronized(this) {\n\t\t\tval newAccessToken = storage.accessToken ?: return null\n\t\t\tif (accessToken != newAccessToken) {\n\t\t\t\treturn newRequestWithAccessToken(response.request, newAccessToken)\n\t\t\t}\n\t\t\tval updatedAccessToken = refreshAccessToken() ?: return null\n\t\t\treturn newRequestWithAccessToken(response.request, updatedAccessToken)\n\t\t}\n\t}\n\n\tprivate fun isRequestWithAccessToken(response: Response): Boolean {\n\t\tval header = response.request.header(CommonHeaders.AUTHORIZATION)\n\t\treturn header?.startsWith(\"Bearer\") == true\n\t}\n\n\tprivate fun newRequestWithAccessToken(request: Request, accessToken: String): Request {\n\t\treturn request.newBuilder()\n\t\t\t.header(CommonHeaders.AUTHORIZATION, \"Bearer $accessToken\")\n\t\t\t.build()\n\t}\n\n\tprivate fun refreshAccessToken(): String? = runCatching {\n\t\tval repository = repositoryProvider.get()\n\t\trunBlocking { repository.authorize(null) }\n\t\treturn storage.accessToken\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/mal/data/MALInterceptor.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.mal.data\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport okio.IOException\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.parsers.util.mimeType\nimport org.koitharu.kotatsu.parsers.util.parseHtml\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport java.net.HttpURLConnection\n\nprivate const val JSON = \"application/json\"\nprivate const val HTML = \"text/html\"\n\nclass MALInterceptor(private val storage: ScrobblerStorage) : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval sourceRequest = chain.request()\n\t\tval request = sourceRequest.newBuilder()\n\t\trequest.header(CommonHeaders.CONTENT_TYPE, JSON)\n\t\trequest.header(CommonHeaders.ACCEPT, JSON)\n\t\tval isAuthRequest = sourceRequest.url.pathSegments.contains(\"oauth\")\n\t\tif (!isAuthRequest) {\n\t\t\tstorage.accessToken?.let {\n\t\t\t\trequest.header(CommonHeaders.AUTHORIZATION, \"Bearer $it\")\n\t\t\t}\n\t\t}\n\t\tval response = chain.proceed(request.build())\n\t\tif (!isAuthRequest && response.code == HttpURLConnection.HTTP_UNAUTHORIZED) {\n\t\t\tthrow ScrobblerAuthRequiredException(ScrobblerService.MAL)\n\t\t}\n\t\tif (response.mimeType == HTML) {\n\t\t\tthrow IOException(response.parseHtml().title())\n\t\t}\n\t\treturn response\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/mal/data/MALRepository.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.mal.data\n\nimport android.content.Context\nimport android.util.Base64\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport okhttp3.FormBody\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.json.getStringOrNull\nimport org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull\nimport org.koitharu.kotatsu.parsers.util.parseJson\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport java.security.SecureRandom\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\nprivate const val REDIRECT_URI = \"kotatsu://mal-auth\"\nprivate const val BASE_WEB_URL = \"https://myanimelist.net\"\nprivate const val BASE_API_URL = \"https://api.myanimelist.net/v2\"\n\n@Singleton\nclass MALRepository @Inject constructor(\n\t@ApplicationContext context: Context,\n\t@ScrobblerType(ScrobblerService.MAL) private val okHttp: OkHttpClient,\n\t@ScrobblerType(ScrobblerService.MAL) private val storage: ScrobblerStorage,\n\tprivate val db: MangaDatabase,\n) : ScrobblerRepository {\n\n\tprivate val clientId = context.getString(R.string.mal_clientId)\n\tprivate val codeVerifier: String by lazy(::generateCodeVerifier)\n\n\toverride val oauthUrl: String\n\t\tget() = \"$BASE_WEB_URL/v1/oauth2/authorize?\" +\n\t\t\t\"response_type=code\" +\n\t\t\t\"&client_id=$clientId\" +\n\t\t\t\"&redirect_uri=$REDIRECT_URI\" +\n\t\t\t\"&code_challenge=$codeVerifier\" +\n\t\t\t\"&code_challenge_method=plain\"\n\n\toverride val isAuthorized: Boolean\n\t\tget() = storage.accessToken != null\n\n\toverride val cachedUser: ScrobblerUser?\n\t\tget() {\n\t\t\treturn storage.user\n\t\t}\n\n\toverride suspend fun authorize(code: String?) {\n\t\tval body = FormBody.Builder()\n\t\tif (code != null) {\n\t\t\tbody.add(\"client_id\", clientId)\n\t\t\tbody.add(\"grant_type\", \"authorization_code\")\n\t\t\tbody.add(\"code\", code)\n\t\t\tbody.add(\"redirect_uri\", REDIRECT_URI)\n\t\t\tbody.add(\"code_verifier\", codeVerifier)\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.post(body.build())\n\t\t\t.url(\"${BASE_WEB_URL}/v1/oauth2/token\")\n\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\tstorage.accessToken = response.getString(\"access_token\")\n\t\tstorage.refreshToken = response.getString(\"refresh_token\")\n\t}\n\n\toverride suspend fun loadUser(): ScrobblerUser {\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(\"${BASE_API_URL}/users/@me\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\treturn MALUser(response).also { storage.user = it }\n\t}\n\n\toverride suspend fun unregister(mangaId: Long) {\n\t\treturn db.getScrobblingDao().delete(ScrobblerService.MAL.id, mangaId)\n\t}\n\n\toverride suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {\n\t\tval url = BASE_API_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"manga\")\n\t\t\t.addQueryParameter(\"offset\", offset.toString())\n\t\t\t.addQueryParameter(\"nsfw\", \"true\")\n\t\t\t// WARNING! MAL API throws a 400 when the query is over 64 characters\n\t\t\t.addQueryParameter(\"q\", query.take(64))\n\t\t\t.build()\n\t\tval request = Request.Builder().url(url).get().build()\n\t\tval response = okHttp.newCall(request).await().parseJson()\n\t\tcheck(response.has(\"data\")) { \"Invalid response: \\\"$response\\\"\" }\n\t\tval data = response.getJSONArray(\"data\")\n\t\treturn data.mapJSONNotNull { jsonToManga(it, query) }\n\t}\n\n\toverride suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {\n\t\tval url = BASE_API_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"manga\")\n\t\t\t.addPathSegment(id.toString())\n\t\t\t.addQueryParameter(\"fields\", \"synopsis\")\n\t\t\t.build()\n\t\tval request = Request.Builder().url(url)\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\treturn ScrobblerMangaInfo(response)\n\t}\n\n\toverride suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) {\n\t\tval body = FormBody.Builder()\n\t\t\t.add(\"status\", \"reading\")\n\t\t\t.add(\"score\", \"0\")\n\t\tval url = BASE_API_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"manga\")\n\t\t\t.addPathSegment(scrobblerMangaId.toString())\n\t\t\t.addPathSegment(\"my_list_status\")\n\t\t\t.addQueryParameter(\"fields\", \"synopsis\")\n\t\t\t.build()\n\t\tval request = Request.Builder()\n\t\t\t.url(url)\n\t\t\t.put(body.build())\n\t\t\t.build()\n\t\tval response = okHttp.newCall(request).await().parseJson()\n\t\tsaveRate(response, mangaId, scrobblerMangaId)\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, chapter: Int) {\n\t\tval body = FormBody.Builder()\n\t\t\t.add(\"num_chapters_read\", chapter.toString())\n\t\tval url = BASE_API_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"manga\")\n\t\t\t.addPathSegment(rateId.toString())\n\t\t\t.addPathSegment(\"my_list_status\")\n\t\t\t.build()\n\t\tval request = Request.Builder()\n\t\t\t.url(url)\n\t\t\t.put(body.build())\n\t\t\t.build()\n\t\tval response = okHttp.newCall(request).await().parseJson()\n\t\tsaveRate(response, mangaId, rateId.toLong())\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) {\n\t\tval body = FormBody.Builder()\n\t\t\t.add(\"status\", status.toString())\n\t\t\t.add(\"score\", rating.toInt().toString())\n\t\t\t.add(\"comments\", comment.orEmpty())\n\t\tval url = BASE_API_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"manga\")\n\t\t\t.addPathSegment(rateId.toString())\n\t\t\t.addPathSegment(\"my_list_status\")\n\t\t\t.build()\n\t\tval request = Request.Builder()\n\t\t\t.url(url)\n\t\t\t.put(body.build())\n\t\t\t.build()\n\t\tval response = okHttp.newCall(request).await().parseJson()\n\t\tsaveRate(response, mangaId, rateId.toLong())\n\t}\n\n\tprivate suspend fun saveRate(json: JSONObject, mangaId: Long, scrobblerMangaId: Long) {\n\t\tval entity = ScrobblingEntity(\n\t\t\tscrobbler = ScrobblerService.MAL.id,\n\t\t\tid = scrobblerMangaId.toInt(),\n\t\t\tmangaId = mangaId,\n\t\t\ttargetId = scrobblerMangaId,\n\t\t\tstatus = json.getString(\"status\"),\n\t\t\tchapter = json.getInt(\"num_chapters_read\"),\n\t\t\tcomment = json.getString(\"comments\"),\n\t\t\trating = (json.getDouble(\"score\").toFloat() / 10f).coerceIn(0f, 1f),\n\t\t)\n\t\tdb.getScrobblingDao().upsert(entity)\n\t}\n\n\toverride fun logout() {\n\t\tstorage.clear()\n\t}\n\n\tprivate fun jsonToManga(json: JSONObject, sourceTitle: String): ScrobblerManga {\n\t\tval node = json.getJSONObject(\"node\")\n\t\tval title = node.getString(\"title\")\n\t\treturn ScrobblerManga(\n\t\t\tid = node.getLong(\"id\"),\n\t\t\tname = title,\n\t\t\taltName = null,\n\t\t\tcover = node.optJSONObject(\"main_picture\")?.getStringOrNull(\"large\"),\n\t\t\turl = \"$BASE_WEB_URL/manga/${node.getLong(\"id\")}\",\n\t\t\tisBestMatch = title.equals(sourceTitle, ignoreCase = true),\n\t\t)\n\t}\n\n\tprivate fun ScrobblerMangaInfo(json: JSONObject) = ScrobblerMangaInfo(\n\t\tid = json.getLong(\"id\"),\n\t\tname = json.getString(\"title\"),\n\t\tcover = json.getJSONObject(\"main_picture\").getString(\"large\"),\n\t\turl = \"$BASE_WEB_URL/manga/${json.getLong(\"id\")}\",\n\t\tdescriptionHtml = json.getString(\"synopsis\"),\n\t)\n\n\t@Suppress(\"FunctionName\")\n\tprivate fun MALUser(json: JSONObject) = ScrobblerUser(\n\t\tid = json.getLong(\"id\"),\n\t\tnickname = json.getString(\"name\"),\n\t\tavatar = json.getStringOrNull(\"picture\"),\n\t\tservice = ScrobblerService.MAL,\n\t)\n\n\tprivate fun generateCodeVerifier(): String {\n\t\tval codeVerifier = ByteArray(50)\n\t\tSecureRandom().nextBytes(codeVerifier)\n\t\treturn Base64.encodeToString(codeVerifier, Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/mal/domain/MALScrobbler.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.mal.domain\n\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport org.koitharu.kotatsu.scrobbling.mal.data.MALRepository\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\nprivate const val RATING_MAX = 10f\n\n@Singleton\nclass MALScrobbler @Inject constructor(\n\tprivate val repository: MALRepository,\n\tdb: MangaDatabase,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n) : Scrobbler(db, ScrobblerService.MAL, repository, mangaRepositoryFactory) {\n\n\tinit {\n\t\tstatuses[ScrobblingStatus.PLANNED] = \"plan_to_read\"\n\t\tstatuses[ScrobblingStatus.READING] = \"reading\"\n\t\tstatuses[ScrobblingStatus.COMPLETED] = \"completed\"\n\t\tstatuses[ScrobblingStatus.ON_HOLD] = \"on_hold\"\n\t\tstatuses[ScrobblingStatus.DROPPED] = \"dropped\"\n\t}\n\n\toverride suspend fun updateScrobblingInfo(\n\t\tmangaId: Long,\n\t\trating: Float,\n\t\tstatus: ScrobblingStatus?,\n\t\tcomment: String?,\n\t) {\n\t\tval entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)\n\t\trequireNotNull(entity) { \"Scrobbling info for manga $mangaId not found\" }\n\t\trepository.updateRate(\n\t\t\trateId = entity.id,\n\t\t\tmangaId = entity.mangaId,\n\t\t\trating = rating * RATING_MAX,\n\t\t\tstatus = statuses[status],\n\t\t\tcomment = comment,\n\t\t)\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriAuthenticator.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.shikimori.data\n\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.Authenticator\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Route\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport javax.inject.Inject\nimport javax.inject.Provider\n\nclass ShikimoriAuthenticator @Inject constructor(\n\t@ScrobblerType(ScrobblerService.SHIKIMORI) private val storage: ScrobblerStorage,\n\tprivate val repositoryProvider: Provider<ShikimoriRepository>,\n) : Authenticator {\n\n\toverride fun authenticate(route: Route?, response: Response): Request? {\n\t\tval accessToken = storage.accessToken ?: return null\n\t\tif (!isRequestWithAccessToken(response)) {\n\t\t\treturn null\n\t\t}\n\t\tsynchronized(this) {\n\t\t\tval newAccessToken = storage.accessToken ?: return null\n\t\t\tif (accessToken != newAccessToken) {\n\t\t\t\treturn newRequestWithAccessToken(response.request, newAccessToken)\n\t\t\t}\n\t\t\tval updatedAccessToken = refreshAccessToken() ?: return null\n\t\t\treturn newRequestWithAccessToken(response.request, updatedAccessToken)\n\t\t}\n\t}\n\n\tprivate fun isRequestWithAccessToken(response: Response): Boolean {\n\t\tval header = response.request.header(CommonHeaders.AUTHORIZATION)\n\t\treturn header?.startsWith(\"Bearer\") == true\n\t}\n\n\tprivate fun newRequestWithAccessToken(request: Request, accessToken: String): Request {\n\t\treturn request.newBuilder()\n\t\t\t.header(CommonHeaders.AUTHORIZATION, \"Bearer $accessToken\")\n\t\t\t.build()\n\t}\n\n\tprivate fun refreshAccessToken(): String? = runCatching {\n\t\tval repository = repositoryProvider.get()\n\t\trunBlocking { repository.authorize(null) }\n\t\treturn storage.accessToken\n\t}.onFailure {\n\t\tit.printStackTraceDebug()\n\t}.getOrNull()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriInterceptor.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.shikimori.data\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport okio.IOException\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport java.net.HttpURLConnection\n\nprivate const val USER_AGENT_SHIKIMORI = \"Kotatsu\"\n\nclass ShikimoriInterceptor(private val storage: ScrobblerStorage) : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval sourceRequest = chain.request()\n\t\tval request = sourceRequest.newBuilder()\n\t\trequest.header(CommonHeaders.USER_AGENT, USER_AGENT_SHIKIMORI)\n\t\tval isAuthRequest = sourceRequest.url.pathSegments.contains(\"oauth\")\n\t\tif (!isAuthRequest) {\n\t\t\tstorage.accessToken?.let {\n\t\t\t\trequest.header(CommonHeaders.AUTHORIZATION, \"Bearer $it\")\n\t\t\t}\n\t\t}\n\t\tval response = chain.proceed(request.build())\n\t\tif (!isAuthRequest && response.code == HttpURLConnection.HTTP_UNAUTHORIZED) {\n\t\t\tthrow ScrobblerAuthRequiredException(ScrobblerService.SHIKIMORI)\n\t\t}\n\t\tif (!response.isSuccessful && !response.isRedirect) {\n\t\t\tthrow IOException(\"${response.code} ${response.message}\")\n\t\t}\n\t\treturn response\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.shikimori.data\n\nimport android.content.Context\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport okhttp3.FormBody\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.util.ext.toRequestBody\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.json.getStringOrNull\nimport org.koitharu.kotatsu.parsers.util.json.mapJSON\nimport org.koitharu.kotatsu.parsers.util.parseJson\nimport org.koitharu.kotatsu.parsers.util.parseJsonArray\nimport org.koitharu.kotatsu.parsers.util.toAbsoluteUrl\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage\nimport org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\nprivate const val DOMAIN = \"shikimori.one\"\nprivate const val REDIRECT_URI = \"kotatsu://shikimori-auth\"\nprivate const val BASE_URL = \"https://$DOMAIN/\"\nprivate const val MANGA_PAGE_SIZE = 10\n\n@Singleton\nclass ShikimoriRepository @Inject constructor(\n\t@ApplicationContext context: Context,\n\t@ScrobblerType(ScrobblerService.SHIKIMORI) private val okHttp: OkHttpClient,\n\t@ScrobblerType(ScrobblerService.SHIKIMORI) private val storage: ScrobblerStorage,\n\tprivate val db: MangaDatabase,\n) : ScrobblerRepository {\n\n\tprivate val clientId = context.getString(R.string.shikimori_clientId)\n\tprivate val clientSecret = context.getString(R.string.shikimori_clientSecret)\n\n\toverride val oauthUrl: String\n\t\tget() = \"${BASE_URL}oauth/authorize?client_id=$clientId&\" +\n\t\t\t\"redirect_uri=$REDIRECT_URI&response_type=code&scope=\"\n\n\toverride val isAuthorized: Boolean\n\t\tget() = storage.accessToken != null\n\n\toverride suspend fun authorize(code: String?) {\n\t\tval body = FormBody.Builder()\n\t\tbody.add(\"client_id\", clientId)\n\t\tbody.add(\"client_secret\", clientSecret)\n\t\tif (code != null) {\n\t\t\tbody.add(\"grant_type\", \"authorization_code\")\n\t\t\tbody.add(\"redirect_uri\", REDIRECT_URI)\n\t\t\tbody.add(\"code\", code)\n\t\t} else {\n\t\t\tbody.add(\"grant_type\", \"refresh_token\")\n\t\t\tbody.add(\"refresh_token\", checkNotNull(storage.refreshToken))\n\t\t}\n\t\tval request = Request.Builder()\n\t\t\t.post(body.build())\n\t\t\t.url(\"${BASE_URL}oauth/token\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\tstorage.accessToken = response.getString(\"access_token\")\n\t\tstorage.refreshToken = response.getString(\"refresh_token\")\n\t}\n\n\toverride suspend fun loadUser(): ScrobblerUser {\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(\"${BASE_URL}api/users/whoami\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\treturn ShikimoriUser(response).also { storage.user = it }\n\t}\n\n\toverride val cachedUser: ScrobblerUser?\n\t\tget() {\n\t\t\treturn storage.user\n\t\t}\n\n\toverride suspend fun unregister(mangaId: Long) {\n\t\treturn db.getScrobblingDao().delete(ScrobblerService.SHIKIMORI.id, mangaId)\n\t}\n\n\toverride fun logout() {\n\t\tstorage.clear()\n\t}\n\n\toverride suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {\n\t\tval page = offset / MANGA_PAGE_SIZE\n\t\tval pageOffset = offset % MANGA_PAGE_SIZE\n\t\tval url = BASE_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"api\")\n\t\t\t.addPathSegment(\"mangas\")\n\t\t\t.addEncodedQueryParameter(\"page\", (page + 1).toString())\n\t\t\t.addEncodedQueryParameter(\"limit\", MANGA_PAGE_SIZE.toString())\n\t\t\t.addEncodedQueryParameter(\"censored\", false.toString())\n\t\t\t.addQueryParameter(\"search\", query)\n\t\t\t.build()\n\t\tval request = Request.Builder().url(url).get().build()\n\t\tval response = okHttp.newCall(request).await().parseJsonArray()\n\t\tval list = response.mapJSON { ScrobblerManga(it, query) }\n\t\treturn if (pageOffset != 0) list.drop(pageOffset) else list\n\t}\n\n\toverride suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) {\n\t\tval user = cachedUser ?: loadUser()\n\t\tval payload = JSONObject()\n\t\tpayload.put(\n\t\t\t\"user_rate\",\n\t\t\tJSONObject().apply {\n\t\t\t\tput(\"target_id\", scrobblerMangaId)\n\t\t\t\tput(\"target_type\", \"Manga\")\n\t\t\t\tput(\"user_id\", user.id)\n\t\t\t},\n\t\t)\n\t\tval url = BASE_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"api\")\n\t\t\t.addPathSegment(\"v2\")\n\t\t\t.addPathSegment(\"user_rates\")\n\t\t\t.build()\n\t\tval request = Request.Builder().url(url).post(payload.toRequestBody()).build()\n\t\tval response = okHttp.newCall(request).await().parseJson()\n\t\tsaveRate(response, mangaId)\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, chapter: Int) {\n\t\tval payload = JSONObject()\n\t\tpayload.put(\n\t\t\t\"user_rate\",\n\t\t\tJSONObject().apply {\n\t\t\t\tput(\"chapters\", chapter)\n\t\t\t},\n\t\t)\n\t\tval url = BASE_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"api\")\n\t\t\t.addPathSegment(\"v2\")\n\t\t\t.addPathSegment(\"user_rates\")\n\t\t\t.addPathSegment(rateId.toString())\n\t\t\t.build()\n\t\tval request = Request.Builder().url(url).patch(payload.toRequestBody()).build()\n\t\tval response = okHttp.newCall(request).await().parseJson()\n\t\tsaveRate(response, mangaId)\n\t}\n\n\toverride suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) {\n\t\tval payload = JSONObject()\n\t\tpayload.put(\n\t\t\t\"user_rate\",\n\t\t\tJSONObject().apply {\n\t\t\t\tput(\"score\", rating.toString())\n\t\t\t\tif (comment != null) {\n\t\t\t\t\tput(\"text\", comment)\n\t\t\t\t}\n\t\t\t\tif (status != null) {\n\t\t\t\t\tput(\"status\", status)\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t\tval url = BASE_URL.toHttpUrl().newBuilder()\n\t\t\t.addPathSegment(\"api\")\n\t\t\t.addPathSegment(\"v2\")\n\t\t\t.addPathSegment(\"user_rates\")\n\t\t\t.addPathSegment(rateId.toString())\n\t\t\t.build()\n\t\tval request = Request.Builder().url(url).patch(payload.toRequestBody()).build()\n\t\tval response = okHttp.newCall(request).await().parseJson()\n\t\tsaveRate(response, mangaId)\n\t}\n\n\toverride suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {\n\t\tval request = Request.Builder()\n\t\t\t.get()\n\t\t\t.url(\"${BASE_URL}api/mangas/$id\")\n\t\tval response = okHttp.newCall(request.build()).await().parseJson()\n\t\treturn ScrobblerMangaInfo(response)\n\t}\n\n\tprivate suspend fun saveRate(json: JSONObject, mangaId: Long) {\n\t\tval entity = ScrobblingEntity(\n\t\t\tscrobbler = ScrobblerService.SHIKIMORI.id,\n\t\t\tid = json.getInt(\"id\"),\n\t\t\tmangaId = mangaId,\n\t\t\ttargetId = json.getLong(\"target_id\"),\n\t\t\tstatus = json.getString(\"status\"),\n\t\t\tchapter = json.getInt(\"chapters\"),\n\t\t\tcomment = json.getString(\"text\"),\n\t\t\trating = (json.getDouble(\"score\").toFloat() / 10f).coerceIn(0f, 1f),\n\t\t)\n\t\tdb.getScrobblingDao().upsert(entity)\n\t}\n\n\tprivate fun ScrobblerManga(json: JSONObject, sourceTitle: String) = ScrobblerManga(\n\t\tid = json.getLong(\"id\"),\n\t\tname = json.getString(\"name\"),\n\t\taltName = json.getStringOrNull(\"russian\"),\n\t\tcover = json.getJSONObject(\"image\").getString(\"preview\").toAbsoluteUrl(DOMAIN),\n\t\turl = json.getString(\"url\").toAbsoluteUrl(DOMAIN),\n\t\tisBestMatch = sourceTitle.equals(json.getString(\"name\"), ignoreCase = true)\n\t\t\t|| json.getStringOrNull(\"russian\")?.equals(sourceTitle, ignoreCase = true) == true\n\t)\n\n\tprivate fun ScrobblerMangaInfo(json: JSONObject) = ScrobblerMangaInfo(\n\t\tid = json.getLong(\"id\"),\n\t\tname = json.getString(\"name\"),\n\t\tcover = json.getJSONObject(\"image\").getString(\"preview\").toAbsoluteUrl(DOMAIN),\n\t\turl = json.getString(\"url\").toAbsoluteUrl(DOMAIN),\n\t\tdescriptionHtml = json.getString(\"description_html\"),\n\t)\n\n\t@Suppress(\"FunctionName\")\n\tprivate fun ShikimoriUser(json: JSONObject) = ScrobblerUser(\n\t\tid = json.getLong(\"id\"),\n\t\tnickname = json.getString(\"nickname\"),\n\t\tavatar = json.getStringOrNull(\"avatar\"),\n\t\tservice = ScrobblerService.SHIKIMORI,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/shikimori/domain/ShikimoriScrobbler.kt",
    "content": "package org.koitharu.kotatsu.scrobbling.shikimori.domain\n\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus\nimport org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\nprivate const val RATING_MAX = 10f\n\n@Singleton\nclass ShikimoriScrobbler @Inject constructor(\n\tprivate val repository: ShikimoriRepository,\n\tdb: MangaDatabase,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n) : Scrobbler(db, ScrobblerService.SHIKIMORI, repository, mangaRepositoryFactory) {\n\n\tinit {\n\t\tstatuses[ScrobblingStatus.PLANNED] = \"planned\"\n\t\tstatuses[ScrobblingStatus.READING] = \"watching\"\n\t\tstatuses[ScrobblingStatus.RE_READING] = \"rewatching\"\n\t\tstatuses[ScrobblingStatus.COMPLETED] = \"completed\"\n\t\tstatuses[ScrobblingStatus.ON_HOLD] = \"on_hold\"\n\t\tstatuses[ScrobblingStatus.DROPPED] = \"dropped\"\n\t}\n\n\toverride suspend fun updateScrobblingInfo(\n\t\tmangaId: Long,\n\t\trating: Float,\n\t\tstatus: ScrobblingStatus?,\n\t\tcomment: String?,\n\t) {\n\t\tval entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)\n\t\trequireNotNull(entity) { \"Scrobbling info for manga $mangaId not found\" }\n\t\trepository.updateRate(\n\t\t\trateId = entity.id,\n\t\t\tmangaId = entity.mangaId,\n\t\t\trating = rating * RATING_MAX,\n\t\t\tstatus = statuses[status],\n\t\t\tcomment = comment,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt",
    "content": "package org.koitharu.kotatsu.search.domain\n\nimport android.app.SearchManager\nimport android.content.Context\nimport android.provider.SearchRecentSuggestions\nimport dagger.Reusable\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toEntity\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaTag\nimport org.koitharu.kotatsu.core.db.entity.toMangaTagsList\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.levenshteinDistance\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider\nimport javax.inject.Inject\n\n@Reusable\nclass MangaSearchRepository @Inject constructor(\n\tprivate val db: MangaDatabase,\n\tprivate val sourcesRepository: MangaSourcesRepository,\n\t@ApplicationContext private val context: Context,\n\tprivate val recentSuggestions: SearchRecentSuggestions,\n\tprivate val settings: AppSettings,\n) {\n\n\tsuspend fun getMangaSuggestion(query: String, limit: Int, source: MangaSource?): List<Manga> = when {\n\t\tquery.isEmpty() -> db.getSuggestionDao().getTopManga(limit)\n\t\tsource != null -> db.getMangaDao().searchByTitle(\"%$query%\", source.name, limit)\n\t\telse -> db.getMangaDao().searchByTitle(\"%$query%\", limit)\n\t}.let {\n\t\tif (settings.isNsfwContentDisabled) it.filterNot { x -> x.manga.isNsfw } else it\n\t}.map {\n\t\tit.toManga()\n\t}.sortedBy { x ->\n\t\tx.title.levenshteinDistance(query)\n\t}\n\n\tsuspend fun getQuerySuggestion(\n\t\tquery: String,\n\t\tlimit: Int,\n\t): List<String> = withContext(Dispatchers.IO) {\n\t\tcontext.contentResolver.query(\n\t\t\tMangaSuggestionsProvider.QUERY_URI,\n\t\t\tarrayOf(SearchManager.SUGGEST_COLUMN_QUERY),\n\t\t\t\"${SearchManager.SUGGEST_COLUMN_QUERY} LIKE ?\",\n\t\t\tarrayOf(\"%$query%\"),\n\t\t\t\"date DESC\",\n\t\t)?.use { cursor ->\n\t\t\tval count = minOf(cursor.count, limit)\n\t\t\tif (count == 0) {\n\t\t\t\treturn@withContext emptyList()\n\t\t\t}\n\t\t\tval result = ArrayList<String>(count)\n\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\tval index = cursor.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_QUERY)\n\t\t\t\tdo {\n\t\t\t\t\tresult += cursor.getString(index)\n\t\t\t\t} while (currentCoroutineContext().isActive && cursor.moveToNext())\n\t\t\t}\n\t\t\tresult\n\t\t}.orEmpty()\n\t}\n\n\tsuspend fun getQueryHintSuggestion(\n\t\tquery: String,\n\t\tlimit: Int,\n\t): List<String> {\n\t\tif (query.isEmpty()) {\n\t\t\treturn emptyList()\n\t\t}\n\t\tval titles = db.getSuggestionDao().getTitles(\"$query%\")\n\t\tif (titles.isEmpty()) {\n\t\t\treturn emptyList()\n\t\t}\n\t\treturn titles.shuffled().take(limit)\n\t}\n\n\tsuspend fun getAuthorsSuggestion(\n\t\tquery: String,\n\t\tlimit: Int,\n\t): List<String> {\n\t\tif (query.isEmpty()) {\n\t\t\treturn emptyList()\n\t\t}\n\t\treturn db.getMangaDao().findAuthors(\"$query%\", limit)\n\t}\n\n\tsuspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List<MangaTag> {\n\t\treturn when {\n\t\t\tquery.isNotEmpty() && source != null -> db.getTagsDao()\n\t\t\t\t.findTags(source.name, \"%$query%\", limit)\n\n\t\t\tquery.isNotEmpty() -> db.getTagsDao().findTags(\"%$query%\", limit)\n\t\t\tsource != null -> db.getTagsDao().findPopularTags(source.name, limit)\n\t\t\telse -> db.getTagsDao().findPopularTags(limit)\n\t\t}.toMangaTagsList()\n\t}\n\n\tsuspend fun getTagsSuggestion(tags: Set<MangaTag>): List<MangaTag> {\n\t\tval ids = tags.mapToSet { it.toEntity().id }\n\t\treturn if (ids.size == 1) {\n\t\t\tdb.getTagsDao().findRelatedTags(ids.first())\n\t\t} else {\n\t\t\tdb.getTagsDao().findRelatedTags(ids)\n\t\t}.mapNotNull { x ->\n\t\t\tif (x.id in ids) null else x.toMangaTag()\n\t\t}\n\t}\n\n\tsuspend fun getRareTags(source: MangaSource, limit: Int): List<MangaTag> {\n\t\treturn db.getTagsDao().findRareTags(source.name, limit).toMangaTagsList()\n\t}\n\n\tsuspend fun getTopTags(source: MangaSource, limit: Int): List<MangaTag> {\n\t\treturn db.getTagsDao().findPopularTags(source.name, limit).toMangaTagsList()\n\t}\n\n\tsuspend fun getSourcesSuggestion(limit: Int): List<MangaSource> = sourcesRepository.getTopSources(limit)\n\n\tfun getSourcesSuggestion(query: String, limit: Int): List<MangaSource> {\n\t\tif (query.length < 3) {\n\t\t\treturn emptyList()\n\t\t}\n\t\tval skipNsfw = settings.isNsfwContentDisabled\n\t\tval sources = sourcesRepository.allMangaSources\n\t\t\t.filter { x ->\n\t\t\t\t(x.contentType != ContentType.HENTAI || !skipNsfw) && x.title.contains(query, ignoreCase = true)\n\t\t\t}\n\t\treturn if (limit == 0) {\n\t\t\tsources\n\t\t} else {\n\t\t\tsources.take(limit)\n\t\t}\n\t}\n\n\tfun saveSearchQuery(query: String) {\n\t\trecentSuggestions.saveRecentQuery(query, null)\n\t}\n\n\tsuspend fun clearSearchHistory(): Unit = withContext(Dispatchers.IO) {\n\t\trecentSuggestions.clearHistory()\n\t}\n\n\tsuspend fun deleteSearchQuery(query: String) = withContext(Dispatchers.IO) {\n\t\tcontext.contentResolver.delete(\n\t\t\tMangaSuggestionsProvider.URI,\n\t\t\t\"display1 = ?\",\n\t\t\tarrayOf(query),\n\t\t)\n\t}\n\n\tsuspend fun getSearchHistoryCount(): Int = withContext(Dispatchers.IO) {\n\t\tcontext.contentResolver.query(\n\t\t\tMangaSuggestionsProvider.QUERY_URI,\n\t\t\tarrayOf(SearchManager.SUGGEST_COLUMN_QUERY),\n\t\t\tnull,\n\t\t\tarrayOfNulls(1),\n\t\t\tnull,\n\t\t)?.use { cursor -> cursor.count } ?: 0\n\t}\n\n    suspend fun getAuthors(source: MangaSource, limit: Int): List<String> {\n        return db.getMangaDao().findAuthorsBySource(source.name, limit)\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/domain/SearchKind.kt",
    "content": "package org.koitharu.kotatsu.search.domain\n\nenum class SearchKind {\n\n\tSIMPLE, TITLE, AUTHOR, TAG\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/domain/SearchResults.kt",
    "content": "package org.koitharu.kotatsu.search.domain\n\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.SortOrder\n\ndata class SearchResults(\n\tval listFilter: MangaListFilter,\n\tval sortOrder: SortOrder,\n\tval manga: List<Manga>,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/domain/SearchV2Helper.kt",
    "content": "package org.koitharu.kotatsu.search.domain\n\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.contains\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.almostEquals\nimport org.koitharu.kotatsu.parsers.util.levenshteinDistance\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\n\nprivate const val MATCH_THRESHOLD_DEFAULT = 0.2f\n\nclass SearchV2Helper @AssistedInject constructor(\n\t@Assisted private val source: MangaSource,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val dataRepository: MangaDataRepository,\n\tprivate val settings: AppSettings,\n) {\n\n\tsuspend operator fun invoke(query: String, kind: SearchKind): SearchResults? {\n\t\tif (settings.isNsfwContentDisabled && source.isNsfw()) {\n\t\t\treturn null\n\t\t}\n\t\tval repository = mangaRepositoryFactory.create(source)\n\t\tval listFilter = repository.getFilter(query, kind) ?: return null\n\t\tval sortOrder = repository.getSortOrder(kind)\n\t\tval list = repository.getList(0, sortOrder, listFilter)\n\t\tif (list.isEmpty()) {\n\t\t\treturn null\n\t\t}\n\t\tval result = list.toMutableList()\n\t\tresult.postFilter(query, kind)\n\t\tresult.sortByRelevance(query, kind)\n\t\treturn SearchResults(listFilter = listFilter, sortOrder = sortOrder, manga = result)\n\t}\n\n\tprivate suspend fun MangaRepository.getFilter(query: String, kind: SearchKind): MangaListFilter? = when (kind) {\n\t\tSearchKind.SIMPLE,\n\t\tSearchKind.TITLE -> if (filterCapabilities.isSearchSupported) {\n\t\t\tMangaListFilter(query = query)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\n\t\tSearchKind.AUTHOR -> if (filterCapabilities.isAuthorSearchSupported) {\n\t\t\tMangaListFilter(author = query)\n\t\t} else if (filterCapabilities.isSearchSupported) {\n\t\t\tMangaListFilter(query = query)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\n\t\tSearchKind.TAG -> {\n\t\t\tval tags = this@SearchV2Helper.dataRepository.findTags(this.source) + runCatchingCancellable {\n\t\t\t\tthis@getFilter.getFilterOptions().availableTags\n\t\t\t}.onFailure { e ->\n\t\t\t\te.printStackTraceDebug()\n\t\t\t}.getOrDefault(emptySet())\n\t\t\tval tag = tags.find { x -> x.title.equals(query, ignoreCase = true) }\n\t\t\tif (tag != null) {\n\t\t\t\tMangaListFilter(tags = setOf(tag))\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun MutableList<Manga>.postFilter(query: String, kind: SearchKind) {\n\t\tif (settings.isNsfwContentDisabled) {\n\t\t\tremoveAll { it.isNsfw() }\n\t\t}\n\t\twhen (kind) {\n\t\t\tSearchKind.TITLE -> retainAll { m ->\n\t\t\t\tm.matches(query, MATCH_THRESHOLD_DEFAULT)\n\t\t\t}\n\n\t\t\tSearchKind.AUTHOR -> retainAll { m ->\n\t\t\t\tm.authors.isEmpty() || m.authors.contains(query, ignoreCase = true)\n\t\t\t}\n\n\t\t\tSearchKind.SIMPLE, // no filtering expected\n\t\t\tSearchKind.TAG -> Unit\n\t\t}\n\t}\n\n\tprivate fun MutableList<Manga>.sortByRelevance(query: String, kind: SearchKind) {\n\t\twhen (kind) {\n\t\t\tSearchKind.SIMPLE,\n\t\t\tSearchKind.TITLE -> sortBy { m ->\n\t\t\t\tminOf(m.title.levenshteinDistance(query), m.altTitle?.levenshteinDistance(query) ?: Int.MAX_VALUE)\n\t\t\t}\n\n\t\t\tSearchKind.AUTHOR -> sortByDescending { m ->\n\t\t\t\tm.authors.contains(query, ignoreCase = true)\n\t\t\t}\n\n\t\t\tSearchKind.TAG -> sortByDescending { m ->\n\t\t\t\tm.tags.any { tag -> tag.title.equals(query, ignoreCase = true) }\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun MangaRepository.getSortOrder(kind: SearchKind): SortOrder {\n\t\tval preferred: SortOrder = when (kind) {\n\t\t\tSearchKind.SIMPLE,\n\t\t\tSearchKind.TITLE,\n\t\t\tSearchKind.AUTHOR -> SortOrder.RELEVANCE\n\n\t\t\tSearchKind.TAG -> SortOrder.POPULARITY\n\t\t}\n\t\treturn if (preferred in sortOrders) {\n\t\t\tpreferred\n\t\t} else {\n\t\t\tdefaultSortOrder\n\t\t}\n\t}\n\n\n\tprivate fun Manga.matches(query: String, threshold: Float): Boolean {\n\t\treturn matchesTitles(title, query, threshold) || matchesTitles(altTitle, query, threshold)\n\t}\n\n\tprivate fun matchesTitles(a: String?, b: String?, threshold: Float): Boolean {\n\t\treturn !a.isNullOrEmpty() && !b.isNullOrEmpty() && a.almostEquals(b, threshold)\n\t}\n\n\t@AssistedFactory\n\tinterface Factory {\n\n\t\tfun create(source: MangaSource): SearchV2Helper\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt",
    "content": "package org.koitharu.kotatsu.search.ui\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.graphics.drawable.toDrawable\nimport androidx.core.os.bundleOf\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePaddingRelative\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.commit\nimport com.google.android.material.appbar.AppBarLayout\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.model.getSummary\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaListFilter\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.model.titleRes\nimport org.koitharu.kotatsu.core.ui.util.FadingAppbarMediator\nimport org.koitharu.kotatsu.core.util.ViewBadge\nimport org.koitharu.kotatsu.core.util.ext.consumeSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.databinding.ActivityMangaListBinding\nimport org.koitharu.kotatsu.filter.ui.FilterCoordinator\nimport org.koitharu.kotatsu.filter.ui.FilterHeaderFragment\nimport org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment\nimport org.koitharu.kotatsu.list.ui.preview.PreviewFragment\nimport org.koitharu.kotatsu.local.ui.LocalListFragment\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.remotelist.ui.RemoteListFragment\nimport kotlin.math.absoluteValue\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nclass MangaListActivity :\n\tBaseActivity<ActivityMangaListBinding>(),\n\tAppBarOwner, View.OnClickListener,\n\tFilterCoordinator.Owner,\n\tAppBarLayout.OnOffsetChangedListener {\n\n\toverride val appBar: AppBarLayout\n\t\tget() = viewBinding.appbar\n\n\toverride val filterCoordinator: FilterCoordinator\n\t\tget() = checkNotNull(findFilterOwner()) {\n\t\t\t\"Cannot find FilterCoordinator.Owner fragment in ${supportFragmentManager.fragments}\"\n\t\t}.filterCoordinator\n\n\tprivate lateinit var source: MangaSource\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityMangaListBinding.inflate(layoutInflater))\n\t\tviewBinding.collapsingToolbarLayout?.let { collapsingToolbarLayout ->\n\t\t\tFadingAppbarMediator(viewBinding.appbar, collapsingToolbarLayout).bind()\n\t\t}\n\t\tval filter = intent.getParcelableExtraCompat<ParcelableMangaListFilter>(AppRouter.KEY_FILTER)?.filter\n\t\tval sortOrder = intent.getSerializableExtraCompat<SortOrder>(AppRouter.KEY_SORT_ORDER)\n\t\tsource = MangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tif (viewBinding.containerFilterHeader != null) {\n\t\t\tviewBinding.appbar.addOnOffsetChangedListener(this)\n\t\t}\n\t\tviewBinding.buttonOrder?.setOnClickListener(this)\n\t\ttitle = source.getTitle(this)\n\t\tinitList(source, filter, sortOrder)\n\t}\n\n\toverride fun isNsfwContent(): Flow<Boolean> = flowOf(source.isNsfw())\n\n\toverride fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {\n\t\tval container = viewBinding.containerFilterHeader ?: return\n\t\tcontainer.background = if (verticalOffset.absoluteValue < appBarLayout.totalScrollRange) {\n\t\t\tcontainer.context.getThemeColor(materialR.attr.backgroundColor).toDrawable()\n\t\t} else {\n\t\t\tviewBinding.collapsingToolbarLayout?.contentScrim\n\t\t}\n\t}\n\n\t/**\n\t * Only for landscape\n\t */\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tviewBinding.cardSide?.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\tmarginEnd = barsInsets.end(v) + resources.getDimensionPixelOffset(R.dimen.side_card_offset)\n\t\t\ttopMargin = barsInsets.top + resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer_double)\n\t\t\tbottomMargin = barsInsets.bottom + resources.getDimensionPixelOffset(R.dimen.side_card_offset)\n\t\t}\n\t\tviewBinding.appbar.updatePaddingRelative(\n\t\t\ttop = barsInsets.top,\n\t\t\tend = if (viewBinding.cardSide == null) barsInsets.end(v) else 0,\n\t\t\tstart = barsInsets.start(v),\n\t\t)\n\t\treturn insets.consumeSystemBarsInsets(v, top = true, end = true)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_order -> router.showFilterSheet()\n\t\t}\n\t}\n\n\tfun showPreview(manga: Manga): Boolean = setSideFragment(\n\t\tPreviewFragment::class.java,\n\t\tbundleOf(AppRouter.KEY_MANGA to ParcelableManga(manga)),\n\t)\n\n\tfun hidePreview() = setSideFragment(FilterSheetFragment::class.java, null)\n\n\tprivate fun initList(source: MangaSource, filter: MangaListFilter?, sortOrder: SortOrder?) {\n\t\tval fm = supportFragmentManager\n\t\tval existingFragment = fm.findFragmentById(R.id.container)\n\t\tif (existingFragment is FilterCoordinator.Owner) {\n\t\t\tinitFilter(existingFragment)\n\t\t} else {\n\t\t\tfm.commit {\n\t\t\t\tsetReorderingAllowed(true)\n\t\t\t\tval fragment = if (source == LocalMangaSource) {\n\t\t\t\t\tLocalListFragment()\n\t\t\t\t} else {\n\t\t\t\t\tRemoteListFragment.newInstance(source)\n\t\t\t\t}\n\t\t\t\treplace(R.id.container, fragment)\n\t\t\t\trunOnCommit { initFilter(fragment) }\n\t\t\t\tif (filter != null || sortOrder != null) {\n\t\t\t\t\trunOnCommit(ApplyFilterRunnable(fragment, filter, sortOrder))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun initFilter(filterOwner: FilterCoordinator.Owner) {\n\t\tif (viewBinding.containerSide != null) {\n\t\t\tif (supportFragmentManager.findFragmentById(R.id.container_side) == null) {\n\t\t\t\tsetSideFragment(FilterSheetFragment::class.java, null)\n\t\t\t}\n\t\t} else if (viewBinding.containerFilterHeader != null) {\n\t\t\tif (supportFragmentManager.findFragmentById(R.id.container_filter_header) == null) {\n\t\t\t\tsupportFragmentManager.commit {\n\t\t\t\t\tsetReorderingAllowed(true)\n\t\t\t\t\treplace(R.id.container_filter_header, FilterHeaderFragment::class.java, null)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tval filter = filterOwner.filterCoordinator\n\t\tval chipSort = viewBinding.buttonOrder\n\t\tif (chipSort != null) {\n\t\t\tval filterBadge = ViewBadge(chipSort, this)\n\t\t\tfilterBadge.setMaxCharacterCount(0)\n\t\t\tfilter.observe().observe(this) { snapshot ->\n\t\t\t\tchipSort.setTextAndVisible(snapshot.sortOrder.titleRes)\n\t\t\t\tfilterBadge.counter = if (snapshot.listFilter.hasNonSearchOptions()) 1 else 0\n\t\t\t}\n\t\t} else {\n\t\t\tfilter.observe().map {\n\t\t\t\tit.listFilter.getSummary()\n\t\t\t}.flowOn(Dispatchers.Default)\n\t\t\t\t.observe(this) {\n\t\t\t\t\tsupportActionBar?.subtitle = it\n\t\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun findFilterOwner(): FilterCoordinator.Owner? {\n\t\treturn supportFragmentManager.findFragmentById(R.id.container) as? FilterCoordinator.Owner\n\t}\n\n\tprivate fun setSideFragment(cls: Class<out Fragment>, args: Bundle?) = if (viewBinding.containerSide != null) {\n\t\tsupportFragmentManager.commit {\n\t\t\tsetReorderingAllowed(true)\n\t\t\treplace(R.id.container_side, cls, args)\n\t\t}\n\t\ttrue\n\t} else {\n\t\tfalse\n\t}\n\n\tprivate class ApplyFilterRunnable(\n\t\tprivate val filterOwner: FilterCoordinator.Owner,\n\t\tprivate val filter: MangaListFilter?,\n\t\tprivate val sortOrder: SortOrder?,\n\t) : Runnable {\n\n\t\toverride fun run() {\n\t\t\tif (sortOrder != null) {\n\t\t\t\tfilterOwner.filterCoordinator.setSortOrder(sortOrder)\n\t\t\t}\n\t\t\tif (filter != null) {\n\t\t\t\tfilterOwner.filterCoordinator.setAdjusted(filter)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaSuggestionsProvider.kt",
    "content": "package org.koitharu.kotatsu.search.ui\n\nimport android.app.SearchManager\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.content.SearchRecentSuggestionsProvider\nimport android.net.Uri\nimport android.provider.SearchRecentSuggestions\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.BuildConfig\n\nclass MangaSuggestionsProvider : SearchRecentSuggestionsProvider() {\n\n\tinit {\n\t\tsetupSuggestions(AUTHORITY, MODE)\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val AUTHORITY = \"${BuildConfig.APPLICATION_ID}.MangaSuggestionsProvider\"\n\t\tprivate const val MODE = DATABASE_MODE_QUERIES\n\n\t\tfun createSuggestions(context: Context): SearchRecentSuggestions {\n\t\t\treturn SearchRecentSuggestions(context, AUTHORITY, MODE)\n\t\t}\n\n\t\tval QUERY_URI: Uri = Uri.Builder()\n\t\t\t.scheme(ContentResolver.SCHEME_CONTENT)\n\t\t\t.authority(AUTHORITY)\n\t\t\t.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY)\n\t\t\t.build()\n\n\t\tval URI: Uri = \"content://$AUTHORITY/suggestions\".toUri()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchActivity.kt",
    "content": "package org.koitharu.kotatsu.search.ui.multi\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.appcompat.view.ActionMode\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.widgets.TipView\nimport org.koitharu.kotatsu.core.util.ShareHelper\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.invalidateNestedItemDecorations\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivitySearchBinding\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.ui.MangaSelectionDecoration\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListListener\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.search.ui.multi.adapter.SearchAdapter\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass SearchActivity :\n\tBaseActivity<ActivitySearchBinding>(),\n\tMangaListListener,\n\tListSelectionController.Callback {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\tprivate val viewModel by viewModels<SearchViewModel>()\n\tprivate lateinit var selectionController: ListSelectionController\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivitySearchBinding.inflate(layoutInflater))\n\t\ttitle = when (viewModel.kind) {\n\t\t\tSearchKind.SIMPLE,\n\t\t\tSearchKind.TITLE -> viewModel.query\n\n\t\t\tSearchKind.AUTHOR -> getString(\n\t\t\t\tR.string.inline_preference_pattern,\n\t\t\t\tgetString(R.string.author),\n\t\t\t\tviewModel.query,\n\t\t\t)\n\n\t\t\tSearchKind.TAG -> getString(R.string.inline_preference_pattern, getString(R.string.genre), viewModel.query)\n\t\t}\n\n\t\tval itemClickListener = OnListItemClickListener<SearchResultsListModel> { item, view ->\n\t\t\tif (item.listFilter == null) {\n\t\t\t\trouter.openSearch(item.source, viewModel.query)\n\t\t\t} else {\n\t\t\t\trouter.openList(item.source, item.listFilter, item.sortOrder)\n\t\t\t}\n\t\t}\n\t\tval sizeResolver = DynamicItemSizeResolver(resources, this, settings, adjustWidth = true)\n\t\tval selectionDecoration = MangaSelectionDecoration(this)\n\t\tselectionController = ListSelectionController(\n\t\t\tappCompatDelegate = delegate,\n\t\t\tdecoration = selectionDecoration,\n\t\t\tregistryOwner = this,\n\t\t\tcallback = this,\n\t\t)\n\t\tval adapter = SearchAdapter(\n\t\t\tlistener = this,\n\t\t\titemClickListener = itemClickListener,\n\t\t\tsizeResolver = sizeResolver,\n\t\t\tselectionDecoration = selectionDecoration,\n\t\t)\n\t\tviewBinding.recyclerView.adapter = adapter\n\t\tviewBinding.recyclerView.setHasFixedSize(true)\n\t\tviewBinding.recyclerView.addItemDecoration(TypedListSpacingDecoration(this, true))\n\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tsupportActionBar?.setSubtitle(R.string.search_results)\n\n\t\taddMenuProvider(SearchMenuProvider(this, viewModel))\n\n\t\tviewModel.list.observe(this, adapter)\n\t\tviewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.toolbar.updatePadding(\n\t\t\ttop = barsInsets.top,\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t)\n\t\tviewBinding.recyclerView.setPadding(\n\t\t\tleft = barsInsets.left,\n\t\t\ttop = 0,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onItemClick(item: MangaListModel, view: View) {\n\t\tif (!selectionController.onItemClick(item.id)) {\n\t\t\trouter.openDetails(item.toMangaWithOverride())\n\t\t}\n\t}\n\n\toverride fun onItemLongClick(item: MangaListModel, view: View): Boolean {\n\t\treturn selectionController.onItemLongClick(view, item.id)\n\t}\n\n\toverride fun onItemContextClick(item: MangaListModel, view: View): Boolean {\n\t\treturn selectionController.onItemContextClick(view, item.id)\n\t}\n\n\toverride fun onReadClick(manga: Manga, view: View) {\n\t\tif (!selectionController.onItemClick(manga.id)) {\n\t\t\trouter.openReader(manga)\n\t\t}\n\t}\n\n\toverride fun onTagClick(manga: Manga, tag: MangaTag, view: View) {\n\t\tif (!selectionController.onItemClick(manga.id)) {\n\t\t\trouter.openList(tag)\n\t\t}\n\t}\n\n\toverride fun onRetryClick(error: Throwable) {\n\t\tviewModel.retry()\n\t}\n\n\toverride fun onFilterOptionClick(option: ListFilterOption) = Unit\n\n\toverride fun onFilterClick(view: View?) = Unit\n\n\toverride fun onEmptyActionClick() = viewModel.continueSearch()\n\n\toverride fun onListHeaderClick(item: ListHeader, view: View) = Unit\n\n\toverride fun onFooterButtonClick() = viewModel.continueSearch()\n\n\toverride fun onPrimaryButtonClick(tipView: TipView) = Unit\n\n\toverride fun onSecondaryButtonClick(tipView: TipView) = Unit\n\n\toverride fun onSelectionChanged(controller: ListSelectionController, count: Int) {\n\t\tviewBinding.recyclerView.invalidateNestedItemDecorations()\n\t}\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_remote, menu)\n\t\treturn true\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_share -> {\n\t\t\t\tShareHelper(this).shareMangaLinks(collectSelectedItems())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_favourite -> {\n\t\t\t\trouter.showFavoriteDialog(collectSelectedItems())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_save -> {\n\t\t\t\trouter.showDownloadDialog(collectSelectedItems(), viewBinding.recyclerView)\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\tprivate fun collectSelectedItems(): Set<Manga> {\n\t\treturn viewModel.getItems(selectionController.peekCheckedIds())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.search.ui.multi\n\nimport android.os.Build\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.search.domain.SearchKind\n\nclass SearchMenuProvider(\n\tprivate val activity: SearchActivity,\n\tprivate val viewModel: SearchViewModel,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_search_kind, menu)\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tmenu.findItem(\n\t\t\twhen (viewModel.kind) {\n\t\t\t\tSearchKind.SIMPLE -> R.id.action_kind_simple\n\t\t\t\tSearchKind.TITLE -> R.id.action_kind_title\n\t\t\t\tSearchKind.AUTHOR -> R.id.action_kind_author\n\t\t\t\tSearchKind.TAG -> R.id.action_kind_tag\n\t\t\t},\n\t\t)?.isChecked = true\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\twhen (menuItem.itemId) {\n\t\t\tR.id.action_filter_pinned_only -> {\n\t\t\t\tmenuItem.isChecked = !menuItem.isChecked\n\t\t\t\tviewModel.setPinnedOnly(menuItem.isChecked)\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tR.id.action_filter_hide_empty -> {\n\t\t\t\tmenuItem.isChecked = !menuItem.isChecked\n\t\t\t\tviewModel.setHideEmpty(menuItem.isChecked)\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\tval newKind = when (menuItem.itemId) {\n\t\t\tR.id.action_kind_simple -> SearchKind.SIMPLE\n\t\t\tR.id.action_kind_title -> SearchKind.TITLE\n\t\t\tR.id.action_kind_author -> SearchKind.AUTHOR\n\t\t\tR.id.action_kind_tag -> SearchKind.TAG\n\t\t\telse -> return false\n\t\t}\n\t\tif (newKind != viewModel.kind) {\n\t\t\tactivity.router.openSearch(\n\t\t\t\tquery = viewModel.query,\n\t\t\t\tkind = newKind,\n\t\t\t)\n\t\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n\t\t\t\tactivity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out, 0)\n\t\t\t} else {\n\t\t\t\tactivity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)\n\t\t\t}\n\t\t\tactivity.finishAfterTransition()\n\t\t}\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchResultsListModel.kt",
    "content": "package org.koitharu.kotatsu.search.ui.multi\n\nimport android.content.Context\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.SortOrder\n\ndata class SearchResultsListModel(\n\t@StringRes val titleResId: Int,\n\tval source: MangaSource,\n\tval listFilter: MangaListFilter?,\n\tval sortOrder: SortOrder?,\n\tval list: List<MangaListModel>,\n\tval error: Throwable?,\n) : ListModel {\n\n\tfun getTitle(context: Context): String = if (titleResId != 0) {\n\t\tcontext.getString(titleResId)\n\t} else {\n\t\tsource.getTitle(context)\n\t}\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is SearchResultsListModel && source == other.source && titleResId == other.titleResId\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\treturn if (previousState is SearchResultsListModel && previousState.list != list) {\n\t\t\tListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchViewModel.kt",
    "content": "package org.koitharu.kotatsu.search.ui.multi\n\nimport androidx.collection.ArraySet\nimport androidx.collection.LongSet\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.dropWhile\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.joinAll\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.LocalMangaSource\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.append\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toLocale\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.ui.model.ButtonFooter\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingFooter\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.search.domain.SearchV2Helper\nimport java.util.Locale\nimport javax.inject.Inject\n\nprivate const val MAX_PARALLELISM = 4\n\n@HiltViewModel\nclass SearchViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val searchHelperFactory: SearchV2Helper.Factory,\n\tprivate val sourcesRepository: MangaSourcesRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val favouritesRepository: FavouritesRepository,\n) : BaseViewModel() {\n\n\tval query = savedStateHandle.get<String>(AppRouter.KEY_QUERY).orEmpty()\n\tval kind = savedStateHandle.get<SearchKind>(AppRouter.KEY_KIND) ?: SearchKind.SIMPLE\n\n\tprivate var includeDisabledSources = MutableStateFlow(false)\n\tprivate var pinnedOnly = MutableStateFlow(false)\n\tprivate var hideEmpty = MutableStateFlow(false)\n\tprivate val results = MutableStateFlow<List<SearchResultsListModel>>(emptyList())\n\n\tprivate var searchJob: Job? = null\n\n\tval list: StateFlow<List<ListModel>> = combine(\n\t\tresults,\n\t\tisLoading.dropWhile { !it },\n\t\tincludeDisabledSources,\n\t\thideEmpty,\n\t) { list, loading, includeDisabled, hideEmptyVal ->\n\t\tval filteredList = if (hideEmptyVal) {\n\t\t\tlist.filter { it.list.isNotEmpty() }\n\t\t} else {\n\t\t\tlist\n\t\t}\n\t\twhen {\n\t\t\tfilteredList.isEmpty() -> listOf(\n\t\t\t\twhen {\n\t\t\t\t\tloading -> LoadingState\n\t\t\t\t\telse -> EmptyState(\n\t\t\t\t\t\ticon = R.drawable.ic_empty_common,\n\t\t\t\t\t\ttextPrimary = R.string.nothing_found,\n\t\t\t\t\t\ttextSecondary = R.string.text_search_holder_secondary,\n\t\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tloading -> filteredList + LoadingFooter()\n\t\t\tincludeDisabled -> filteredList\n\t\t\telse -> filteredList + ButtonFooter(R.string.search_disabled_sources)\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tinit {\n\t\tdoSearch()\n\t}\n\n\tfun getItems(ids: LongSet): Set<Manga> {\n\t\tval snapshot = results.value\n\t\tval result = ArraySet<Manga>(ids.size)\n\t\tsnapshot.forEach { x ->\n\t\t\tfor (item in x.list) {\n\t\t\t\tif (item.id in ids) {\n\t\t\t\t\tresult.add(item.manga)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tfun retry() {\n\t\tsearchJob?.cancel()\n\t\tresults.value = emptyList()\n\t\tincludeDisabledSources.value = false\n\t\tdoSearch()\n\t}\n\n\tfun setPinnedOnly(value: Boolean) {\n\t\tif (pinnedOnly.value != value) {\n\t\t\tpinnedOnly.value = value\n\t\t\tretry()\n\t\t}\n\t}\n\n\tfun setHideEmpty(value: Boolean) {\n\t\thideEmpty.value = value\n\t}\n\n\tfun continueSearch() {\n\t\tif (includeDisabledSources.value) {\n\t\t\treturn\n\t\t}\n\t\tval prevJob = searchJob\n\t\tsearchJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tincludeDisabledSources.value = true\n\t\t\tprevJob?.join()\n\t\t\tval sources = if (pinnedOnly.value) {\n\t\t\t\temptyList()\n\t\t\t} else {\n\t\t\t\tsourcesRepository.getDisabledSources()\n\t\t\t\t\t.sortedByDescending { it.priority() }\n\t\t\t}\n\t\t\tval semaphore = Semaphore(MAX_PARALLELISM)\n\t\t\tsources.map { source ->\n\t\t\t\tlaunch {\n\t\t\t\t\tsemaphore.withPermit {\n\t\t\t\t\t\tappendResult(searchSource(source))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}.joinAll()\n\t\t}\n\t}\n\n\tprivate fun doSearch() {\n\t\tval prevJob = searchJob\n\t\tsearchJob = launchLoadingJob(Dispatchers.Default) {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tappendResult(searchHistory())\n\t\t\tappendResult(searchFavorites())\n\t\t\tappendResult(searchLocal())\n\t\t\tval sources = if (pinnedOnly.value) {\n\t\t\t\tsourcesRepository.getPinnedSources().toList()\n\t\t\t} else {\n\t\t\t\tsourcesRepository.getEnabledSources()\n\t\t\t}\n\t\t\tval semaphore = Semaphore(MAX_PARALLELISM)\n\t\t\tsources.map { source ->\n\t\t\t\tlaunch {\n\t\t\t\t\tsemaphore.withPermit {\n\t\t\t\t\t\tappendResult(searchSource(source))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}.joinAll()\n\t\t}\n\t}\n\n\t// impl\n\n\tprivate suspend fun searchSource(source: MangaSource): SearchResultsListModel? = runCatchingCancellable {\n\t\tval searchHelper = searchHelperFactory.create(source)\n\t\tsearchHelper(query, kind)\n\t}.fold(\n\t\tonSuccess = { result ->\n\t\t\tif (result == null || result.manga.isEmpty()) {\n\t\t\t\tnull\n\t\t\t} else {\n\t\t\t\tval list = mangaListMapper.toListModelList(\n\t\t\t\t\tmanga = result.manga,\n\t\t\t\t\tmode = ListMode.GRID,\n\t\t\t\t)\n\t\t\t\tSearchResultsListModel(\n\t\t\t\t\ttitleResId = 0,\n\t\t\t\t\tsource = source,\n\t\t\t\t\tlist = list,\n\t\t\t\t\terror = null,\n\t\t\t\t\tlistFilter = result.listFilter,\n\t\t\t\t\tsortOrder = result.sortOrder,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t\tonFailure = { error ->\n\t\t\terror.printStackTraceDebug()\n\t\t\tif (source is MangaParserSource && source.isBroken) {\n\t\t\t\tnull\n\t\t\t} else {\n\t\t\t\tSearchResultsListModel(0, source, null, null, emptyList(), error)\n\t\t\t}\n\t\t},\n\t)\n\n\tprivate suspend fun searchHistory(): SearchResultsListModel? = runCatchingCancellable {\n\t\thistoryRepository.search(query, kind, Int.MAX_VALUE)\n\t}.fold(\n\t\tonSuccess = { result ->\n\t\t\tif (result.isNotEmpty()) {\n\t\t\t\tSearchResultsListModel(\n\t\t\t\t\ttitleResId = R.string.history,\n\t\t\t\t\tsource = UnknownMangaSource,\n\t\t\t\t\tlist = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID),\n\t\t\t\t\terror = null,\n\t\t\t\t\tlistFilter = null,\n\t\t\t\t\tsortOrder = null,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t},\n\t\tonFailure = { error ->\n\t\t\tSearchResultsListModel(\n\t\t\t\ttitleResId = R.string.history,\n\t\t\t\tsource = UnknownMangaSource,\n\t\t\t\tlist = emptyList(),\n\t\t\t\terror = error,\n\t\t\t\tlistFilter = null,\n\t\t\t\tsortOrder = null,\n\t\t\t)\n\t\t},\n\t)\n\n\tprivate suspend fun searchFavorites(): SearchResultsListModel? = runCatchingCancellable {\n\t\tfavouritesRepository.search(query, kind, Int.MAX_VALUE)\n\t}.fold(\n\t\tonSuccess = { result ->\n\t\t\tif (result.isNotEmpty()) {\n\t\t\t\tSearchResultsListModel(\n\t\t\t\t\ttitleResId = R.string.favourites,\n\t\t\t\t\tsource = UnknownMangaSource,\n\t\t\t\t\tlist = mangaListMapper.toListModelList(\n\t\t\t\t\t\tmanga = result,\n\t\t\t\t\t\tmode = ListMode.GRID,\n\t\t\t\t\t\tflags = MangaListMapper.NO_FAVORITE,\n\t\t\t\t\t),\n\t\t\t\t\terror = null,\n\t\t\t\t\tlistFilter = null,\n\t\t\t\t\tsortOrder = null,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t},\n\t\tonFailure = { error ->\n\t\t\tSearchResultsListModel(\n\t\t\t\ttitleResId = R.string.favourites,\n\t\t\t\tsource = UnknownMangaSource,\n\t\t\t\tlist = emptyList(),\n\t\t\t\terror = error,\n\t\t\t\tlistFilter = null,\n\t\t\t\tsortOrder = null,\n\t\t\t)\n\t\t},\n\t)\n\n\tprivate suspend fun searchLocal(): SearchResultsListModel? = runCatchingCancellable {\n\t\tsearchHelperFactory.create(LocalMangaSource).invoke(query, kind)\n\t}.fold(\n\t\tonSuccess = { result ->\n\t\t\tif (!result?.manga.isNullOrEmpty()) {\n\t\t\t\tSearchResultsListModel(\n\t\t\t\t\ttitleResId = 0,\n\t\t\t\t\tsource = LocalMangaSource,\n\t\t\t\t\tlist = mangaListMapper.toListModelList(\n\t\t\t\t\t\tmanga = result.manga,\n\t\t\t\t\t\tmode = ListMode.GRID,\n\t\t\t\t\t\tflags = MangaListMapper.NO_SAVED,\n\t\t\t\t\t),\n\t\t\t\t\terror = null,\n\t\t\t\t\tlistFilter = result.listFilter,\n\t\t\t\t\tsortOrder = result.sortOrder,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t},\n\t\tonFailure = { error ->\n\t\t\tSearchResultsListModel(\n\t\t\t\ttitleResId = 0,\n\t\t\t\tsource = LocalMangaSource,\n\t\t\t\tlist = emptyList(),\n\t\t\t\terror = error,\n\t\t\t\tlistFilter = null,\n\t\t\t\tsortOrder = null,\n\t\t\t)\n\t\t},\n\t)\n\n\tprivate fun appendResult(item: SearchResultsListModel?) {\n\t\tif (item != null) {\n\t\t\tresults.append(item)\n\t\t}\n\t}\n\n\tprivate fun MangaSource.priority(): Int {\n\t\tvar res = 0\n\t\tif (this is MangaParserSource) {\n\t\t\tif (locale.toLocale() == Locale.getDefault()) res += 2\n\t\t}\n\t\treturn res\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchAdapter.kt",
    "content": "package org.koitharu.kotatsu.search.ui.multi.adapter\n\nimport android.content.Context\nimport androidx.recyclerview.widget.RecyclerView.RecycledViewPool\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.list.ui.MangaSelectionDecoration\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListListener\nimport org.koitharu.kotatsu.list.ui.adapter.buttonFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.errorStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.size.ItemSizeResolver\nimport org.koitharu.kotatsu.search.ui.multi.SearchResultsListModel\n\nclass SearchAdapter(\n\tlistener: MangaListListener,\n\titemClickListener: OnListItemClickListener<SearchResultsListModel>,\n\tsizeResolver: ItemSizeResolver,\n\tselectionDecoration: MangaSelectionDecoration,\n) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {\n\n\tinit {\n\t\tval pool = RecycledViewPool()\n\t\taddDelegate(\n\t\t\tListItemType.MANGA_NESTED_GROUP,\n\t\t\tsearchResultsAD(\n\t\t\t\tsharedPool = pool,\n\t\t\t\tsizeResolver = sizeResolver,\n\t\t\t\tselectionDecoration = selectionDecoration,\n\t\t\t\tlistener = listener,\n\t\t\t\titemClickListener = itemClickListener,\n\t\t\t),\n\t\t)\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listener))\n\t\taddDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener))\n\t\taddDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(listener))\n\t}\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn (items.getOrNull(position) as? SearchResultsListModel)?.getTitle(context)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.multi.adapter\n\nimport android.annotation.SuppressLint\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.RecyclerView.RecycledViewPool\nimport com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.UnknownMangaSource\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemListGroupBinding\nimport org.koitharu.kotatsu.list.ui.MangaSelectionDecoration\nimport org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.size.ItemSizeResolver\nimport org.koitharu.kotatsu.search.ui.multi.SearchResultsListModel\n\n@SuppressLint(\"NotifyDataSetChanged\")\nfun searchResultsAD(\n\tsharedPool: RecycledViewPool,\n\tsizeResolver: ItemSizeResolver,\n\tselectionDecoration: MangaSelectionDecoration,\n\tlistener: OnListItemClickListener<MangaListModel>,\n\titemClickListener: OnListItemClickListener<SearchResultsListModel>,\n) = adapterDelegateViewBinding<SearchResultsListModel, ListModel, ItemListGroupBinding>(\n\t{ layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.recyclerView.setRecycledViewPool(sharedPool)\n\tval adapter = ListDelegationAdapter(mangaGridItemAD(sizeResolver, listener))\n\tbinding.recyclerView.addItemDecoration(selectionDecoration)\n\tbinding.recyclerView.adapter = adapter\n\tval spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer)\n\tbinding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing, withBottomPadding = true))\n\tval eventListener = AdapterDelegateClickListenerAdapter(this, itemClickListener)\n\tbinding.buttonMore.setOnClickListener(eventListener)\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.getTitle(context)\n\t\tbinding.buttonMore.isVisible = item.source !== UnknownMangaSource\n\t\tadapter.items = item.list\n\t\tadapter.notifyDataSetChanged()\n\t\tbinding.recyclerView.isGone = item.list.isEmpty()\n\t\tbinding.textViewError.textAndVisible = item.error?.getDisplayMessage(context.resources)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion\n\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.search.ui.suggestion.adapter.SEARCH_SUGGESTION_ITEM_TYPE_QUERY\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nclass SearchSuggestionItemCallback(\n\tprivate val listener: SuggestionItemListener,\n) : ItemTouchHelper.Callback() {\n\n\tprivate val movementFlags = makeMovementFlags(\n\t\t0,\n\t\tItemTouchHelper.LEFT or ItemTouchHelper.RIGHT,\n\t)\n\n\toverride fun getMovementFlags(\n\t\trecyclerView: RecyclerView,\n\t\tviewHolder: RecyclerView.ViewHolder,\n\t): Int = if (viewHolder.itemViewType == SEARCH_SUGGESTION_ITEM_TYPE_QUERY) {\n\t\tmovementFlags\n\t} else {\n\t\t0\n\t}\n\n\toverride fun onMove(\n\t\trecyclerView: RecyclerView,\n\t\tviewHolder: RecyclerView.ViewHolder,\n\t\ttarget: RecyclerView.ViewHolder,\n\t): Boolean = false\n\n\toverride fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n\t\tval item = viewHolder.getItem(SearchSuggestionItem.RecentQuery::class.java) ?: return\n\t\tlistener.onRemoveQuery(item.query)\n\t}\n\n\tinterface SuggestionItemListener {\n\n\t\tfun onRemoveQuery(query: String)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion\n\nimport android.text.TextWatcher\nimport android.widget.TextView\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.search.domain.SearchKind\n\ninterface SearchSuggestionListener : TextWatcher, TextView.OnEditorActionListener {\n\n\tfun onMangaClick(manga: Manga)\n\n\tfun onQueryClick(query: String, kind: SearchKind, submit: Boolean)\n\n\tfun onSourceToggle(source: MangaSource, isEnabled: Boolean)\n\n\tfun onSourceClick(source: MangaSource)\n\n\tfun onTagClick(tag: MangaTag)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListenerImpl.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion\n\nimport android.text.Editable\nimport android.view.KeyEvent\nimport android.widget.TextView\nimport androidx.core.net.toUri\nimport com.google.android.material.search.SearchView\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaLinkResolver\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.search.domain.SearchKind\n\nclass SearchSuggestionListenerImpl(\n\tprivate val router: AppRouter,\n\tprivate val searchView: SearchView,\n\tprivate val viewModel: SearchSuggestionViewModel,\n) : SearchSuggestionListener {\n\n\toverride fun onMangaClick(manga: Manga) {\n\t\trouter.openDetails(manga)\n\t}\n\n\toverride fun onQueryClick(query: String, kind: SearchKind, submit: Boolean) {\n\t\tif (submit && query.isNotEmpty()) {\n\t\t\tif (kind == SearchKind.SIMPLE && MangaLinkResolver.isValidLink(query)) {\n\t\t\t\trouter.openDetails(query.toUri())\n\t\t\t} else {\n\t\t\t\trouter.openSearch(query, kind)\n\t\t\t\tif (kind != SearchKind.TAG) {\n\t\t\t\t\tviewModel.saveQuery(query)\n\t\t\t\t}\n\t\t\t}\n\t\t\tsearchView.hide()\n\t\t} else {\n\t\t\tsearchView.setText(query)\n\t\t}\n\t}\n\n\toverride fun onTagClick(tag: MangaTag) {\n\t\trouter.openSearch(tag.title, SearchKind.TAG)\n\t}\n\n\toverride fun onSourceToggle(source: MangaSource, isEnabled: Boolean) {\n\t\tviewModel.onSourceToggle(source, isEnabled)\n\t}\n\n\toverride fun onSourceClick(source: MangaSource) {\n\t\trouter.openList(source, null, null)\n\t}\n\n\toverride fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit\n\n\toverride fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit\n\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tviewModel.onQueryChanged(s?.toString().orEmpty())\n\t}\n\n\toverride fun onEditorAction(\n\t\tv: TextView?,\n\t\tactionId: Int,\n\t\tevent: KeyEvent?\n\t): Boolean {\n\t\tval query = v?.text?.toString()\n\t\tif (query.isNullOrEmpty()) {\n\t\t\treturn false\n\t\t}\n\t\tonQueryClick(query, SearchKind.SIMPLE, true)\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion\n\nimport android.content.Context\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.util.ext.resolve\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\n\nclass SearchSuggestionMenuProvider(\n\tprivate val context: Context,\n\tprivate val voiceInputLauncher: ActivityResultLauncher<String?>,\n\tprivate val viewModel: SearchSuggestionViewModel,\n) : MenuProvider {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_search_suggestion, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\treturn when (menuItem.itemId) {\n\t\t\tR.id.action_clear -> {\n\t\t\t\tclearSearchHistory()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_voice_search -> {\n\t\t\t\tvoiceInputLauncher.tryLaunch(context.getString(R.string.search_manga), null)\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tmenu.findItem(R.id.action_voice_search)?.isVisible = voiceInputLauncher.resolve(context, null) != null\n\t}\n\n\tprivate fun clearSearchHistory() {\n\t\tbuildAlertDialog(context, isCentered = true) {\n\t\t\tsetTitle(R.string.clear_search_history)\n\t\t\tsetIcon(R.drawable.ic_clear_all)\n\t\t\tsetCancelable(true)\n\t\t\tsetMessage(R.string.text_clear_search_history_prompt)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.clear) { _, _ -> viewModel.clearSearchHistory() }\n\t\t}.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.mapLatest\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.SearchSuggestionType\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.search.domain.MangaSearchRepository\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\nimport javax.inject.Inject\n\nprivate const val DEBOUNCE_TIMEOUT = 300L\nprivate const val MAX_MANGA_ITEMS = 12\nprivate const val MAX_QUERY_ITEMS = 16\nprivate const val MAX_HINTS_ITEMS = 3\nprivate const val MAX_AUTHORS_ITEMS = 2\nprivate const val MAX_TAGS_ITEMS = 8\nprivate const val MAX_SOURCES_ITEMS = 6\nprivate const val MAX_SOURCES_TIPS_ITEMS = 2\n\n@HiltViewModel\nclass SearchSuggestionViewModel @Inject constructor(\n\tprivate val repository: MangaSearchRepository,\n\tprivate val settings: AppSettings,\n\tprivate val sourcesRepository: MangaSourcesRepository,\n) : BaseViewModel() {\n\n\tprivate val query = MutableStateFlow(\"\")\n\tprivate val invalidationTrigger = MutableStateFlow(0)\n\n\tval isIncognitoModeEnabled = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_INCOGNITO_MODE,\n\t\tvalueProducer = { isIncognitoModeEnabled },\n\t)\n\n\tval suggestion: Flow<List<SearchSuggestionItem>> = combine(\n\t\tquery.debounce(DEBOUNCE_TIMEOUT),\n\t\tsourcesRepository.observeEnabledSources().map { it.mapToSet { x -> x.name } },\n\t\tsettings.observeAsFlow(AppSettings.KEY_SEARCH_SUGGESTION_TYPES) { searchSuggestionTypes },\n\t\tinvalidationTrigger,\n\t)\n\t{ a, b, c, _ ->\n\t\tTriple(a, b, c)\n\t}.mapLatest { (searchQuery, enabledSources, types) ->\n\t\tbuildSearchSuggestion(searchQuery, enabledSources, types)\n\t}.distinctUntilChanged()\n\t\t.withErrorHandling()\n\t\t.flowOn(Dispatchers.Default)\n\n\tfun onQueryChanged(newQuery: String) {\n\t\tquery.value = newQuery\n\t}\n\n\tfun saveQuery(query: String) {\n\t\tif (!settings.isIncognitoModeEnabled) {\n\t\t\trepository.saveSearchQuery(query)\n\t\t\tinvalidationTrigger.value++\n\t\t}\n\t}\n\n\tfun clearSearchHistory() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.clearSearchHistory()\n\t\t\tinvalidationTrigger.value++\n\t\t}\n\t}\n\n\tfun onSourceToggle(source: MangaSource, isEnabled: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tsourcesRepository.setSourcesEnabled(setOf(source), isEnabled)\n\t\t}\n\t}\n\n\tfun deleteQuery(query: String) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.deleteSearchQuery(query)\n\t\t\tinvalidationTrigger.value++\n\t\t}\n\t}\n\n\tprivate suspend fun buildSearchSuggestion(\n\t\tsearchQuery: String,\n\t\tenabledSources: Set<String>,\n\t\ttypes: Set<SearchSuggestionType>,\n\t): List<SearchSuggestionItem> = coroutineScope {\n\t\tlistOfNotNull(\n\t\t\tif (SearchSuggestionType.GENRES in types) {\n\t\t\t\tasync { getTags(searchQuery) }\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tif (SearchSuggestionType.MANGA in types) {\n\t\t\t\tasync { getManga(searchQuery) }\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tif (SearchSuggestionType.QUERIES_RECENT in types) {\n\t\t\t\tasync { getRecentQueries(searchQuery) }\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tif (SearchSuggestionType.QUERIES_SUGGEST in types) {\n\t\t\t\tasync { getQueryHints(searchQuery) }\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tif (SearchSuggestionType.SOURCES in types) {\n\t\t\t\tasync { getSources(searchQuery, enabledSources) }\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tif (SearchSuggestionType.RECENT_SOURCES in types) {\n\t\t\t\tasync { getRecentSources(searchQuery) }\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t\tif (SearchSuggestionType.AUTHORS in types) {\n\t\t\t\tasync {\n\t\t\t\t\tgetAuthors(searchQuery)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t},\n\t\t).flatMap { it.await() }\n\t}\n\n\tprivate suspend fun getAuthors(searchQuery: String): List<SearchSuggestionItem> = runCatchingCancellable {\n\t\trepository.getAuthorsSuggestion(searchQuery, MAX_AUTHORS_ITEMS)\n\t\t\t.map { SearchSuggestionItem.Author(it) }\n\t}.getOrElse { e ->\n\t\te.printStackTraceDebug()\n\t\tlistOf(SearchSuggestionItem.Text(0, e))\n\t}\n\n\tprivate suspend fun getQueryHints(searchQuery: String): List<SearchSuggestionItem> = runCatchingCancellable {\n\t\trepository.getQueryHintSuggestion(searchQuery, MAX_HINTS_ITEMS)\n\t\t\t.map { SearchSuggestionItem.Hint(it) }\n\t}.getOrElse { e ->\n\t\te.printStackTraceDebug()\n\t\tlistOf(SearchSuggestionItem.Text(0, e))\n\t}\n\n\tprivate suspend fun getRecentQueries(searchQuery: String): List<SearchSuggestionItem> = runCatchingCancellable {\n\t\trepository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS)\n\t\t\t.map { SearchSuggestionItem.RecentQuery(it) }\n\t}.getOrElse { e ->\n\t\te.printStackTraceDebug()\n\t\tlistOf(SearchSuggestionItem.Text(0, e))\n\t}\n\n\tprivate suspend fun getTags(searchQuery: String): List<SearchSuggestionItem> = runCatchingCancellable {\n\t\tval tags = repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, null)\n\t\tif (tags.isEmpty()) {\n\t\t\temptyList()\n\t\t} else {\n\t\t\tlistOf(SearchSuggestionItem.Tags(mapTags(tags)))\n\t\t}\n\t}.getOrElse { e ->\n\t\te.printStackTraceDebug()\n\t\tlistOf(SearchSuggestionItem.Text(0, e))\n\t}\n\n\tprivate suspend fun getManga(searchQuery: String): List<SearchSuggestionItem> = runCatchingCancellable {\n\t\tval manga = repository.getMangaSuggestion(searchQuery, MAX_MANGA_ITEMS, null)\n\t\tif (manga.isEmpty()) {\n\t\t\temptyList()\n\t\t} else {\n\t\t\tlistOf(SearchSuggestionItem.MangaList(manga))\n\t\t}\n\t}.getOrElse { e ->\n\t\te.printStackTraceDebug()\n\t\tlistOf(SearchSuggestionItem.Text(0, e))\n\t}\n\n\tprivate fun getSources(searchQuery: String, enabledSources: Set<String>): List<SearchSuggestionItem> =\n\t\trunCatchingCancellable {\n\t\t\trepository.getSourcesSuggestion(searchQuery, MAX_SOURCES_ITEMS)\n\t\t\t\t.map { SearchSuggestionItem.Source(it, it.name in enabledSources) }\n\t\t}.getOrElse { e ->\n\t\t\te.printStackTraceDebug()\n\t\t\tlistOf(SearchSuggestionItem.Text(0, e))\n\t\t}\n\n\tprivate suspend fun getRecentSources(searchQuery: String): List<SearchSuggestionItem> = if (searchQuery.isEmpty()) {\n\t\trunCatchingCancellable {\n\t\t\trepository.getSourcesSuggestion(MAX_SOURCES_TIPS_ITEMS)\n\t\t\t\t.map { SearchSuggestionItem.SourceTip(it) }\n\t\t}.getOrElse { e ->\n\t\t\te.printStackTraceDebug()\n\t\t\tlistOf(SearchSuggestionItem.Text(0, e))\n\t\t}\n\t} else {\n\t\temptyList()\n\t}\n\n\tprivate fun mapTags(tags: List<MangaTag>): List<ChipsView.ChipModel> = tags.map { tag ->\n\t\tChipsView.ChipModel(\n\t\t\ttitle = tag.title,\n\t\t\tdata = tag,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nconst val SEARCH_SUGGESTION_ITEM_TYPE_QUERY = 0\n\nclass SearchSuggestionAdapter(\n\tlistener: SearchSuggestionListener,\n) : BaseListAdapter<SearchSuggestionItem>() {\n\n\tinit {\n\t\tdelegatesManager\n\t\t\t.addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))\n\t\t\t.addDelegate(searchSuggestionSourceAD(listener))\n\t\t\t.addDelegate(searchSuggestionSourceTipAD(listener))\n\t\t\t.addDelegate(searchSuggestionTagsAD(listener))\n\t\t\t.addDelegate(searchSuggestionMangaListAD(listener))\n\t\t\t.addDelegate(searchSuggestionQueryHintAD(listener))\n\t\t\t.addDelegate(searchSuggestionAuthorAD(listener))\n\t\t\t.addDelegate(searchSuggestionTextAD())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAuthorAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport android.view.View\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.databinding.ItemSearchSuggestionQueryHintBinding\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionAuthorAD(\n\tlistener: SearchSuggestionListener,\n) = adapterDelegateViewBinding<SearchSuggestionItem.Author, SearchSuggestionItem, ItemSearchSuggestionQueryHintBinding>(\n\t{ inflater, parent -> ItemSearchSuggestionQueryHintBinding.inflate(inflater, parent, false) },\n) {\n\n\tval viewClickListener = View.OnClickListener { _ ->\n\t\tlistener.onQueryClick(item.name, SearchKind.AUTHOR, true)\n\t}\n\n\tbinding.root.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_user, 0, 0, 0)\n\tbinding.root.setOnClickListener(viewClickListener)\n\n\tbind {\n\t\tbinding.root.text = item.name\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionQueryAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport android.view.View\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.databinding.ItemSearchSuggestionQueryBinding\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionQueryAD(\n\tlistener: SearchSuggestionListener,\n) =\n\tadapterDelegateViewBinding<SearchSuggestionItem.RecentQuery, SearchSuggestionItem, ItemSearchSuggestionQueryBinding>(\n\t\t{ inflater, parent -> ItemSearchSuggestionQueryBinding.inflate(inflater, parent, false) },\n\t) {\n\n\t\tval viewClickListener = View.OnClickListener { v ->\n\t\t\tlistener.onQueryClick(item.query, SearchKind.SIMPLE, v.id != R.id.button_complete)\n\t\t}\n\n\t\tbinding.root.setOnClickListener(viewClickListener)\n\t\tbinding.buttonComplete.setOnClickListener(viewClickListener)\n\n\t\tbind {\n\t\t\tbinding.textViewTitle.text = item.query\n\t\t}\n\t}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionQueryHintAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport android.view.View\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.databinding.ItemSearchSuggestionQueryHintBinding\nimport org.koitharu.kotatsu.search.domain.SearchKind\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionQueryHintAD(\n\tlistener: SearchSuggestionListener,\n) = adapterDelegateViewBinding<SearchSuggestionItem.Hint, SearchSuggestionItem, ItemSearchSuggestionQueryHintBinding>(\n\t{ inflater, parent -> ItemSearchSuggestionQueryHintBinding.inflate(inflater, parent, false) },\n) {\n\n\tval viewClickListener = View.OnClickListener { _ ->\n\t\tlistener.onQueryClick(item.query, SearchKind.SIMPLE, true)\n\t}\n\n\tbinding.root.setOnClickListener(viewClickListener)\n\n\tbind {\n\t\tbinding.root.text = item.query\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.model.getSummary\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceBinding\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionSourceAD(\n\tlistener: SearchSuggestionListener,\n) = adapterDelegateViewBinding<SearchSuggestionItem.Source, SearchSuggestionItem, ItemSearchSuggestionSourceBinding>(\n\t{ inflater, parent -> ItemSearchSuggestionSourceBinding.inflate(inflater, parent, false) },\n) {\n\n\tbinding.switchLocal.setOnCheckedChangeListener { _, isChecked ->\n\t\tlistener.onSourceToggle(item.source, isChecked)\n\t}\n\tbinding.root.setOnClickListener {\n\t\tlistener.onSourceClick(item.source)\n\t}\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.source.getTitle(context)\n\t\tbinding.textViewSubtitle.text = item.source.getSummary(context)\n\t\tbinding.switchLocal.isChecked = item.isEnabled\n\t\tbinding.imageViewCover.setImageAsync(item.source)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.model.getSummary\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceTipBinding\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionSourceTipAD(\n\tlistener: SearchSuggestionListener,\n) =\n\tadapterDelegateViewBinding<SearchSuggestionItem.SourceTip, SearchSuggestionItem, ItemSearchSuggestionSourceTipBinding>(\n\t\t{ inflater, parent -> ItemSearchSuggestionSourceTipBinding.inflate(inflater, parent, false) },\n\t) {\n\n\t\tbinding.root.setOnClickListener {\n\t\t\tlistener.onSourceClick(item.source)\n\t\t}\n\n\t\tbind {\n\t\t\tbinding.textViewTitle.text = item.source.getTitle(context)\n\t\t\tbinding.textViewSubtitle.text = item.source.getSummary(context)\n\t\t\tbinding.imageViewCover.setImageAsync(item.source)\n\t\t}\n\t}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.databinding.ItemSearchSuggestionTagsBinding\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionTagsAD(\n\tlistener: SearchSuggestionListener,\n) = adapterDelegateViewBinding<SearchSuggestionItem.Tags, SearchSuggestionItem, ItemSearchSuggestionTagsBinding>(\n\t{ layoutInflater, parent -> ItemSearchSuggestionTagsBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.chipsGenres.onChipClickListener = ChipsView.OnChipClickListener { _, data ->\n\t\tlistener.onTagClick(data as? MangaTag ?: return@OnChipClickListener)\n\t}\n\n\tbind {\n\t\tbinding.chipsGenres.setChips(item.tags)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTextAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport android.widget.TextView\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionTextAD() = adapterDelegate<SearchSuggestionItem.Text, SearchSuggestionItem>(\n\tR.layout.item_search_suggestion_text,\n) {\n\n\tbind {\n\t\tval tv = itemView as TextView\n\t\tval isError = item.error != null\n\t\ttv.setCompoundDrawablesRelativeWithIntrinsicBounds(\n\t\t\tif (isError) R.drawable.ic_error_small else 0,\n\t\t\t0,\n\t\t\t0,\n\t\t\t0,\n\t\t)\n\t\tif (item.textResId != 0) {\n\t\t\ttv.setText(item.textResId)\n\t\t} else {\n\t\t\ttv.text = item.error?.getDisplayMessage(tv.resources)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.adapter\n\nimport androidx.core.view.updatePadding\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration\nimport org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaGridBinding\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener\nimport org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem\n\nfun searchSuggestionMangaListAD(\n\tlistener: SearchSuggestionListener,\n) = adapterDelegate<SearchSuggestionItem.MangaList, SearchSuggestionItem>(R.layout.item_search_suggestion_manga_list) {\n\tval adapter = AsyncListDifferDelegationAdapter(\n\t\tSuggestionMangaDiffCallback(),\n\t\tsearchSuggestionMangaGridAD(listener),\n\t)\n\tval recyclerView = itemView as RecyclerView\n\trecyclerView.adapter = adapter\n\tval spacing = context.resources.getDimensionPixelOffset(R.dimen.search_suggestions_manga_spacing)\n\trecyclerView.updatePadding(\n\t\tleft = recyclerView.paddingLeft - spacing,\n\t\tright = recyclerView.paddingRight - spacing,\n\t)\n\trecyclerView.addItemDecoration(SpacingItemDecoration(spacing, withBottomPadding = true))\n\tval scrollResetCallback = RecyclerViewScrollCallback(recyclerView, 0, 0)\n\n\tbind {\n\t\tadapter.setItems(item.items, scrollResetCallback)\n\t}\n}\n\nprivate fun searchSuggestionMangaGridAD(\n\tlistener: SearchSuggestionListener,\n) = adapterDelegateViewBinding<Manga, Manga, ItemSearchSuggestionMangaGridBinding>(\n\t{ layoutInflater, parent -> ItemSearchSuggestionMangaGridBinding.inflate(layoutInflater, parent, false) },\n) {\n\titemView.setOnClickListener {\n\t\tlistener.onMangaClick(item)\n\t}\n\n\tbind {\n\t\titemView.setTooltipCompat(item.title)\n\t\tbinding.imageViewCover.setImageAsync(item.coverUrl, item.source)\n\t\tbinding.textViewTitle.text = item.title\n\t}\n}\n\nprivate class SuggestionMangaDiffCallback : DiffUtil.ItemCallback<Manga>() {\n\n\toverride fun areItemsTheSame(oldItem: Manga, newItem: Manga): Boolean {\n\t\treturn oldItem.id == newItem.id\n\t}\n\n\toverride fun areContentsTheSame(oldItem: Manga, newItem: Manga): Boolean {\n\t\treturn oldItem.title == newItem.title && oldItem.coverUrl == newItem.coverUrl\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt",
    "content": "package org.koitharu.kotatsu.search.ui.suggestion.model\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nsealed interface SearchSuggestionItem : ListModel {\n\n\tdata class MangaList(\n\t\tval items: List<Manga>,\n\t) : SearchSuggestionItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is MangaList\n\t\t}\n\t}\n\n\tdata class RecentQuery(\n\t\tval query: String,\n\t) : SearchSuggestionItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is RecentQuery && query == other.query\n\t\t}\n\t}\n\n\tdata class Hint(\n\t\tval query: String,\n\t) : SearchSuggestionItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is Hint && query == other.query\n\t\t}\n\t}\n\n\tdata class Author(\n\t\tval name: String,\n\t) : SearchSuggestionItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is Author && name == other.name\n\t\t}\n\t}\n\n\tdata class Source(\n\t\tval source: MangaSource,\n\t\tval isEnabled: Boolean,\n\t) : SearchSuggestionItem {\n\n\t\tval isNsfw: Boolean\n\t\t\tget() = source.isNsfw()\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is Source && other.source.name == source.name\n\t\t}\n\n\t\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\t\tif (previousState !is Source) {\n\t\t\t\treturn super.getChangePayload(previousState)\n\t\t\t}\n\t\t\treturn if (isEnabled != previousState.isEnabled) {\n\t\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t}\n\t}\n\n\tdata class SourceTip(\n\t\tval source: MangaSource,\n\t) : SearchSuggestionItem {\n\n\t\tval isNsfw: Boolean\n\t\t\tget() = source.isNsfw()\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is SourceTip && other.source.name == source.name\n\t\t}\n\t}\n\n\tdata class Tags(\n\t\tval tags: List<ChipsView.ChipModel>,\n\t) : SearchSuggestionItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is Tags\n\t\t}\n\n\t\toverride fun getChangePayload(previousState: ListModel): Any {\n\t\t\treturn ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED\n\t\t}\n\t}\n\n\tdata class Text(\n\t\t@StringRes val textResId: Int,\n\t\tval error: Throwable?,\n\t) : SearchSuggestionItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean = other is Text\n\t\t\t&& textResId == other.textResId\n\t\t\t&& error?.javaClass == other.error?.javaClass\n\t\t\t&& error?.message == other.error?.message\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.provider.Settings\nimport android.view.View\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.preference.ListPreference\nimport androidx.preference.MultiSelectListPreference\nimport androidx.preference.Preference\nimport androidx.preference.TwoStatePreference\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.prefs.ProgressIndicatorMode\nimport org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy\nimport org.koitharu.kotatsu.core.prefs.SearchSuggestionType\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle\nimport org.koitharu.kotatsu.core.util.LocaleComparator\nimport org.koitharu.kotatsu.core.util.ext.getLocalesConfig\nimport org.koitharu.kotatsu.core.util.ext.postDelayed\nimport org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat\nimport org.koitharu.kotatsu.core.util.ext.sortedWithSafe\nimport org.koitharu.kotatsu.core.util.ext.toList\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.names\nimport org.koitharu.kotatsu.parsers.util.toTitleCase\nimport org.koitharu.kotatsu.settings.protect.ProtectSetupActivity\nimport org.koitharu.kotatsu.settings.utils.ActivityListPreference\nimport org.koitharu.kotatsu.settings.utils.MultiSummaryProvider\nimport org.koitharu.kotatsu.settings.utils.PercentSummaryProvider\nimport org.koitharu.kotatsu.settings.utils.SliderPreference\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass AppearanceSettingsFragment :\n    BasePreferenceFragment(R.string.appearance),\n    SharedPreferences.OnSharedPreferenceChangeListener {\n\n    @Inject\n    lateinit var activityRecreationHandle: ActivityRecreationHandle\n\n    @Inject\n    lateinit var appShortcutManager: AppShortcutManager\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_appearance)\n        findPreference<SliderPreference>(AppSettings.KEY_GRID_SIZE)?.summaryProvider = PercentSummaryProvider()\n        findPreference<ListPreference>(AppSettings.KEY_LIST_MODE)?.run {\n            entryValues = ListMode.entries.names()\n            setDefaultValueCompat(ListMode.GRID.name)\n        }\n        findPreference<ListPreference>(AppSettings.KEY_PROGRESS_INDICATORS)?.run {\n            entryValues = ProgressIndicatorMode.entries.names()\n            setDefaultValueCompat(ProgressIndicatorMode.PERCENT_READ.name)\n        }\n        findPreference<ActivityListPreference>(AppSettings.KEY_APP_LOCALE)?.run {\n            initLocalePicker(this)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                activityIntent = Intent(\n                    Settings.ACTION_APP_LOCALE_SETTINGS,\n                    Uri.fromParts(\"package\", context.packageName, null),\n                )\n            }\n            summaryProvider = Preference.SummaryProvider<ActivityListPreference> {\n                val locale = AppCompatDelegate.getApplicationLocales().get(0)\n                locale?.getDisplayName(locale)?.toTitleCase(locale) ?: getString(R.string.follow_system)\n            }\n            setDefaultValueCompat(\"\")\n        }\n        findPreference<MultiSelectListPreference>(AppSettings.KEY_MANGA_LIST_BADGES)?.run {\n            summaryProvider = MultiSummaryProvider(R.string.none)\n        }\n        findPreference<Preference>(AppSettings.KEY_SHORTCUTS)?.isVisible =\n            appShortcutManager.isDynamicShortcutsAvailable()\n        findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)\n            ?.isChecked = !settings.appPassword.isNullOrEmpty()\n        findPreference<ListPreference>(AppSettings.KEY_SCREENSHOTS_POLICY)?.run {\n            entryValues = ScreenshotsPolicy.entries.names()\n            setDefaultValueCompat(ScreenshotsPolicy.ALLOW.name)\n        }\n        findPreference<MultiSelectListPreference>(AppSettings.KEY_SEARCH_SUGGESTION_TYPES)?.let { pref ->\n            pref.entryValues = SearchSuggestionType.entries.names()\n            pref.entries = SearchSuggestionType.entries.map { pref.context.getString(it.titleResId) }.toTypedArray()\n            pref.summaryProvider = MultiSummaryProvider(R.string.none)\n            pref.values = settings.searchSuggestionTypes.mapToSet { it.name }\n        }\n        bindNavSummary()\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        settings.subscribe(this)\n    }\n\n    override fun onDestroyView() {\n        settings.unsubscribe(this)\n        super.onDestroyView()\n    }\n\n    override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {\n        when (key) {\n            AppSettings.KEY_THEME -> {\n                AppCompatDelegate.setDefaultNightMode(settings.theme)\n            }\n\n            AppSettings.KEY_COLOR_THEME,\n            AppSettings.KEY_THEME_AMOLED,\n                -> {\n                postRestart()\n            }\n\n            AppSettings.KEY_APP_LOCALE -> {\n                AppCompatDelegate.setApplicationLocales(settings.appLocales)\n            }\n\n            AppSettings.KEY_NAV_MAIN -> {\n                bindNavSummary()\n            }\n\n            AppSettings.KEY_APP_PASSWORD -> {\n                findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)\n                    ?.isChecked = !settings.appPassword.isNullOrEmpty()\n            }\n        }\n    }\n\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        return when (preference.key) {\n            AppSettings.KEY_PROTECT_APP -> {\n                val pref = (preference as? TwoStatePreference ?: return false)\n                if (pref.isChecked) {\n                    pref.isChecked = false\n                    startActivity(Intent(preference.context, ProtectSetupActivity::class.java))\n                } else {\n                    settings.appPassword = null\n                }\n                true\n            }\n\n            else -> super.onPreferenceTreeClick(preference)\n        }\n    }\n\n    private fun postRestart() {\n        viewLifecycleOwner.lifecycle.postDelayed(400) {\n            activityRecreationHandle.recreateAll()\n        }\n    }\n\n    private fun initLocalePicker(preference: ListPreference) {\n        val locales = preference.context.getLocalesConfig()\n            .toList()\n            .sortedWithSafe(LocaleComparator())\n        preference.entries = Array(locales.size + 1) { i ->\n            if (i == 0) {\n                getString(R.string.follow_system)\n            } else {\n                val lc = locales[i - 1]\n                lc.getDisplayName(lc).toTitleCase(lc)\n            }\n        }\n        preference.entryValues = Array(locales.size + 1) { i ->\n            if (i == 0) {\n                \"\"\n            } else {\n                locales[i - 1].toLanguageTag()\n            }\n        }\n    }\n\n    private fun bindNavSummary() {\n        val pref = findPreference<Preference>(AppSettings.KEY_NAV_MAIN) ?: return\n        pref.summary = settings.mainNavItems.joinToString {\n            getString(it.title)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/DownloadsSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.preference.ListPreference\nimport androidx.preference.Preference\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.os.OpenDocumentTreeHelper\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.DownloadFormat\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.resolveFile\nimport org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport org.koitharu.kotatsu.parsers.util.names\nimport org.koitharu.kotatsu.settings.utils.DozeHelper\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass DownloadsSettingsFragment :\n\tBasePreferenceFragment(R.string.downloads),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\tprivate val dozeHelper = DozeHelper(this)\n\n\t@Inject\n\tlateinit var storageManager: LocalStorageManager\n\n\t@Inject\n\tlateinit var downloadsScheduler: DownloadWorker.Scheduler\n\n\tprivate val pickFileTreeLauncher = OpenDocumentTreeHelper(this) {\n\t\tif (it != null) onDirectoryPicked(it)\n\t}\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_downloads)\n\t\tfindPreference<ListPreference>(AppSettings.KEY_DOWNLOADS_FORMAT)?.run {\n\t\t\tentryValues = DownloadFormat.entries.names()\n\t\t\tsetDefaultValueCompat(DownloadFormat.AUTOMATIC.name)\n\t\t}\n\t\tfindPreference<ListPreference>(AppSettings.KEY_DOWNLOADS_METERED_NETWORK)?.run {\n\t\t\tentryValues = TriStateOption.entries.names()\n\t\t\tsetDefaultValueCompat(TriStateOption.ASK.name)\n\t\t}\n\t\tdozeHelper.updatePreference()\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tfindPreference<Preference>(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName()\n\t\tfindPreference<Preference>(AppSettings.KEY_LOCAL_MANGA_DIRS)?.bindDirectoriesCount()\n\t\tfindPreference<Preference>(AppSettings.KEY_PAGES_SAVE_DIR)?.bindPagesDirectory()\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_LOCAL_STORAGE -> {\n\t\t\t\tfindPreference<Preference>(key)?.bindStorageName()\n\t\t\t}\n\n\t\t\tAppSettings.KEY_LOCAL_MANGA_DIRS -> {\n\t\t\t\tfindPreference<Preference>(key)?.bindDirectoriesCount()\n\t\t\t}\n\n\t\t\tAppSettings.KEY_DOWNLOADS_METERED_NETWORK -> {\n\t\t\t\tupdateDownloadsConstraints()\n\t\t\t}\n\n\t\t\tAppSettings.KEY_PAGES_SAVE_DIR -> {\n\t\t\t\tfindPreference<Preference>(AppSettings.KEY_PAGES_SAVE_DIR)?.bindPagesDirectory()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tAppSettings.KEY_LOCAL_STORAGE -> {\n\t\t\t\trouter.showDirectorySelectDialog()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_LOCAL_MANGA_DIRS -> {\n\t\t\t\trouter.openDirectoriesSettings()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_IGNORE_DOZE -> {\n\t\t\t\tdozeHelper.startIgnoreDoseActivity()\n\t\t\t}\n\n\t\t\tAppSettings.KEY_PAGES_SAVE_DIR -> {\n\t\t\t\tif (!pickFileTreeLauncher.tryLaunch(settings.getPagesSaveDir(preference.context)?.uri)) {\n\t\t\t\t\tSnackbar.make(\n\t\t\t\t\t\trequireView(), R.string.operation_not_supported, Snackbar.LENGTH_SHORT,\n\t\t\t\t\t).show()\n\t\t\t\t}\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\tprivate fun onDirectoryPicked(uri: Uri) {\n\t\tstorageManager.takePermissions(uri)\n\t\tval doc = DocumentFile.fromTreeUri(requireContext(), uri)?.takeIf {\n\t\t\tit.canWrite()\n\t\t}\n\t\tsettings.setPagesSaveDir(doc?.uri)\n\t}\n\n\tprivate fun Preference.bindStorageName() {\n\t\tviewLifecycleScope.launch {\n\t\t\tval storage = storageManager.getDefaultWriteableDir()\n\t\t\tsummary = if (storage != null) {\n\t\t\t\tstorageManager.getDirectoryDisplayName(storage, isFullPath = true)\n\t\t\t} else {\n\t\t\t\tgetString(R.string.not_available)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun Preference.bindDirectoriesCount() {\n\t\tviewLifecycleScope.launch {\n\t\t\tval dirs = storageManager.getReadableDirs().size\n\t\t\tsummary = resources.getQuantityStringSafe(R.plurals.items, dirs, dirs)\n\t\t}\n\t}\n\n\tprivate fun Preference.bindPagesDirectory() {\n\t\tviewLifecycleScope.launch {\n\t\t\tval df = withContext(Dispatchers.IO) {\n\t\t\t\tsettings.getPagesSaveDir(this@bindPagesDirectory.context)\n\t\t\t}\n\t\t\tsummary = df?.getDisplayPath(this@bindPagesDirectory.context)\n\t\t\t\t?: this@bindPagesDirectory.context.getString(androidx.preference.R.string.not_set)\n\t\t}\n\t}\n\n\tprivate fun updateDownloadsConstraints() {\n\t\tval preference = findPreference<Preference>(AppSettings.KEY_DOWNLOADS_METERED_NETWORK)\n\t\tviewLifecycleScope.launch {\n\t\t\ttry {\n\t\t\t\tpreference?.isEnabled = false\n\t\t\t\twithContext(Dispatchers.Default) {\n\t\t\t\t\tval option = when (settings.allowDownloadOnMeteredNetwork) {\n\t\t\t\t\t\tTriStateOption.ENABLED -> true\n\t\t\t\t\t\tTriStateOption.ASK -> return@withContext\n\t\t\t\t\t\tTriStateOption.DISABLED -> false\n\t\t\t\t\t}\n\t\t\t\t\tdownloadsScheduler.updateConstraints(option)\n\t\t\t\t}\n\t\t\t} catch (e: Exception) {\n\t\t\t\te.printStackTraceDebug()\n\t\t\t} finally {\n\t\t\t\tpreference?.isEnabled = true\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun DocumentFile.getDisplayPath(context: Context): String {\n\t\treturn uri.resolveFile(context)?.path ?: uri.toString()\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/NotificationSettingsLegacyFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.SharedPreferences\nimport android.media.RingtoneManager\nimport android.os.Bundle\nimport android.view.View\nimport androidx.preference.Preference\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.settings.utils.RingtonePickContract\n\nclass NotificationSettingsLegacyFragment :\n\tBasePreferenceFragment(R.string.notifications),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\tprivate val ringtonePickContract = registerForActivityResult(\n\t\tRingtonePickContract(R.string.notification_sound),\n\t) { uri ->\n\t\tsettings.notificationSound = uri ?: return@registerForActivityResult\n\t\tfindPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_SOUND)?.run {\n\t\t\tsummary = RingtoneManager.getRingtone(context, uri)?.getTitle(context)\n\t\t\t\t?: getString(R.string.silent)\n\t\t}\n\t}\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_notifications)\n\t\tfindPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_SOUND)?.run {\n\t\t\tval uri = settings.notificationSound\n\t\t\tsummary = RingtoneManager.getRingtone(context, uri)?.getTitle(context)\n\t\t\t\t?: getString(R.string.silent)\n\t\t}\n\t\tupdateInfo()\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_TRACKER_NOTIFICATIONS -> updateInfo()\n\t\t}\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tAppSettings.KEY_NOTIFICATIONS_SOUND -> {\n\t\t\t\tringtonePickContract.launch(settings.notificationSound)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\tprivate fun updateInfo() {\n\t\tfindPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_INFO)\n\t\t\t?.isVisible = !settings.isTrackerNotificationsEnabled\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/ProxySettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.view.View\nimport android.view.inputmethod.EditorInfo\nimport androidx.preference.EditTextPreference\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceCategory\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.settings.utils.EditTextBindListener\nimport org.koitharu.kotatsu.settings.utils.PasswordSummaryProvider\nimport org.koitharu.kotatsu.settings.utils.validation.DomainValidator\nimport org.koitharu.kotatsu.settings.utils.validation.PortNumberValidator\nimport java.net.Proxy\nimport javax.inject.Inject\nimport kotlin.coroutines.cancellation.CancellationException\n\n@AndroidEntryPoint\nclass ProxySettingsFragment : BasePreferenceFragment(R.string.proxy),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\tprivate var testJob: Job? = null\n\n\t@Inject\n\t@BaseHttpClient\n\tlateinit var okHttpClient: OkHttpClient\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_proxy)\n\t\t@Suppress(\"UsePropertyAccessSyntax\")\n\t\tfindPreference<EditTextPreference>(AppSettings.KEY_PROXY_ADDRESS)?.setOnBindEditTextListener(\n\t\t\tEditTextBindListener(\n\t\t\t\tinputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI,\n\t\t\t\thint = null,\n\t\t\t\tvalidator = DomainValidator(),\n\t\t\t),\n\t\t)\n\t\t@Suppress(\"UsePropertyAccessSyntax\")\n\t\tfindPreference<EditTextPreference>(AppSettings.KEY_PROXY_PORT)?.setOnBindEditTextListener(\n\t\t\tEditTextBindListener(\n\t\t\t\tinputType = EditorInfo.TYPE_CLASS_NUMBER,\n\t\t\t\thint = null,\n\t\t\t\tvalidator = PortNumberValidator(),\n\t\t\t),\n\t\t)\n\t\tfindPreference<EditTextPreference>(AppSettings.KEY_PROXY_PASSWORD)?.let { pref ->\n\t\t\t@Suppress(\"UsePropertyAccessSyntax\")\n\t\t\tpref.setOnBindEditTextListener(\n\t\t\t\tEditTextBindListener(\n\t\t\t\t\tinputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD,\n\t\t\t\t\thint = null,\n\t\t\t\t\tvalidator = null,\n\t\t\t\t),\n\t\t\t)\n\t\t\tpref.summaryProvider = PasswordSummaryProvider()\n\t\t}\n\t\tupdateDependencies()\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {\n\t\tAppSettings.KEY_PROXY_TEST -> {\n\t\t\ttestConnection()\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onPreferenceTreeClick(preference)\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_PROXY_TYPE -> updateDependencies()\n\t\t}\n\t}\n\n\tprivate fun updateDependencies() {\n\t\tval isProxyEnabled = settings.proxyType != Proxy.Type.DIRECT\n\t\tfindPreference<Preference>(AppSettings.KEY_PROXY_ADDRESS)?.isEnabled = isProxyEnabled\n\t\tfindPreference<Preference>(AppSettings.KEY_PROXY_PORT)?.isEnabled = isProxyEnabled\n\t\tfindPreference<PreferenceCategory>(AppSettings.KEY_PROXY_AUTH)?.isEnabled = isProxyEnabled\n\t\tfindPreference<Preference>(AppSettings.KEY_PROXY_LOGIN)?.isEnabled = isProxyEnabled\n\t\tfindPreference<Preference>(AppSettings.KEY_PROXY_PASSWORD)?.isEnabled = isProxyEnabled\n\t\tfindPreference<Preference>(AppSettings.KEY_PROXY_TEST)?.isEnabled = isProxyEnabled && testJob?.isActive != true\n\t}\n\n\tprivate fun testConnection() {\n\t\ttestJob?.cancel()\n\t\ttestJob = viewLifecycleScope.launch {\n\t\t\tval pref = findPreference<Preference>(AppSettings.KEY_PROXY_TEST)\n\t\t\tpref?.run {\n\t\t\t\tsetSummary(R.string.loading_)\n\t\t\t\tisEnabled = false\n\t\t\t}\n\t\t\ttry {\n\t\t\t\twithContext(Dispatchers.Default) {\n\t\t\t\t\tval request = Request.Builder()\n\t\t\t\t\t\t.get()\n\t\t\t\t\t\t.url(\"http://neverssl.com\")\n\t\t\t\t\t\t.build()\n\t\t\t\t\tokHttpClient.newCall(request).await().use { response ->\n\t\t\t\t\t\tcheck(response.isSuccessful) { response.message }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tshowTestResult(null)\n\t\t\t} catch (e: CancellationException) {\n\t\t\t\tthrow e\n\t\t\t} catch (e: Throwable) {\n\t\t\t\te.printStackTraceDebug()\n\t\t\t\tshowTestResult(e)\n\t\t\t} finally {\n\t\t\t\tpref?.run {\n\t\t\t\t\tisEnabled = true\n\t\t\t\t\tsummary = null\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun showTestResult(error: Throwable?) {\n\t\tMaterialAlertDialogBuilder(requireContext())\n\t\t\t.setTitle(R.string.proxy)\n\t\t\t.setMessage(error?.getDisplayMessage(resources) ?: getString(R.string.connection_ok))\n\t\t\t.setPositiveButton(android.R.string.ok, null)\n\t\t\t.setCancelable(true)\n\t\t\t.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.SharedPreferences\nimport android.content.pm.ActivityInfo\nimport android.os.Bundle\nimport android.view.View\nimport androidx.preference.ListPreference\nimport androidx.preference.MultiSelectListPreference\nimport androidx.preference.Preference\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.ZoomMode\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ReaderAnimation\nimport org.koitharu.kotatsu.core.prefs.ReaderBackground\nimport org.koitharu.kotatsu.core.prefs.ReaderControl\nimport org.koitharu.kotatsu.core.prefs.ReaderMode\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.parsers.util.names\nimport org.koitharu.kotatsu.settings.utils.MultiSummaryProvider\nimport org.koitharu.kotatsu.settings.utils.PercentSummaryProvider\nimport org.koitharu.kotatsu.settings.utils.SliderPreference\n\n@AndroidEntryPoint\nclass ReaderSettingsFragment :\n\tBasePreferenceFragment(R.string.reader_settings),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_reader)\n\t\tfindPreference<ListPreference>(AppSettings.KEY_READER_MODE)?.run {\n\t\t\tentryValues = ReaderMode.entries.names()\n\t\t\tsetDefaultValueCompat(ReaderMode.STANDARD.name)\n\t\t}\n\t\tfindPreference<ListPreference>(AppSettings.KEY_READER_ORIENTATION)?.run {\n\t\t\tentryValues = arrayOf(\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED.toString(),\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR.toString(),\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT.toString(),\n\t\t\t\tActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE.toString(),\n\t\t\t)\n\t\t\tsetDefaultValueCompat(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED.toString())\n\t\t}\n\t\tfindPreference<MultiSelectListPreference>(AppSettings.KEY_READER_CONTROLS)?.run {\n\t\t\tentryValues = ReaderControl.entries.names()\n\t\t\tsetDefaultValueCompat(ReaderControl.DEFAULT.mapToSet { it.name })\n\t\t\tsummaryProvider = MultiSummaryProvider(R.string.none)\n\t\t}\n\t\tfindPreference<ListPreference>(AppSettings.KEY_READER_BACKGROUND)?.run {\n\t\t\tentryValues = ReaderBackground.entries.names()\n\t\t\tsetDefaultValueCompat(ReaderBackground.DEFAULT.name)\n\t\t}\n\t\tfindPreference<ListPreference>(AppSettings.KEY_READER_ANIMATION)?.run {\n\t\t\tentryValues = ReaderAnimation.entries.names()\n\t\t\tsetDefaultValueCompat(ReaderAnimation.DEFAULT.name)\n\t\t}\n\t\tfindPreference<ListPreference>(AppSettings.KEY_ZOOM_MODE)?.run {\n\t\t\tentryValues = ZoomMode.entries.names()\n\t\t\tsetDefaultValueCompat(ZoomMode.FIT_CENTER.name)\n\t\t}\n\t\tfindPreference<MultiSelectListPreference>(AppSettings.KEY_READER_CROP)?.run {\n\t\t\tsummaryProvider = MultiSummaryProvider(R.string.disabled)\n\t\t}\n\t\tfindPreference<SliderPreference>(AppSettings.KEY_WEBTOON_ZOOM_OUT)?.summaryProvider = PercentSummaryProvider()\n\t\tupdateReaderModeDependency()\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tAppSettings.KEY_READER_TAP_ACTIONS -> {\n\t\t\t\trouter.openReaderTapGridSettings()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_READER_MODE -> updateReaderModeDependency()\n\t\t}\n\t}\n\n\tprivate fun updateReaderModeDependency() {\n\t\tfindPreference<Preference>(AppSettings.KEY_READER_MODE_DETECT)?.run {\n\t\t\tisEnabled = settings.defaultReaderMode != ReaderMode.WEBTOON\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/RootSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.annotation.StringRes\nimport androidx.fragment.app.activityViewModels\nimport androidx.fragment.app.viewModels\nimport androidx.preference.Preference\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.settings.search.SettingsSearchMenuProvider\nimport org.koitharu.kotatsu.settings.search.SettingsSearchViewModel\n\n@AndroidEntryPoint\nclass RootSettingsFragment : BasePreferenceFragment(0) {\n\n\tprivate val viewModel: RootSettingsViewModel by viewModels()\n\tprivate val activityViewModel: SettingsSearchViewModel by activityViewModels()\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_root)\n\t\taddPreferencesFromResource(R.xml.pref_root_debug)\n\t\tbindPreferenceSummary(\"appearance\", R.string.theme, R.string.list_mode, R.string.language)\n\t\tbindPreferenceSummary(\"reader\", R.string.read_mode, R.string.scale_mode, R.string.switch_pages)\n\t\tbindPreferenceSummary(\"network\", R.string.storage_usage, R.string.proxy, R.string.prefetch_content)\n\t\tbindPreferenceSummary(\"userdata\", R.string.create_or_restore_backup, R.string.periodic_backups)\n\t\tbindPreferenceSummary(\"downloads\", R.string.manga_save_location, R.string.downloads_wifi_only)\n\t\tbindPreferenceSummary(\"tracker\", R.string.track_sources, R.string.notifications_settings)\n\t\tbindPreferenceSummary(\"services\", R.string.suggestions, R.string.sync, R.string.tracking)\n\t\tfindPreference<Preference>(\"about\")?.summary = getString(R.string.app_version, BuildConfig.VERSION_NAME)\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tfindPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.let { pref ->\n\t\t\tval total = viewModel.totalSourcesCount\n\t\t\tviewModel.enabledSourcesCount.observe(viewLifecycleOwner) {\n\t\t\t\tpref.summary = if (it >= 0) {\n\t\t\t\t\tgetString(R.string.enabled_d_of_d, it, total)\n\t\t\t\t} else {\n\t\t\t\t\tresources.getQuantityStringSafe(R.plurals.items, total, total)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\taddMenuProvider(SettingsSearchMenuProvider(activityViewModel))\n\t}\n\n\toverride fun setTitle(title: CharSequence?) {\n\t\tif (!resources.getBoolean(R.bool.is_tablet)) {\n\t\t\tsuper.setTitle(title)\n\t\t}\n\t}\n\n\tprivate fun bindPreferenceSummary(key: String, @StringRes vararg items: Int) {\n\t\tfindPreference<Preference>(key)?.summary = items.joinToString { getString(it) }\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/RootSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass RootSettingsViewModel @Inject constructor(\n\tsourcesRepository: MangaSourcesRepository,\n) : BaseViewModel() {\n\n\tval totalSourcesCount = sourcesRepository.allMangaSources.size\n\n\tval enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/ServicesSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.accounts.AccountManager\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.view.View\nimport androidx.preference.Preference\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\nimport org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService\nimport org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper\nimport org.koitharu.kotatsu.settings.utils.SplitSwitchPreference\nimport org.koitharu.kotatsu.sync.domain.SyncController\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ServicesSettingsFragment : BasePreferenceFragment(R.string.services),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\t@Inject\n\tlateinit var syncController: SyncController\n\n\t@Inject\n\tlateinit var scrobblerAuthHelper: ScrobblerAuthHelper\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_services)\n\t\tfindPreference<SplitSwitchPreference>(AppSettings.KEY_STATS_ENABLED)?.let {\n\t\t\tit.onContainerClickListener = Preference.OnPreferenceClickListener {\n\t\t\t\trouter.openStatistic()\n\t\t\t\ttrue\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tbindSuggestionsSummary()\n\t\tbindStatsSummary()\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tbindScrobblerSummary(AppSettings.KEY_SHIKIMORI, ScrobblerService.SHIKIMORI)\n\t\tbindScrobblerSummary(AppSettings.KEY_ANILIST, ScrobblerService.ANILIST)\n\t\tbindScrobblerSummary(AppSettings.KEY_MAL, ScrobblerService.MAL)\n\t\tbindScrobblerSummary(AppSettings.KEY_KITSU, ScrobblerService.KITSU)\n\t\tbindSyncSummary()\n\t}\n\n\toverride fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_SUGGESTIONS -> bindSuggestionsSummary()\n\t\t\tAppSettings.KEY_STATS_ENABLED -> bindStatsSummary()\n\t\t}\n\t}\n\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tAppSettings.KEY_SHIKIMORI -> {\n\t\t\t\thandleScrobblerClick(ScrobblerService.SHIKIMORI)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_MAL -> {\n\t\t\t\thandleScrobblerClick(ScrobblerService.MAL)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_ANILIST -> {\n\t\t\t\thandleScrobblerClick(ScrobblerService.ANILIST)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_KITSU -> {\n\t\t\t\thandleScrobblerClick(ScrobblerService.KITSU)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_SYNC -> {\n\t\t\t\tval am = AccountManager.get(requireContext())\n\t\t\t\tval accountType = getString(R.string.account_type_sync)\n\t\t\t\tval account = am.getAccountsByType(accountType).firstOrNull()\n\t\t\t\tif (account == null) {\n\t\t\t\t\tam.addAccount(accountType, accountType, null, null, requireActivity(), null, null)\n\t\t\t\t} else {\n\t\t\t\t\tif (!router.openSystemSyncSettings(account)) {\n\t\t\t\t\t\tSnackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\tprivate fun bindScrobblerSummary(\n\t\tkey: String,\n\t\tscrobblerService: ScrobblerService\n\t) {\n\t\tval pref = findPreference<Preference>(key) ?: return\n\t\tif (!scrobblerAuthHelper.isAuthorized(scrobblerService)) {\n\t\t\tpref.setSummary(R.string.disabled)\n\t\t\treturn\n\t\t}\n\t\tval username = scrobblerAuthHelper.getCachedUser(scrobblerService)?.nickname\n\t\tif (username != null) {\n\t\t\tpref.summary = getString(R.string.logged_in_as, username)\n\t\t} else {\n\t\t\tpref.setSummary(R.string.loading_)\n\t\t\tviewLifecycleScope.launch {\n\t\t\t\tpref.summary = withContext(Dispatchers.Default) {\n\t\t\t\t\trunCatching {\n\t\t\t\t\t\tval user = scrobblerAuthHelper.getUser(scrobblerService)\n\t\t\t\t\t\tgetString(R.string.logged_in_as, user.nickname)\n\t\t\t\t\t}.getOrElse {\n\t\t\t\t\t\tit.printStackTraceDebug()\n\t\t\t\t\t\tit.getDisplayMessage(resources)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun handleScrobblerClick(scrobblerService: ScrobblerService) {\n\t\tif (!scrobblerAuthHelper.isAuthorized(scrobblerService)) {\n\t\t\tconfirmScrobblerAuth(scrobblerService)\n\t\t} else {\n\t\t\trouter.openScrobblerSettings(scrobblerService)\n\t\t}\n\t}\n\n\tprivate fun bindSyncSummary() {\n\t\tviewLifecycleScope.launch {\n\t\t\tval account = withContext(Dispatchers.Default) {\n\t\t\t\tval type = getString(R.string.account_type_sync)\n\t\t\t\tAccountManager.get(requireContext()).getAccountsByType(type).firstOrNull()\n\t\t\t}\n\t\t\tfindPreference<Preference>(AppSettings.KEY_SYNC)?.run {\n\t\t\t\tsummary = when {\n\t\t\t\t\taccount == null -> getString(R.string.sync_title)\n\t\t\t\t\tsyncController.isEnabled(account) -> account.name\n\t\t\t\t\telse -> getString(R.string.disabled)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfindPreference<Preference>(AppSettings.KEY_SYNC_SETTINGS)?.isEnabled = account != null\n\t\t}\n\t}\n\n\tprivate fun bindSuggestionsSummary() {\n\t\tfindPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(\n\t\t\tif (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled,\n\t\t)\n\t}\n\n\tprivate fun bindStatsSummary() {\n\t\tfindPreference<Preference>(AppSettings.KEY_STATS_ENABLED)?.setSummary(\n\t\t\tif (settings.isStatsEnabled) R.string.enabled else R.string.disabled,\n\t\t)\n\t}\n\n\tprivate fun confirmScrobblerAuth(scrobblerService: ScrobblerService) {\n\t\tbuildAlertDialog(context ?: return, isCentered = true) {\n\t\t\tsetIcon(scrobblerService.iconResId)\n\t\t\tsetTitle(scrobblerService.titleResId)\n\t\t\tsetMessage(context.getString(R.string.scrobbler_auth_intro, context.getString(scrobblerService.titleResId)))\n\t\t\tsetPositiveButton(R.string.sign_in) { _, _ ->\n\t\t\t\tscrobblerAuthHelper.startAuth(context, scrobblerService).onFailure {\n\t\t\t\t\tSnackbar.make(listView, it.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t}.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/SettingsActivity.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePaddingRelative\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentFactory\nimport androidx.fragment.app.FragmentTransaction\nimport androidx.fragment.app.commit\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport com.google.android.material.appbar.AppBarLayout\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.util.ext.buildBundle\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ActivitySettingsBinding\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.settings.about.AboutSettingsFragment\nimport org.koitharu.kotatsu.settings.discord.DiscordSettingsFragment\nimport org.koitharu.kotatsu.settings.search.SettingsItem\nimport org.koitharu.kotatsu.settings.search.SettingsSearchFragment\nimport org.koitharu.kotatsu.settings.search.SettingsSearchViewModel\nimport org.koitharu.kotatsu.settings.sources.SourceSettingsFragment\nimport org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment\nimport org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment\nimport org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment\nimport org.koitharu.kotatsu.settings.userdata.BackupsSettingsFragment\n\n@AndroidEntryPoint\nclass SettingsActivity :\n\tBaseActivity<ActivitySettingsBinding>(),\n\tPreferenceFragmentCompat.OnPreferenceStartFragmentCallback,\n\tAppBarOwner {\n\n\toverride val appBar: AppBarLayout\n\t\tget() = viewBinding.appbar\n\n\tprivate val isMasterDetails\n\t\tget() = viewBinding.containerMaster != null\n\n\tprivate val viewModel: SettingsSearchViewModel by viewModels()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivitySettingsBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tval fm = supportFragmentManager\n\t\tval currentFragment = fm.findFragmentById(R.id.container)\n\t\tif (currentFragment == null || (isMasterDetails && currentFragment is RootSettingsFragment)) {\n\t\t\topenDefaultFragment()\n\t\t}\n\t\tif (isMasterDetails && fm.findFragmentById(R.id.container_master) == null) {\n\t\t\tsupportFragmentManager.commit {\n\t\t\t\tsetReorderingAllowed(true)\n\t\t\t\treplace(R.id.container_master, RootSettingsFragment())\n\t\t\t}\n\t\t}\n\t\tviewModel.isSearchActive.observe(this, ::toggleSearchMode)\n\t\tviewModel.onNavigateToPreference.observeEvent(this, ::navigateToPreference)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tval isTablet = viewBinding.containerMaster != null\n\t\tviewBinding.appbar.updatePaddingRelative(\n\t\t\tstart = bars.start(v),\n\t\t\ttop = bars.top,\n\t\t\tend = if (isTablet) 0 else bars.end(v),\n\t\t)\n\t\tviewBinding.textViewHeader?.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\tmarginEnd = bars.end(v)\n\t\t\ttopMargin = bars.top\n\t\t}\n\t\treturn insets\n\t}\n\n\toverride fun onPreferenceStartFragment(\n\t\tcaller: PreferenceFragmentCompat,\n\t\tpref: Preference,\n\t): Boolean {\n\t\tval fragmentName = pref.fragment ?: return false\n\t\topenFragment(\n\t\t\tfragmentClass = FragmentFactory.loadFragmentClass(classLoader, fragmentName),\n\t\t\targs = pref.peekExtras(),\n\t\t\tisFromRoot = caller is RootSettingsFragment,\n\t\t)\n\t\treturn true\n\t}\n\n\tfun setSectionTitle(title: CharSequence?) {\n\t\tviewBinding.textViewHeader?.apply {\n\t\t\ttextAndVisible = title\n\t\t} ?: setTitle(title ?: getString(R.string.settings))\n\t}\n\n\tfun openFragment(fragmentClass: Class<out Fragment>, args: Bundle?, isFromRoot: Boolean) {\n\t\tviewModel.discardSearch()\n\t\tval hasFragment = supportFragmentManager.findFragmentById(R.id.container) != null\n\t\tsupportFragmentManager.commit {\n\t\t\tsetReorderingAllowed(true)\n\t\t\treplace(R.id.container, fragmentClass, args)\n\t\t\tsetTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)\n\t\t\tif (!isMasterDetails || (hasFragment && !isFromRoot)) {\n\t\t\t\taddToBackStack(null)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun toggleSearchMode(isEnabled: Boolean) {\n\t\tviewBinding.containerSearch.isVisible = isEnabled\n\t\tval searchFragment = supportFragmentManager.findFragmentById(R.id.container_search)\n\t\tif (searchFragment != null) {\n\t\t\tif (!isEnabled) {\n\t\t\t\tinvalidateOptionsMenu()\n\t\t\t\tsupportFragmentManager.commit {\n\t\t\t\t\tsetReorderingAllowed(true)\n\t\t\t\t\tremove(searchFragment)\n\t\t\t\t\tsetTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isEnabled) {\n\t\t\tsupportFragmentManager.commit {\n\t\t\t\tsetReorderingAllowed(true)\n\t\t\t\tadd(R.id.container_search, SettingsSearchFragment::class.java, null)\n\t\t\t\tsetTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun openDefaultFragment() {\n\t\tval fragment = when (intent?.action) {\n\t\t\tAppRouter.ACTION_READER -> ReaderSettingsFragment()\n\t\t\tAppRouter.ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()\n\t\t\tAppRouter.ACTION_HISTORY -> BackupsSettingsFragment()\n\t\t\tAppRouter.ACTION_TRACKER -> TrackerSettingsFragment()\n\t\t\tAppRouter.ACTION_PERIODIC_BACKUP -> PeriodicalBackupSettingsFragment()\n\t\t\tAppRouter.ACTION_SOURCES -> SourcesSettingsFragment()\n\t\t\tAppRouter.ACTION_MANAGE_DISCORD -> DiscordSettingsFragment()\n\t\t\tAppRouter.ACTION_PROXY -> ProxySettingsFragment()\n\t\t\tAppRouter.ACTION_MANAGE_DOWNLOADS -> DownloadsSettingsFragment()\n\t\t\tAppRouter.ACTION_SOURCE -> SourceSettingsFragment.newInstance(\n\t\t\t\tMangaSource(intent.getStringExtra(AppRouter.KEY_SOURCE)),\n\t\t\t)\n\n\t\t\tAppRouter.ACTION_MANAGE_SOURCES -> SourcesManageFragment()\n\t\t\tIntent.ACTION_VIEW -> {\n\t\t\t\twhen (intent.data?.host) {\n\t\t\t\t\tHOST_ABOUT -> AboutSettingsFragment()\n\t\t\t\t\tHOST_SYNC_SETTINGS -> SyncSettingsFragment()\n\t\t\t\t\telse -> null\n\t\t\t\t}\n\t\t\t}\n\n\t\t\telse -> null\n\t\t} ?: if (isMasterDetails) AppearanceSettingsFragment() else RootSettingsFragment()\n\t\tsupportFragmentManager.commit {\n\t\t\tsetReorderingAllowed(true)\n\t\t\treplace(R.id.container, fragment)\n\t\t}\n\t}\n\n\tprivate fun navigateToPreference(item: SettingsItem) {\n\t\tval args = buildBundle(1) {\n\t\t\tputString(ARG_PREF_KEY, item.key)\n\t\t}\n\t\topenFragment(item.fragmentClass, args, true)\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val HOST_ABOUT = \"about\"\n\t\tprivate const val HOST_SYNC_SETTINGS = \"sync-settings\"\n\t\tconst val ARG_PREF_KEY = \"pref_key\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/StorageAndNetworkSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport androidx.preference.ListPreference\nimport androidx.preference.Preference\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.network.DoHProvider\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat\nimport org.koitharu.kotatsu.parsers.util.names\nimport org.koitharu.kotatsu.settings.userdata.storage.StorageUsagePreference\nimport java.net.Proxy\n\nclass StorageAndNetworkSettingsFragment :\n    BasePreferenceFragment(R.string.storage_and_network),\n    SharedPreferences.OnSharedPreferenceChangeListener {\n\n    private val viewModel by viewModels<StorageAndNetworkSettingsViewModel>()\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_network_storage)\n        findPreference<ListPreference>(AppSettings.KEY_DOH)?.run {\n            entryValues = DoHProvider.entries.names()\n            setDefaultValueCompat(DoHProvider.NONE.name)\n        }\n        bindProxySummary()\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))\n        settings.subscribe(this)\n        findPreference<StorageUsagePreference>(AppSettings.KEY_STORAGE_USAGE)?.let { pref ->\n            viewModel.storageUsage.observe(viewLifecycleOwner, pref)\n        }\n    }\n\n    override fun onDestroyView() {\n        settings.unsubscribe(this)\n        super.onDestroyView()\n    }\n\n    override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {\n        when (key) {\n            AppSettings.KEY_SSL_BYPASS -> {\n                Snackbar.make(listView, R.string.settings_apply_restart_required, Snackbar.LENGTH_INDEFINITE).show()\n            }\n\n            AppSettings.KEY_PROXY_TYPE,\n            AppSettings.KEY_PROXY_ADDRESS,\n            AppSettings.KEY_PROXY_PORT -> {\n                bindProxySummary()\n            }\n        }\n    }\n\n    private fun bindProxySummary() {\n        findPreference<Preference>(AppSettings.KEY_PROXY)?.run {\n            val type = settings.proxyType\n            val address = settings.proxyAddress\n            val port = settings.proxyPort\n            summary = when {\n                type == Proxy.Type.DIRECT -> context.getString(R.string.disabled)\n                address.isNullOrEmpty() || port == 0 -> context.getString(R.string.invalid_proxy_configuration)\n                else -> \"$address:$port\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/StorageAndNetworkSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.local.data.CacheDir\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport org.koitharu.kotatsu.settings.userdata.storage.StorageUsage\nimport javax.inject.Inject\n\n@HiltViewModel\nclass StorageAndNetworkSettingsViewModel @Inject constructor(\n    private val storageManager: LocalStorageManager,\n) : BaseViewModel() {\n\n    val storageUsage: StateFlow<StorageUsage?> = flow {\n        emit(loadStorageUsage())\n    }.withErrorHandling()\n        .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(1000), null)\n\n    private suspend fun loadStorageUsage(): StorageUsage {\n        val pagesCacheSize = storageManager.computeCacheSize(CacheDir.PAGES)\n        val otherCacheSize = storageManager.computeCacheSize() - pagesCacheSize\n        val storageSize = storageManager.computeStorageSize()\n        val availableSpace = storageManager.computeAvailableSize()\n        val totalBytes = pagesCacheSize + otherCacheSize + storageSize + availableSpace\n        return StorageUsage(\n            savedManga = StorageUsage.Item(\n                bytes = storageSize,\n                percent = (storageSize.toDouble() / totalBytes).toFloat(),\n            ),\n            pagesCache = StorageUsage.Item(\n                bytes = pagesCacheSize,\n                percent = (pagesCacheSize.toDouble() / totalBytes).toFloat(),\n            ),\n            otherCache = StorageUsage.Item(\n                bytes = otherCacheSize,\n                percent = (otherCacheSize.toDouble() / totalBytes).toFloat(),\n            ),\n            available = StorageUsage.Item(\n                bytes = availableSpace,\n                percent = (availableSpace.toDouble() / totalBytes).toFloat(),\n            ),\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/SuggestionsSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport androidx.lifecycle.lifecycleScope\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.settings.utils.MultiAutoCompleteTextViewPreference\nimport org.koitharu.kotatsu.settings.utils.TagsAutoCompleteProvider\nimport org.koitharu.kotatsu.suggestions.domain.SuggestionRepository\nimport org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass SuggestionsSettingsFragment : BasePreferenceFragment(R.string.suggestions),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\t@Inject\n\tlateinit var repository: SuggestionRepository\n\n\t@Inject\n\tlateinit var tagsCompletionProvider: TagsAutoCompleteProvider\n\n\t@Inject\n\tlateinit var suggestionsScheduler: SuggestionsWorker.Scheduler\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_suggestions)\n\n\t\tfindPreference<MultiAutoCompleteTextViewPreference>(AppSettings.KEY_SUGGESTIONS_EXCLUDE_TAGS)?.run {\n\t\t\tautoCompleteProvider = tagsCompletionProvider\n\t\t\tsummaryProvider = MultiAutoCompleteTextViewPreference.SimpleSummaryProvider(summary)\n\t\t}\n\t}\n\n\toverride fun onDestroy() {\n\t\tsuper.onDestroy()\n\t\tsettings.unsubscribe(this)\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\tif (settings.isSuggestionsEnabled && (key == AppSettings.KEY_SUGGESTIONS\n\t\t\t\t|| key == AppSettings.KEY_SUGGESTIONS_EXCLUDE_TAGS\n\t\t\t\t|| key == AppSettings.KEY_SUGGESTIONS_EXCLUDE_NSFW)\n\t\t) {\n\t\t\tupdateSuggestions()\n\t\t}\n\t}\n\n\tprivate fun updateSuggestions() {\n\t\tlifecycleScope.launch(Dispatchers.Default) {\n\t\t\tsuggestionsScheduler.startNow()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/SyncSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.FragmentResultListener\nimport androidx.preference.Preference\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.sync.data.SyncSettings\nimport org.koitharu.kotatsu.sync.ui.SyncHostDialogFragment\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass SyncSettingsFragment : BasePreferenceFragment(R.string.sync_settings), FragmentResultListener {\n\n\t@Inject\n\tlateinit var syncSettings: SyncSettings\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_sync)\n\t\tbindHostSummary()\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tchildFragmentManager.setFragmentResultListener(SyncHostDialogFragment.REQUEST_KEY, viewLifecycleOwner, this)\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tSyncSettings.KEY_SYNC_URL -> {\n\t\t\t\tSyncHostDialogFragment.show(childFragmentManager, null)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\toverride fun onFragmentResult(requestKey: String, result: Bundle) {\n\t\tbindHostSummary()\n\t}\n\n\tprivate fun bindHostSummary() {\n\t\tval preference = findPreference<Preference>(SyncSettings.KEY_SYNC_URL) ?: return\n\t\tpreference.summary = syncSettings.syncUrl\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AboutSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.about\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport androidx.annotation.StringRes\nimport androidx.fragment.app.viewModels\nimport androidx.preference.Preference\nimport androidx.preference.SwitchPreferenceCompat\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.combine\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.github.AppVersion\nimport org.koitharu.kotatsu.core.github.VersionId\nimport org.koitharu.kotatsu.core.github.isStable\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\n\n@AndroidEntryPoint\nclass AboutSettingsFragment : BasePreferenceFragment(R.string.about) {\n\n\tprivate val viewModel by viewModels<AboutSettingsViewModel>()\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_about)\n\t\tfindPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {\n\t\t\ttitle = getString(R.string.app_version, BuildConfig.VERSION_NAME)\n\t\t}\n\t\tfindPreference<SwitchPreferenceCompat>(AppSettings.KEY_UPDATES_UNSTABLE)?.run {\n\t\t\tisEnabled = VersionId(BuildConfig.VERSION_NAME).isStable\n\t\t\tif (!isEnabled) isChecked = true\n\t\t}\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tcombine(viewModel.isUpdateSupported, viewModel.isLoading, ::Pair)\n\t\t\t.observe(viewLifecycleOwner) { (isUpdateSupported, isLoading) ->\n\t\t\t\tfindPreference<Preference>(AppSettings.KEY_UPDATES_UNSTABLE)?.isVisible = isUpdateSupported\n\t\t\t\tfindPreference<Preference>(AppSettings.KEY_APP_VERSION)?.isEnabled = isUpdateSupported && !isLoading\n\n\t\t\t}\n\t\tviewModel.onUpdateAvailable.observeEvent(viewLifecycleOwner, ::onUpdateAvailable)\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tAppSettings.KEY_APP_VERSION -> {\n\t\t\t\tviewModel.checkForUpdates()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_LINK_WEBLATE -> {\n\t\t\t\topenLink(R.string.url_weblate, preference.title)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_LINK_GITHUB -> {\n\t\t\t\topenLink(R.string.url_github, preference.title)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_LINK_MANUAL -> {\n\t\t\t\topenLink(R.string.url_user_manual, preference.title)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_LINK_TELEGRAM -> {\n\t\t\t\tif (!openLink(R.string.url_telegram, null)) {\n\t\t\t\t\topenLink(R.string.url_telegram_web, preference.title)\n\t\t\t\t}\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\tprivate fun onUpdateAvailable(version: AppVersion?) {\n\t\tif (version == null) {\n\t\t\tSnackbar.make(listView, R.string.no_update_available, Snackbar.LENGTH_SHORT).show()\n\t\t} else {\n\t\t\tstartActivity(Intent(requireContext(), AppUpdateActivity::class.java))\n\t\t}\n\t}\n\n\tprivate fun openLink(\n\t\t@StringRes url: Int,\n\t\ttitle: CharSequence?\n\t): Boolean = if (router.openExternalBrowser(getString(url), title)) {\n\t\ttrue\n\t} else {\n\t\tSnackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\tfalse\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AboutSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.about\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.stateIn\nimport org.koitharu.kotatsu.core.github.AppUpdateRepository\nimport org.koitharu.kotatsu.core.github.AppVersion\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport javax.inject.Inject\n\n@HiltViewModel\nclass AboutSettingsViewModel @Inject constructor(\n\tprivate val appUpdateRepository: AppUpdateRepository,\n) : BaseViewModel() {\n\n\tval isUpdateSupported = flow {\n\t\temit(appUpdateRepository.isUpdateSupported())\n\t}.stateIn(viewModelScope, SharingStarted.Eagerly, false)\n\n\tval onUpdateAvailable = MutableEventFlow<AppVersion?>()\n\n\tfun checkForUpdates() {\n\t\tlaunchLoadingJob {\n\t\t\tval update = appUpdateRepository.fetchUpdate()\n\t\t\tonUpdateAvailable.call(update)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AppUpdateActivity.kt",
    "content": "package org.koitharu.kotatsu.settings.about\n\nimport android.Manifest\nimport android.app.DownloadManager\nimport android.content.ActivityNotFoundException\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.core.content.ContextCompat\nimport androidx.core.text.buildSpannedString\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport io.noties.markwon.Markwon\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.github.AppVersion\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.core.util.ext.showOrHide\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ActivityAppUpdateBinding\n\n@AndroidEntryPoint\nclass AppUpdateActivity : BaseActivity<ActivityAppUpdateBinding>(), View.OnClickListener {\n\n\tprivate val viewModel: AppUpdateViewModel by viewModels()\n\tprivate lateinit var downloadReceiver: UpdateDownloadReceiver\n\n\tprivate val permissionRequest = registerForActivityResult(\n\t\tActivityResultContracts.RequestPermission(),\n\t) {\n\t\tif (it) {\n\t\t\tviewModel.startDownload()\n\t\t} else {\n\t\t\topenInBrowser()\n\t\t}\n\t}\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityAppUpdateBinding.inflate(layoutInflater))\n\t\tdownloadReceiver = UpdateDownloadReceiver(viewModel)\n\t\tviewModel.nextVersion.observe(this, ::onNextVersionChanged)\n\t\tviewBinding.buttonCancel.setOnClickListener(this)\n\t\tviewBinding.buttonUpdate.setOnClickListener(this)\n\n\t\tContextCompat.registerReceiver(\n\t\t\tthis,\n\t\t\tdownloadReceiver,\n\t\t\tIntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),\n\t\t\tContextCompat.RECEIVER_EXPORTED,\n\t\t)\n\t\tcombine(viewModel.isLoading, viewModel.downloadProgress, ::Pair)\n\t\t\t.observe(this, ::onProgressChanged)\n\t\tviewModel.downloadState.observe(this, ::onDownloadStateChanged)\n\t\tviewModel.onError.observeEvent(this, ::onError)\n\t\tviewModel.onDownloadDone.observeEvent(this) { intent ->\n\t\t\ttry {\n\t\t\t\tstartActivity(intent)\n\t\t\t} catch (e: ActivityNotFoundException) {\n\t\t\t\te.printStackTraceDebug()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onDestroy() {\n\t\tunregisterReceiver(downloadReceiver)\n\t\tsuper.onDestroy()\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.root.updatePadding(top = barsInsets.top)\n\t\tviewBinding.dockedToolbarChild.updateLayoutParams<MarginLayoutParams> {\n\t\t\tleftMargin = barsInsets.left\n\t\t\trightMargin = barsInsets.right\n\t\t\tbottomMargin = barsInsets.bottom\n\t\t}\n\t\tviewBinding.scrollView.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_cancel -> finishAfterTransition()\n\t\t\tR.id.button_update -> doUpdate()\n\t\t}\n\t}\n\n\tprivate suspend fun onNextVersionChanged(version: AppVersion?) {\n\t\tviewBinding.buttonUpdate.isEnabled = version != null && !viewModel.isLoading.value\n\t\tif (version == null) {\n\t\t\tviewBinding.textViewContent.setText(R.string.loading_)\n\t\t\treturn\n\t\t}\n\t\tval markwon = Markwon.create(this)\n\t\tval message = withContext(Dispatchers.Default) {\n\t\t\tbuildSpannedString {\n\t\t\t\tappend(getString(R.string.new_version_s, version.name))\n\t\t\t\tappendLine()\n\t\t\t\tappend(getString(R.string.size_s, FileSize.BYTES.format(this@AppUpdateActivity, version.apkSize)))\n\t\t\t\tappendLine()\n\t\t\t\tappendLine()\n\t\t\t\tappend(markwon.toMarkdown(version.description))\n\t\t\t}\n\t\t}\n\t\tmarkwon.setParsedMarkdown(viewBinding.textViewContent, message)\n\t}\n\n\tprivate fun doUpdate() {\n\t\tviewModel.installIntent.value?.let { intent ->\n\t\t\ttry {\n\t\t\t\tstartActivity(intent)\n\t\t\t} catch (e: Exception) {\n\t\t\t\tonError(e)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {\n\t\t\tpermissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)\n\t\t} else {\n\t\t\tviewModel.startDownload()\n\t\t}\n\t}\n\n\tprivate fun openInBrowser() {\n\t\tval latestVersion = viewModel.nextVersion.value ?: return\n\t\tif (!router.openExternalBrowser(latestVersion.url, getString(R.string.open_in_browser))) {\n\t\t\tSnackbar.make(viewBinding.scrollView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\t}\n\t}\n\n\tprivate fun onProgressChanged(value: Pair<Boolean, Float>) {\n\t\tval (isLoading, downloadProgress) = value\n\t\tval indicator = viewBinding.progressBar\n\t\tindicator.showOrHide(isLoading)\n\t\tindicator.isIndeterminate = downloadProgress <= 0f\n\t\tif (downloadProgress > 0f) {\n\t\t\tindicator.setProgressCompat((indicator.max * downloadProgress).toInt(), true)\n\t\t}\n\t\tviewBinding.buttonUpdate.isEnabled = !isLoading && viewModel.nextVersion.value != null\n\t}\n\n\tprivate fun onDownloadStateChanged(state: Int) {\n\t\tval message = when (state) {\n\t\t\tDownloadManager.STATUS_FAILED -> R.string.error_occurred\n\t\t\tDownloadManager.STATUS_PAUSED -> R.string.downloads_paused\n\t\t\telse -> 0\n\t\t}\n\t\tviewBinding.textViewError.setTextAndVisible(message)\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tviewBinding.textViewError.textAndVisible = e.getDisplayMessage(resources)\n\t}\n\n\tprivate class UpdateDownloadReceiver(\n\t\tprivate val viewModel: AppUpdateViewModel,\n\t) : BroadcastReceiver() {\n\n\t\toverride fun onReceive(context: Context, intent: Intent) {\n\t\t\twhen (intent.action) {\n\t\t\t\tDownloadManager.ACTION_DOWNLOAD_COMPLETE -> {\n\t\t\t\t\tviewModel.onDownloadComplete(intent)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/about/AppUpdateViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.about\n\nimport android.app.DownloadManager\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Environment\nimport androidx.core.net.toUri\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.isActive\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.github.AppUpdateRepository\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.requireValue\nimport javax.inject.Inject\n\n@HiltViewModel\nclass AppUpdateViewModel @Inject constructor(\n\tprivate val repository: AppUpdateRepository,\n\t@ApplicationContext context: Context,\n) : BaseViewModel() {\n\n\tval nextVersion = repository.observeAvailableUpdate()\n\tval downloadProgress = MutableStateFlow(-1f)\n\tval downloadState = MutableStateFlow(DownloadManager.STATUS_PENDING)\n\tval installIntent = MutableStateFlow<Intent?>(null)\n\tval onDownloadDone = MutableEventFlow<Intent>()\n\n\tprivate val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager\n\tprivate val appName = context.getString(R.string.app_name)\n\n\tinit {\n\t\tif (nextVersion.value == null) {\n\t\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\t\trepository.fetchUpdate()\n\t\t\t}\n\t\t}\n\t}\n\n\tfun startDownload() {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval version = nextVersion.requireValue()\n\t\t\tval url = version.apkUrl.toUri()\n\t\t\tval request = DownloadManager.Request(url)\n\t\t\t\t.setTitle(\"$appName v${version.name}\")\n\t\t\t\t.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, url.lastPathSegment)\n\t\t\t\t.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)\n\t\t\t\t.setMimeType(\"application/vnd.android.package-archive\")\n\t\t\tval downloadId = downloadManager.enqueue(request)\n\t\t\tobserveDownload(downloadId)\n\t\t}\n\t}\n\n\tfun onDownloadComplete(intent: Intent) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)\n\t\t\tif (downloadId == 0L) {\n\t\t\t\treturn@launchLoadingJob\n\t\t\t}\n\t\t\t@Suppress(\"DEPRECATION\")\n\t\t\tval installerIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)\n\t\t\tinstallerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)\n\t\t\tinstallerIntent.setDataAndType(\n\t\t\t\tdownloadManager.getUriForDownloadedFile(downloadId),\n\t\t\t\tdownloadManager.getMimeTypeForDownloadedFile(downloadId),\n\t\t\t)\n\t\t\tinstallerIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)\n\t\t\tinstallIntent.value = installerIntent\n\t\t\tonDownloadDone.call(installerIntent)\n\t\t}\n\t}\n\n\tprivate suspend fun observeDownload(id: Long) {\n\t\tval query = DownloadManager.Query()\n\t\tquery.setFilterById(id)\n\t\twhile (currentCoroutineContext().isActive) {\n\t\t\tdownloadManager.query(query).use { cursor ->\n\t\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\t\tval bytesDownloaded = cursor.getLong(\n\t\t\t\t\t\tcursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR),\n\t\t\t\t\t)\n\t\t\t\t\tval bytesTotal = cursor.getLong(\n\t\t\t\t\t\tcursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES),\n\t\t\t\t\t)\n\t\t\t\t\tdownloadProgress.value = bytesDownloaded.toFloat() / bytesTotal\n\t\t\t\t\tval state = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))\n\t\t\t\t\tdownloadState.value = state\n\t\t\t\t\tif (state == DownloadManager.STATUS_SUCCESSFUL || state == DownloadManager.STATUS_FAILED) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tdelay(100)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/about/changelog/ChangelogFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.about.changelog\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport io.noties.markwon.Markwon\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.container\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.showOrHide\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.databinding.FragmentChangelogBinding\n\n@AndroidEntryPoint\nclass ChangelogFragment : BaseFragment<FragmentChangelogBinding>() {\n\n\tprivate val viewModel: ChangelogViewModel by viewModels()\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?\n\t) = FragmentChangelogBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: FragmentChangelogBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval markwon = Markwon.create(binding.root.context)\n\t\tviewModel.isLoading.observe(viewLifecycleOwner) {\n\t\t\tbinding.progressBar.showOrHide(it)\n\t\t}\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, DialogErrorObserver(binding.root, this))\n\t\tviewModel.changelog.filterNotNull()\n\t\t\t.map { markwon.toMarkdown(it) }\n\t\t\t.flowOn(Dispatchers.Default)\n\t\t\t.observe(viewLifecycleOwner) {\n\t\t\t\tmarkwon.setParsedMarkdown(binding.textViewContent, it)\n\t\t\t}\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tactivity?.setTitle(R.string.changelog)\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tval isTablet = !resources.getBoolean(R.bool.is_tablet)\n\t\tval isMaster = container?.id == R.id.container_master\n\t\tval basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)\n\t\trequireViewBinding().textViewContent.setPaddingRelative(\n\t\t\tbasePadding + if (isTablet && !isMaster) 0 else barsInsets.start(v),\n\t\t\tbasePadding,\n\t\t\tbasePadding + if (isTablet && isMaster) 0 else barsInsets.end(v),\n\t\t\tbasePadding + barsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAll(typeMask)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/about/changelog/ChangelogViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.about.changelog\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.jsoup.internal.StringUtil\nimport org.koitharu.kotatsu.core.github.AppUpdateRepository\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ChangelogViewModel @Inject constructor(\n\tprivate val appUpdateRepository: AppUpdateRepository,\n) : BaseViewModel() {\n\n\tval changelog = MutableStateFlow<String?>(null)\n\n\tinit {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval versions = appUpdateRepository.getAvailableVersions()\n\t\t\tval stringJoiner = StringUtil.StringJoiner(\"\\n\\n\\n\")\n\t\t\tfor (version in versions) {\n\t\t\t\tstringJoiner.add(\"# \")\n\t\t\t\t\t.append(version.name)\n\t\t\t\t\t.append(\"\\n\\n\")\n\t\t\t\t\t.append(version.description)\n\t\t\t}\n\t\t\tchangelog.value = stringJoiner.complete()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/discord/DiscordSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.discord\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport android.view.inputmethod.EditorInfo\nimport androidx.appcompat.app.AlertDialog\nimport androidx.fragment.app.viewModels\nimport androidx.preference.EditTextPreference\nimport androidx.preference.EditTextPreferenceDialogFragmentCompat\nimport androidx.preference.Preference\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.withArgs\nimport org.koitharu.kotatsu.scrobbling.discord.ui.DiscordAuthActivity\n\n@AndroidEntryPoint\nclass DiscordSettingsFragment : BasePreferenceFragment(R.string.discord) {\n\n\tprivate val viewModel by viewModels<DiscordSettingsViewModel>()\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_discord)\n\t\tfindPreference<EditTextPreference>(AppSettings.KEY_DISCORD_TOKEN)?.let { pref ->\n\t\t\tpref.dialogMessage = pref.context.getString(\n\t\t\t\tR.string.discord_token_description,\n\t\t\t\tpref.context.getString(R.string.sign_in),\n\t\t\t)\n\t\t\tpref.setOnBindEditTextListener {\n\t\t\t\tit.setHint(R.string.discord_token_hint)\n\t\t\t\tit.inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tviewModel.tokenState.observe(viewLifecycleOwner) { (state, token) ->\n\t\t\tbindTokenPreference(state, token)\n\t\t}\n\t}\n\n\toverride fun onDisplayPreferenceDialog(preference: Preference) {\n\t\tif (preference is EditTextPreference && preference.key == AppSettings.KEY_DISCORD_TOKEN) {\n\t\t\tif (parentFragmentManager.findFragmentByTag(TokenDialogFragment.DIALOG_FRAGMENT_TAG) != null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval f = TokenDialogFragment.newInstance(preference.key)\n\t\t\t@Suppress(\"DEPRECATION\")\n\t\t\tf.setTargetFragment(this, 0)\n\t\t\tf.show(parentFragmentManager, TokenDialogFragment.DIALOG_FRAGMENT_TAG)\n\t\t\treturn\n\t\t}\n\t\tsuper.onDisplayPreferenceDialog(preference)\n\t}\n\n\tprivate fun bindTokenPreference(state: TokenState, token: String?) {\n\t\tval pref = findPreference<EditTextPreference>(AppSettings.KEY_DISCORD_TOKEN) ?: return\n\t\twhen (state) {\n\t\t\tTokenState.EMPTY -> {\n\t\t\t\tpref.icon = null\n\t\t\t\tpref.setSummary(R.string.discord_token_summary)\n\t\t\t}\n\n\t\t\tTokenState.REQUIRED -> {\n\t\t\t\tpref.icon = getWarningIcon()\n\t\t\t\tpref.setSummary(R.string.discord_token_summary)\n\t\t\t}\n\n\t\t\tTokenState.INVALID -> {\n\t\t\t\tpref.icon = getWarningIcon()\n\t\t\t\tpref.summary = getString(R.string.invalid_token, token)\n\t\t\t}\n\n\t\t\tTokenState.VALID -> {\n\t\t\t\tpref.icon = null\n\t\t\t\tpref.summary = token\n\t\t\t}\n\n\t\t\tTokenState.CHECKING -> {\n\t\t\t\tpref.icon = null\n\t\t\t\tpref.setSummary(R.string.loading_)\n\t\t\t}\n\t\t}\n\t}\n\n\tclass TokenDialogFragment : EditTextPreferenceDialogFragmentCompat() {\n\n\t\toverride fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {\n\t\t\tsuper.onPrepareDialogBuilder(builder)\n\t\t\tbuilder.setNeutralButton(R.string.sign_in) { _, _ ->\n\t\t\t\topenSignIn()\n\t\t\t}\n\t\t}\n\n\t\tprivate fun openSignIn() {\n\t\t\tactivity?.run {\n\t\t\t\tstartActivity(Intent(this, DiscordAuthActivity::class.java))\n\t\t\t}\n\t\t}\n\n\t\tcompanion object {\n\n\t\t\tconst val DIALOG_FRAGMENT_TAG: String = \"androidx.preference.PreferenceFragment.DIALOG\"\n\n\t\t\tfun newInstance(key: String) = TokenDialogFragment().withArgs(1) {\n\t\t\t\tputString(ARG_KEY, key)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/discord/DiscordSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.discord\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.isNetworkError\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.scrobbling.discord.data.DiscordRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass DiscordSettingsViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val repository: DiscordRepository,\n) : BaseViewModel() {\n\n\tval tokenState: StateFlow<Pair<TokenState, String?>> = settings.observe(\n\t\tAppSettings.KEY_DISCORD_RPC,\n\t\tAppSettings.KEY_DISCORD_TOKEN,\n\t).flatMapLatest {\n\t\tcheckToken()\n\t}.stateIn(\n\t\tviewModelScope + Dispatchers.Default,\n\t\tSharingStarted.Eagerly,\n\t\tTokenState.CHECKING to settings.discordToken,\n\t)\n\n\tprivate fun checkToken(): Flow<Pair<TokenState, String?>> = flow {\n\t\tval token = settings.discordToken\n\t\tif (!settings.isDiscordRpcEnabled) {\n\t\t\temit(\n\t\t\t\tif (token == null) {\n\t\t\t\t\tTokenState.EMPTY to null\n\t\t\t\t} else {\n\t\t\t\t\tTokenState.VALID to token\n\t\t\t\t},\n\t\t\t)\n\t\t\treturn@flow\n\t\t}\n\t\tif (token == null) {\n\t\t\temit(TokenState.REQUIRED to null)\n\t\t\treturn@flow\n\t\t}\n\t\temit(TokenState.CHECKING to token)\n\t\tif (validateToken(token)) {\n\t\t\temit(TokenState.VALID to token)\n\t\t} else {\n\t\t\temit(TokenState.INVALID to token)\n\t\t}\n\t}\n\n\tprivate suspend fun validateToken(token: String) = runCatchingCancellable {\n\t\trepository.checkToken(token)\n\t}.fold(\n\t\tonSuccess = { true },\n\t\tonFailure = { it.isNetworkError() },\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/discord/TokenState.kt",
    "content": "package org.koitharu.kotatsu.settings.discord\n\nenum class TokenState {\n\n\tEMPTY, REQUIRED, INVALID, VALID, CHECKING\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.nav\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.NavItem\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.dialog.setRecyclerViewList\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.container\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.settings.nav.adapter.navAddAD\nimport org.koitharu.kotatsu.settings.nav.adapter.navAvailableAD\nimport org.koitharu.kotatsu.settings.nav.adapter.navConfigAD\n\n@AndroidEntryPoint\nclass NavConfigFragment : BaseFragment<FragmentSettingsSourcesBinding>(), RecyclerViewOwner,\n\tOnListItemClickListener<NavItem>, View.OnClickListener {\n\n\tprivate var reorderHelper: ItemTouchHelper? = null\n\tprivate val viewModel by viewModels<NavConfigViewModel>()\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerView\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t): FragmentSettingsSourcesBinding {\n\t\treturn FragmentSettingsSourcesBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(\n\t\tbinding: FragmentSettingsSourcesBinding,\n\t\tsavedInstanceState: Bundle?,\n\t) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval navConfigAdapter = BaseListAdapter<ListModel>()\n\t\t\t.addDelegate(ListItemType.NAV_ITEM, navConfigAD(this))\n\t\t\t.addDelegate(ListItemType.FOOTER_LOADING, navAddAD(this))\n\t\twith(binding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\tadapter = navConfigAdapter\n\t\t\treorderHelper = ItemTouchHelper(ReorderCallback()).also {\n\t\t\t\tit.attachToRecyclerView(this)\n\t\t\t}\n\t\t}\n\t\tviewModel.content.observe(viewLifecycleOwner, navConfigAdapter)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval isTablet = !resources.getBoolean(R.bool.is_tablet)\n\t\tval isMaster = container?.id == R.id.container_master\n\t\tv.setPaddingRelative(\n\t\t\tif (isTablet && !isMaster) 0 else barsInsets.start(v),\n\t\t\t0,\n\t\t\tif (isTablet && isMaster) 0 else barsInsets.end(v),\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tactivity?.setTitle(R.string.main_screen_sections)\n\t}\n\n\toverride fun onDestroyView() {\n\t\treorderHelper = null\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\tvar dialog: DialogInterface? = null\n\t\tval listener = OnListItemClickListener<NavItem> { item, _ ->\n\t\t\tviewModel.addItem(item)\n\t\t\tdialog?.dismiss()\n\t\t}\n\t\tdialog = buildAlertDialog(v.context) {\n\t\t\tsetTitle(R.string.add)\n\t\t\tsetCancelable(true)\n\t\t\tsetRecyclerViewList(viewModel.availableItems, navAvailableAD(listener))\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t}.apply { show() }\n\t}\n\n\toverride fun onItemClick(item: NavItem, view: View) {\n\t\tviewModel.removeItem(item)\n\t}\n\n\toverride fun onItemLongClick(item: NavItem, view: View): Boolean {\n\t\tval holder = viewBinding?.recyclerView?.findContainingViewHolder(view) ?: return false\n\t\treorderHelper?.startDrag(holder)\n\t\treturn true\n\t}\n\n\tprivate inner class ReorderCallback : ItemTouchHelper.SimpleCallback(\n\t\tItemTouchHelper.DOWN or ItemTouchHelper.UP,\n\t\t0,\n\t) {\n\n\t\toverride fun onMove(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tviewHolder: RecyclerView.ViewHolder,\n\t\t\ttarget: RecyclerView.ViewHolder,\n\t\t): Boolean = target.itemViewType == ListItemType.NAV_ITEM.ordinal\n\n\t\toverride fun onMoved(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tviewHolder: RecyclerView.ViewHolder,\n\t\t\tfromPos: Int,\n\t\t\ttarget: RecyclerView.ViewHolder,\n\t\t\ttoPos: Int,\n\t\t\tx: Int,\n\t\t\ty: Int,\n\t\t) {\n\t\t\tsuper.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)\n\t\t\tviewModel.reorder(fromPos, toPos)\n\t\t}\n\n\t\toverride fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit\n\n\t\toverride fun isLongPressDragEnabled() = false\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.nav\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.NavItem\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.main.ui.MainActivity\nimport org.koitharu.kotatsu.main.ui.MainNavigationDelegate\nimport org.koitharu.kotatsu.parsers.util.move\nimport org.koitharu.kotatsu.settings.nav.model.NavItemAddModel\nimport org.koitharu.kotatsu.settings.nav.model.NavItemConfigModel\nimport javax.inject.Inject\n\n@HiltViewModel\nclass NavConfigViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val activityRecreationHandle: ActivityRecreationHandle,\n) : BaseViewModel() {\n\n\tprivate val items = MutableStateFlow(settings.mainNavItems)\n\n\tval content: StateFlow<List<ListModel>> = items.map { snapshot ->\n\t\tbuildList(snapshot.size + 1) {\n\t\t\tsnapshot.mapTo(this) {\n\t\t\t\tNavItemConfigModel(it, getUnavailabilityHint(it))\n\t\t\t}\n\t\t\tif (size < NavItem.entries.size) {\n\t\t\t\tadd(NavItemAddModel(size < MainNavigationDelegate.MAX_ITEM_COUNT))\n\t\t\t}\n\t\t}\n\t}.stateIn(\n\t\tviewModelScope + Dispatchers.Default,\n\t\tSharingStarted.WhileSubscribed(5000),\n\t\temptyList(),\n\t)\n\n\tprivate var commitJob: Job? = null\n\n\tval availableItems\n\t\tget() = items.value.let { snapshot ->\n\t\t\tNavItem.entries.filterNot { x -> x in snapshot }\n\t\t}\n\n\tfun reorder(fromPos: Int, toPos: Int) {\n\t\titems.value = items.value.toMutableList().apply {\n\t\t\tmove(fromPos, toPos)\n\t\t\tcommit(this)\n\t\t}\n\t}\n\n\tfun addItem(item: NavItem) {\n\t\titems.value = items.value.plus(item).also {\n\t\t\tcommit(it)\n\t\t}\n\t}\n\n\tfun removeItem(item: NavItem) {\n\t\tval newList = items.value.toMutableList()\n\t\tnewList.remove(item)\n\t\tif (newList.isEmpty()) {\n\t\t\tnewList.add(NavItem.EXPLORE)\n\t\t}\n\t\titems.value = newList\n\t\tcommit(newList)\n\t}\n\n\tprivate fun commit(value: List<NavItem>) {\n\t\tval prevJob = commitJob\n\t\tcommitJob = launchJob {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tdelay(500)\n\t\t\tsettings.mainNavItems = value\n\t\t\tactivityRecreationHandle.recreate(MainActivity::class.java)\n\t\t}\n\t}\n\n\tprivate fun getUnavailabilityHint(item: NavItem) = if (item.isAvailable(settings)) {\n\t\t0\n\t} else when (item) {\n\t\tNavItem.FEED -> R.string.check_for_new_chapters_disabled\n\t\tNavItem.SUGGESTIONS -> R.string.suggestions_unavailable_text\n\t\telse -> 0\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/adapter/NavConfigAD.kt",
    "content": "package org.koitharu.kotatsu.settings.nav.adapter\n\nimport android.annotation.SuppressLint\nimport android.view.MotionEvent\nimport android.view.View\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.NavItem\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.ItemNavAvailableBinding\nimport org.koitharu.kotatsu.databinding.ItemNavConfigBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.settings.nav.model.NavItemAddModel\nimport org.koitharu.kotatsu.settings.nav.model.NavItemConfigModel\n\n@SuppressLint(\"ClickableViewAccessibility\")\nfun navConfigAD(\n\tclickListener: OnListItemClickListener<NavItem>,\n) = adapterDelegateViewBinding<NavItemConfigModel, ListModel, ItemNavConfigBinding>(\n\t{ layoutInflater, parent -> ItemNavConfigBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tval eventListener = object : View.OnClickListener, View.OnTouchListener {\n\t\toverride fun onClick(v: View) = clickListener.onItemClick(item.item, v)\n\n\t\toverride fun onTouch(v: View?, event: MotionEvent): Boolean =\n\t\t\tevent.actionMasked == MotionEvent.ACTION_DOWN &&\n\t\t\t\tclickListener.onItemLongClick(item.item, itemView)\n\t}\n\tbinding.imageViewRemove.setOnClickListener(eventListener)\n\tbinding.imageViewReorder.setOnTouchListener(eventListener)\n\n\tbind {\n\t\twith(binding.textViewTitle) {\n\t\t\tisEnabled = item.disabledHintResId == 0\n\t\t\tsetText(item.item.title)\n\t\t\tsetCompoundDrawablesRelativeWithIntrinsicBounds(item.item.icon, 0, 0, 0)\n\t\t}\n\t\tbinding.textViewHint.setTextAndVisible(item.disabledHintResId)\n\t}\n}\n\nfun navAvailableAD(\n\tclickListener: OnListItemClickListener<NavItem>,\n) = adapterDelegateViewBinding<NavItem, NavItem, ItemNavAvailableBinding>(\n\t{ layoutInflater, parent -> ItemNavAvailableBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.root.setOnClickListener { v ->\n\t\tclickListener.onItemClick(item, v)\n\t}\n\n\tbind {\n\t\twith(binding.root) {\n\t\t\tsetText(item.title)\n\t\t\tsetCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0)\n\t\t}\n\t}\n}\n\nfun navAddAD(\n\tclickListener: View.OnClickListener,\n) = adapterDelegateViewBinding<NavItemAddModel, ListModel, ItemNavAvailableBinding>(\n\t{ layoutInflater, parent -> ItemNavAvailableBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.root.setOnClickListener(clickListener)\n\tbinding.root.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_add, 0, 0, 0)\n\n\tbind {\n\t\twith(binding.root) {\n\t\t\tsetText(if (item.canAdd) R.string.add else R.string.items_limit_exceeded)\n\t\t\tisEnabled = item.canAdd\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/model/NavItemAddModel.kt",
    "content": "package org.koitharu.kotatsu.settings.nav.model\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class NavItemAddModel(\n\tval canAdd: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean = other is NavItemAddModel\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/model/NavItemConfigModel.kt",
    "content": "package org.koitharu.kotatsu.settings.nav.model\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.core.prefs.NavItem\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class NavItemConfigModel(\n\tval item: NavItem,\n\t@StringRes val disabledHintResId: Int,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is NavItemConfigModel && other.item == item\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt",
    "content": "package org.koitharu.kotatsu.settings.override\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.filterNotNull\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.databinding.ActivityOverrideEditBinding\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.picker.ui.PageImagePickContract\nimport com.google.android.material.R as materialR\n\n@AndroidEntryPoint\nclass OverrideConfigActivity : BaseActivity<ActivityOverrideEditBinding>(), View.OnClickListener,\n\tActivityResultCallback<Uri?> {\n\n\tprivate val viewModel: OverrideConfigViewModel by viewModels()\n\n\tprivate val pickCoverFileLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument(), this)\n\tprivate val pickPageLauncher = registerForActivityResult(PageImagePickContract(), this)\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityOverrideEditBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tviewBinding.buttonDone.setOnClickListener(this)\n\t\tviewBinding.buttonPickFile.setOnClickListener(this)\n\t\tviewBinding.buttonPickPage.setOnClickListener(this)\n\t\tviewBinding.buttonResetCover.setOnClickListener(this)\n\t\tviewBinding.layoutName.setEndIconOnClickListener(this)\n\t\tviewModel.data.filterNotNull().observe(this, ::onDataChanged)\n\t\tviewModel.onSaved.observeEvent(this) { onDataSaved() }\n\t\tviewModel.isLoading.observe(this, ::onLoadingStateChanged)\n\t\tviewModel.onError.observeEvent(this, ::onError)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tviewBinding.root.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAll(typeMask)\n\t}\n\n\toverride fun onActivityResult(result: Uri?) {\n\t\tif (result != null) {\n\t\t\tif (result.host?.startsWith(packageName) != true) {\n\t\t\t\tcontentResolver.takePersistableUriPermission(result, Intent.FLAG_GRANT_READ_URI_PERMISSION)\n\t\t\t}\n\t\t\tviewModel.updateCover(result.toString())\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_done -> viewModel.save(\n\t\t\t\ttitle = viewBinding.editName.text?.toString()?.trim(),\n\t\t\t)\n\n\t\t\tmaterialR.id.text_input_end_icon -> viewBinding.editName.text?.clear()\n\n\t\t\tR.id.button_reset_cover -> viewModel.updateCover(null)\n\t\t\tR.id.button_pick_file -> {\n\t\t\t\tif (!pickCoverFileLauncher.tryLaunch(arrayOf(\"image/*\"))) {\n\t\t\t\t\tSnackbar.make(\n\t\t\t\t\t\tviewBinding.imageViewCover,\n\t\t\t\t\t\tR.string.operation_not_supported,\n\t\t\t\t\t\tSnackbar.LENGTH_SHORT,\n\t\t\t\t\t).show()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tR.id.button_pick_page -> {\n\t\t\t\tval manga = viewModel.data.value?.first\n\t\t\t\tpickPageLauncher.launch(manga)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun onDataChanged(data: Pair<Manga, MangaOverride>) {\n\t\tval (manga, override) = data\n\t\tviewBinding.imageViewCover.setImageAsync(override.coverUrl.ifNullOrEmpty { manga.coverUrl }, manga)\n\t\tviewBinding.layoutName.placeholderText = manga.title\n\t\tif (viewBinding.editName.tag == null) {\n\t\t\tviewBinding.editName.setText(override.title)\n\t\t\tviewBinding.editName.tag = override.title\n\t\t}\n\t\tviewBinding.buttonResetCover.isEnabled = !override.coverUrl.isNullOrEmpty()\n\t}\n\n\tprivate fun onError(e: Throwable) {\n\t\tviewBinding.textViewError.text = e.getDisplayMessage(resources)\n\t\tviewBinding.textViewError.isVisible = true\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tviewBinding.buttonDone.isEnabled = !isLoading\n\t\tviewBinding.editName.isEnabled = !isLoading\n\t\tif (isLoading) {\n\t\t\tviewBinding.textViewError.isVisible = false\n\t\t}\n\t}\n\n\tprivate fun onDataSaved() {\n\t\tsetResult(RESULT_OK)\n\t\tfinish()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.override\n\nimport android.content.Context\nimport androidx.core.net.toUri\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.withContext\nimport okio.buffer\nimport okio.sink\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.core.util.MimeTypes\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.isFileUri\nimport org.koitharu.kotatsu.core.util.ext.openSource\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull\nimport org.koitharu.kotatsu.core.util.ext.toUriOrNull\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.md5\nimport java.io.File\nimport javax.inject.Inject\n\nprivate const val DIR_COVERS = \"covers\"\n\n@HiltViewModel\nclass OverrideConfigViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\t@ApplicationContext private val context: Context,\n\tprivate val dataRepository: MangaDataRepository,\n) : BaseViewModel() {\n\n\tprivate val manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga\n\n\tval data = MutableStateFlow<Pair<Manga, MangaOverride>?>(null)\n\tval onSaved = MutableEventFlow<Unit>()\n\n\tinit {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tdata.value = manga to (dataRepository.getOverride(manga.id) ?: emptyOverride())\n\t\t}\n\t}\n\n\tfun save(title: String?) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval override = checkNotNull(data.value).second.let {\n\t\t\t\tit.copy(\n\t\t\t\t\ttitle = title,\n\t\t\t\t\tcoverUrl = it.coverUrl?.cachedFile(),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdataRepository.setOverride(manga, override)\n\t\t\tonSaved.call(Unit)\n\t\t}\n\t}\n\n\tfun updateCover(coverUri: String?) {\n\t\tval snapshot = data.value ?: return\n\t\tdata.value = snapshot.first to snapshot.second.copy(\n\t\t\tcoverUrl = coverUri,\n\t\t)\n\t}\n\n\tprivate suspend fun String.cachedFile(): String {\n\t\tval uri = toUriOrNull()\n\t\tif (uri == null || uri.isFileUri()) {\n\t\t\treturn this\n\t\t}\n\t\tval cacheDir = context.getExternalFilesDir(DIR_COVERS) ?: return this\n\t\tval cr = context.contentResolver\n\t\tval ext = cr.getType(uri)?.toMimeTypeOrNull()?.let {\n\t\t\tMimeTypes.getExtension(it)\n\t\t}\n\t\tval fileName = buildString {\n\t\t\tappend(this@cachedFile.md5())\n\t\t\tif (!ext.isNullOrEmpty()) {\n\t\t\t\tappend('.')\n\t\t\t\tappend(ext)\n\t\t\t}\n\t\t}\n\t\treturn withContext(Dispatchers.IO) {\n\t\t\tval dest = File(cacheDir, fileName)\n\t\t\tcr.openSource(uri).use { source ->\n\t\t\t\tdest.sink().buffer().use { sink ->\n\t\t\t\t\tsink.writeAll(source)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdest\n\t\t}.toUri().toString()\n\t}\n\n\tprivate fun emptyOverride() = MangaOverride(null, null, null)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt",
    "content": "package org.koitharu.kotatsu.settings.protect\n\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.text.Editable\nimport android.view.KeyEvent\nimport android.view.View\nimport android.view.WindowManager\nimport android.view.inputmethod.EditorInfo\nimport android.widget.CompoundButton\nimport android.widget.TextView\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding\n\nprivate const val MIN_PASSWORD_LENGTH = 4\n\n@AndroidEntryPoint\nclass ProtectSetupActivity :\n\tBaseActivity<ActivitySetupProtectBinding>(),\n\tDefaultTextWatcher,\n\tView.OnClickListener,\n\tTextView.OnEditorActionListener,\n\tCompoundButton.OnCheckedChangeListener {\n\n\tprivate val viewModel by viewModels<ProtectSetupViewModel>()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\twindow.addFlags(WindowManager.LayoutParams.FLAG_SECURE)\n\t\tsetContentView(ActivitySetupProtectBinding.inflate(layoutInflater))\n\t\tviewBinding.editPassword.addTextChangedListener(this)\n\t\tviewBinding.editPassword.setOnEditorActionListener(this)\n\t\tviewBinding.buttonNext.setOnClickListener(this)\n\t\tviewBinding.buttonCancel.setOnClickListener(this)\n\n\t\tviewBinding.switchBiometric.isChecked = viewModel.isBiometricEnabled\n\t\tviewBinding.switchBiometric.setOnCheckedChangeListener(this)\n\n\t\tviewModel.isSecondStep.observe(this, this::onStepChanged)\n\t\tviewModel.onPasswordSet.observeEvent(this) {\n\t\t\tfinishAfterTransition()\n\t\t}\n\t\tviewModel.onPasswordMismatch.observeEvent(this) {\n\t\t\tviewBinding.editPassword.error = getString(R.string.passwords_mismatch)\n\t\t}\n\t\tviewModel.onClearText.observeEvent(this) {\n\t\t\tviewBinding.editPassword.text?.clear()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)\n\t\tviewBinding.root.setPadding(\n\t\t\tbarsInsets.left + basePadding,\n\t\t\tbarsInsets.top + basePadding,\n\t\t\tbarsInsets.right + basePadding,\n\t\t\tbarsInsets.bottom + basePadding,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_cancel -> finish()\n\t\t\tR.id.button_next -> viewModel.onNextClick(\n\t\t\t\tpassword = viewBinding.editPassword.text?.toString() ?: return,\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {\n\t\tviewModel.setBiometricEnabled(isChecked)\n\t}\n\n\toverride fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {\n\t\treturn if (actionId == EditorInfo.IME_ACTION_DONE && viewBinding.buttonNext.isEnabled) {\n\t\t\tviewBinding.buttonNext.performClick()\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tviewBinding.editPassword.error = null\n\t\tval isEnoughLength = (s?.length ?: 0) >= MIN_PASSWORD_LENGTH\n\t\tviewBinding.buttonNext.isEnabled = isEnoughLength\n\t\tviewBinding.layoutPassword.isHelperTextEnabled =\n\t\t\t!isEnoughLength || viewModel.isSecondStep.value == true\n\t}\n\n\tprivate fun onStepChanged(isSecondStep: Boolean) {\n\t\tviewBinding.buttonCancel.isGone = isSecondStep\n\t\tviewBinding.switchBiometric.isVisible = isSecondStep && isBiometricAvailable()\n\t\tif (isSecondStep) {\n\t\t\tviewBinding.layoutPassword.helperText = getString(R.string.repeat_password)\n\t\t\tviewBinding.buttonNext.setText(R.string.confirm)\n\t\t} else {\n\t\t\tviewBinding.layoutPassword.helperText = getString(R.string.password_length_hint)\n\t\t\tviewBinding.buttonNext.setText(R.string.next)\n\t\t}\n\t}\n\n\tprivate fun isBiometricAvailable(): Boolean {\n\t\treturn packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.protect\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.parsers.util.isNumeric\nimport org.koitharu.kotatsu.parsers.util.md5\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ProtectSetupViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n) : BaseViewModel() {\n\n\tprivate val firstPassword = MutableStateFlow<String?>(null)\n\n\tval isSecondStep = firstPassword.map {\n\t\tit != null\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)\n\tval onPasswordSet = MutableEventFlow<Unit>()\n\tval onPasswordMismatch = MutableEventFlow<Unit>()\n\tval onClearText = MutableEventFlow<Unit>()\n\n\tval isBiometricEnabled\n\t\tget() = settings.isBiometricProtectionEnabled\n\n\tfun onNextClick(password: String) {\n\t\tif (firstPassword.value == null) {\n\t\t\tfirstPassword.value = password\n\t\t\tonClearText.call(Unit)\n\t\t} else {\n\t\t\tif (firstPassword.value == password) {\n\t\t\t\tsettings.appPassword = password.md5()\n\t\t\t\tsettings.isAppPasswordNumeric = password.isNumeric()\n\t\t\t\tonPasswordSet.call(Unit)\n\t\t\t} else {\n\t\t\t\tonPasswordMismatch.call(Unit)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun setBiometricEnabled(isEnabled: Boolean) {\n\t\tsettings.isBiometricProtectionEnabled = isEnabled\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt",
    "content": "package org.koitharu.kotatsu.settings.reader\n\nimport android.content.DialogInterface\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.LayerDrawable\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.TextView\nimport androidx.activity.viewModels\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.graphics.drawable.toDrawable\nimport androidx.core.text.bold\nimport androidx.core.text.buildSpannedString\nimport androidx.core.view.WindowInsetsCompat\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.findKeyByValue\nimport org.koitharu.kotatsu.core.util.ext.getThemeDrawable\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityReaderTapActionsBinding\nimport org.koitharu.kotatsu.reader.domain.TapGridArea\nimport org.koitharu.kotatsu.reader.ui.tapgrid.TapAction\nimport java.util.EnumMap\nimport androidx.appcompat.R as appcompatR\n\n@AndroidEntryPoint\nclass ReaderTapGridConfigActivity : BaseActivity<ActivityReaderTapActionsBinding>(), View.OnClickListener,\n\tView.OnLongClickListener {\n\n\tprivate val viewModel: ReaderTapGridConfigViewModel by viewModels()\n\n\tprivate val controls = EnumMap<TapGridArea, TextView>(TapGridArea::class.java)\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityReaderTapActionsBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tcontrols[TapGridArea.TOP_LEFT] = viewBinding.textViewTopLeft\n\t\tcontrols[TapGridArea.TOP_CENTER] = viewBinding.textViewTopCenter\n\t\tcontrols[TapGridArea.TOP_RIGHT] = viewBinding.textViewTopRight\n\t\tcontrols[TapGridArea.CENTER_LEFT] = viewBinding.textViewCenterLeft\n\t\tcontrols[TapGridArea.CENTER] = viewBinding.textViewCenter\n\t\tcontrols[TapGridArea.CENTER_RIGHT] = viewBinding.textViewCenterRight\n\t\tcontrols[TapGridArea.BOTTOM_LEFT] = viewBinding.textViewBottomLeft\n\t\tcontrols[TapGridArea.BOTTOM_CENTER] = viewBinding.textViewBottomCenter\n\t\tcontrols[TapGridArea.BOTTOM_RIGHT] = viewBinding.textViewBottomRight\n\n\t\tcontrols.forEach { (_, view) ->\n\t\t\tview.setOnClickListener(this)\n\t\t\tview.setOnLongClickListener(this)\n\t\t}\n\n\t\tviewModel.content.observe(this, ::onContentChanged)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.root.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onCreateOptionsMenu(menu: Menu?): Boolean {\n\t\tmenuInflater.inflate(R.menu.opt_tap_grid_config, menu)\n\t\treturn super.onCreateOptionsMenu(menu)\n\t}\n\n\toverride fun onOptionsItemSelected(item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_reset -> {\n\t\t\t\tconfirmReset()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_disable_all -> {\n\t\t\t\tviewModel.disableAll()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onOptionsItemSelected(item)\n\t\t}\n\t}\n\n\toverride fun onClick(v: View) {\n\t\tval area = controls.findKeyByValue(v) ?: return\n\t\tshowActionSelector(area, isLongTap = false)\n\t}\n\n\toverride fun onLongClick(v: View?): Boolean {\n\t\tval area = controls.findKeyByValue(v) ?: return false\n\t\tshowActionSelector(area, isLongTap = true)\n\t\treturn true\n\t}\n\n\tprivate fun onContentChanged(content: Map<TapGridArea, ReaderTapGridConfigViewModel.TapActions>) {\n\t\tcontrols.forEach { (area, view) ->\n\t\t\tval actions = content[area]\n\t\t\tview.text = buildSpannedString {\n\t\t\t\tappendLine(getString(R.string.tap_action))\n\t\t\t\tbold {\n\t\t\t\t\tappendLine(actions?.tapAction.getText())\n\t\t\t\t}\n\t\t\t\tappendLine()\n\t\t\t\tappendLine(getString(R.string.long_tap_action))\n\t\t\t\tbold {\n\t\t\t\t\tappendLine(actions?.longTapAction.getText())\n\t\t\t\t}\n\t\t\t}\n\t\t\tview.background = createBackground(actions?.tapAction)\n\t\t}\n\t}\n\n\t// lint bug\n\tprivate fun TapAction?.getText(): String = if (this != null) {\n\t\tgetString(nameStringResId)\n\t} else {\n\t\tgetString(R.string.none)\n\t}\n\n\tprivate fun showActionSelector(area: TapGridArea, isLongTap: Boolean) {\n\t\tval selectedItem = viewModel.content.value[area]?.run {\n\t\t\tif (isLongTap) longTapAction else tapAction\n\t\t}?.ordinal ?: -1\n\t\tval listener = DialogInterface.OnClickListener { dialog, which ->\n\t\t\tviewModel.setTapAction(area, isLongTap, TapAction.entries.getOrNull(which - 1))\n\t\t\tdialog.dismiss()\n\t\t}\n\t\tval names = arrayOfNulls<String>(TapAction.entries.size + 1)\n\t\tnames[0] = getString(R.string.none)\n\t\tTapAction.entries.forEachIndexed { index, action -> names[index + 1] = getString(action.nameStringResId) }\n\t\tMaterialAlertDialogBuilder(this)\n\t\t\t.setSingleChoiceItems(names, selectedItem + 1, listener)\n\t\t\t.setTitle(if (isLongTap) R.string.long_tap_action else R.string.tap_action)\n\t\t\t.setIcon(R.drawable.ic_tap)\n\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t\t\t.show()\n\t}\n\n\tprivate fun confirmReset() {\n\t\tMaterialAlertDialogBuilder(this)\n\t\t\t.setTitle(R.string.reader_actions)\n\t\t\t.setMessage(R.string.config_reset_confirm)\n\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t\t\t.setPositiveButton(R.string.reset) { _, _ ->\n\t\t\t\tviewModel.reset()\n\t\t\t}.show()\n\t}\n\n\tprivate fun createBackground(action: TapAction?): Drawable? {\n\t\tval ripple = getThemeDrawable(appcompatR.attr.selectableItemBackground)\n\t\treturn if (action == null) {\n\t\t\tripple\n\t\t} else {\n\t\t\tLayerDrawable(arrayOf(ripple, ColorUtils.setAlphaComponent(action.color, 40).toDrawable()))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.reader\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.reader.data.TapGridSettings\nimport org.koitharu.kotatsu.reader.domain.TapGridArea\nimport org.koitharu.kotatsu.reader.ui.tapgrid.TapAction\nimport java.util.EnumMap\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ReaderTapGridConfigViewModel @Inject constructor(\n\tprivate val tapGridSettings: TapGridSettings,\n) : BaseViewModel() {\n\n\tval content = tapGridSettings.observeChanges()\n\t\t.onStart { emit(null) }\n\t\t.map { getData() }\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyMap())\n\n\tfun reset() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\ttapGridSettings.reset()\n\t\t}\n\t}\n\n\tfun disableAll() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\ttapGridSettings.disableAll()\n\t\t}\n\t}\n\n\tfun setTapAction(area: TapGridArea, isLongTap: Boolean, action: TapAction?) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\ttapGridSettings.setTapAction(area, isLongTap, action)\n\t\t}\n\t}\n\n\tprivate fun getData(): Map<TapGridArea, TapActions> {\n\t\tval map = EnumMap<TapGridArea, TapActions>(TapGridArea::class.java)\n\t\tfor (area in TapGridArea.entries) {\n\t\t\tmap[area] = TapActions(\n\t\t\t\ttapAction = tapGridSettings.getTapAction(area, isLongTap = false),\n\t\t\t\tlongTapAction = tapGridSettings.getTapAction(area, isLongTap = true),\n\t\t\t)\n\t\t}\n\t\treturn map\n\t}\n\n\tdata class TapActions(\n\t\tval tapAction: TapAction?,\n\t\tval longTapAction: TapAction?,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/search/SettingsItem.kt",
    "content": "package org.koitharu.kotatsu.settings.search\n\nimport androidx.preference.PreferenceFragmentCompat\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class SettingsItem(\n\tval key: String,\n\tval title: CharSequence,\n\tval breadcrumbs: List<String>,\n\tval fragmentClass: Class<out PreferenceFragmentCompat>,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is SettingsItem && other.key == key && other.fragmentClass == fragmentClass\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/search/SettingsItemAD.kt",
    "content": "package org.koitharu.kotatsu.settings.search\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemPreferenceBinding\n\nfun settingsItemAD(\n\tlistener: OnListItemClickListener<SettingsItem>,\n) = adapterDelegateViewBinding<SettingsItem, SettingsItem, ItemPreferenceBinding>(\n\t{ layoutInflater, parent -> ItemPreferenceBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tAdapterDelegateClickListenerAdapter(this, listener).attach()\n\tval breadcrumbsSeparator = getString(R.string.breadcrumbs_separator)\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.title\n\t\tbinding.textViewSummary.textAndVisible = item.breadcrumbs.joinToString(breadcrumbsSeparator)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/search/SettingsSearchFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.search\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.activityViewModels\nimport androidx.recyclerview.widget.AsyncListDiffer.ListListener\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.databinding.FragmentSearchSuggestionBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\n\n@AndroidEntryPoint\nclass SettingsSearchFragment : BaseFragment<FragmentSearchSuggestionBinding>(),\n\tOnListItemClickListener<SettingsItem>,\n\tListListener<SettingsItem> {\n\n\tprivate val viewModel: SettingsSearchViewModel by activityViewModels()\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSearchSuggestionBinding {\n\t\treturn FragmentSearchSuggestionBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: FragmentSearchSuggestionBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval adapter = BaseListAdapter<SettingsItem>()\n\t\t\t.addDelegate(ListItemType.NAV_ITEM, settingsItemAD(this))\n\t\tadapter.addListListener(this)\n\t\tbinding.root.adapter = adapter\n\t\tbinding.root.setHasFixedSize(true)\n\t\tviewModel.content.observe(viewLifecycleOwner, adapter)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval type = WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(type)\n\t\tv.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\t0,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAll(type)\n\t}\n\n\toverride fun onItemClick(item: SettingsItem, view: View) = viewModel.navigateToPreference(item)\n\n\toverride fun onCurrentListChanged(\n\t\tpreviousList: List<SettingsItem?>,\n\t\tcurrentList: List<SettingsItem?>\n\t) {\n\t\tif (currentList.size != previousList.size) {\n\t\t\t(viewBinding?.root?.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(0, 0)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/search/SettingsSearchHelper.kt",
    "content": "package org.koitharu.kotatsu.settings.search\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport androidx.annotation.XmlRes\nimport androidx.preference.PreferenceFragmentCompat\nimport androidx.preference.PreferenceManager\nimport androidx.preference.PreferenceScreen\nimport androidx.preference.get\nimport dagger.Reusable\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.settings.AppearanceSettingsFragment\nimport org.koitharu.kotatsu.settings.DownloadsSettingsFragment\nimport org.koitharu.kotatsu.settings.ProxySettingsFragment\nimport org.koitharu.kotatsu.settings.ReaderSettingsFragment\nimport org.koitharu.kotatsu.settings.ServicesSettingsFragment\nimport org.koitharu.kotatsu.settings.StorageAndNetworkSettingsFragment\nimport org.koitharu.kotatsu.settings.SuggestionsSettingsFragment\nimport org.koitharu.kotatsu.settings.about.AboutSettingsFragment\nimport org.koitharu.kotatsu.settings.discord.DiscordSettingsFragment\nimport org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment\nimport org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment\nimport org.koitharu.kotatsu.settings.userdata.BackupsSettingsFragment\nimport org.koitharu.kotatsu.settings.userdata.storage.DataCleanupSettingsFragment\nimport javax.inject.Inject\n\n@Reusable\n@SuppressLint(\"RestrictedApi\")\nclass SettingsSearchHelper @Inject constructor(\n    @LocalizedAppContext private val context: Context,\n) {\n\n    fun inflatePreferences(): List<SettingsItem> {\n        val preferenceManager = PreferenceManager(context)\n        val result = ArrayList<SettingsItem>()\n        preferenceManager.inflateTo(result, R.xml.pref_appearance, emptyList(), AppearanceSettingsFragment::class.java)\n        preferenceManager.inflateTo(result, R.xml.pref_sources, emptyList(), SourcesSettingsFragment::class.java)\n        preferenceManager.inflateTo(result, R.xml.pref_reader, emptyList(), ReaderSettingsFragment::class.java)\n        preferenceManager.inflateTo(\n            result,\n            R.xml.pref_network_storage,\n            emptyList(),\n            StorageAndNetworkSettingsFragment::class.java,\n        )\n        preferenceManager.inflateTo(result, R.xml.pref_backups, emptyList(), BackupsSettingsFragment::class.java)\n        preferenceManager.inflateTo(\n            result,\n            R.xml.pref_data_cleanup,\n            listOf(context.getString(R.string.storage_and_network)),\n            DataCleanupSettingsFragment::class.java,\n        )\n        preferenceManager.inflateTo(result, R.xml.pref_downloads, emptyList(), DownloadsSettingsFragment::class.java)\n        preferenceManager.inflateTo(result, R.xml.pref_tracker, emptyList(), TrackerSettingsFragment::class.java)\n        preferenceManager.inflateTo(result, R.xml.pref_services, emptyList(), ServicesSettingsFragment::class.java)\n        preferenceManager.inflateTo(result, R.xml.pref_about, emptyList(), AboutSettingsFragment::class.java)\n        preferenceManager.inflateTo(\n            result,\n            R.xml.pref_backup_periodic,\n            listOf(context.getString(R.string.backup_restore)),\n            PeriodicalBackupSettingsFragment::class.java,\n        )\n        preferenceManager.inflateTo(\n            result,\n            R.xml.pref_proxy,\n            listOf(context.getString(R.string.storage_and_network)),\n            ProxySettingsFragment::class.java,\n        )\n        preferenceManager.inflateTo(\n            result,\n            R.xml.pref_suggestions,\n            listOf(context.getString(R.string.services)),\n            SuggestionsSettingsFragment::class.java,\n        )\n        preferenceManager.inflateTo(\n            result,\n            R.xml.pref_discord,\n            listOf(context.getString(R.string.services)),\n            DiscordSettingsFragment::class.java,\n        )\n        preferenceManager.inflateTo(\n            result,\n            R.xml.pref_sources,\n            listOf(),\n            SourcesSettingsFragment::class.java,\n        )\n        return result\n    }\n\n    private fun PreferenceManager.inflateTo(\n        result: MutableList<SettingsItem>,\n        @XmlRes resId: Int,\n        breadcrumbs: List<String>,\n        fragmentClass: Class<out PreferenceFragmentCompat>\n    ) {\n        val screen = inflateFromResource(context, resId, null)\n        val screenTitle = screen.title?.toString()\n        screen.inflateTo(\n            result = result,\n            breadcrumbs = if (screenTitle.isNullOrEmpty()) breadcrumbs else breadcrumbs + screenTitle,\n            fragmentClass = fragmentClass,\n        )\n    }\n\n    private fun PreferenceScreen.inflateTo(\n        result: MutableList<SettingsItem>,\n        breadcrumbs: List<String>,\n        fragmentClass: Class<out PreferenceFragmentCompat>\n    ): Unit = repeat(preferenceCount) { i ->\n        val pref = this[i]\n        if (pref is PreferenceScreen) {\n            val screenTitle = pref.title?.toString()\n            pref.inflateTo(\n                result = result,\n                breadcrumbs = if (screenTitle.isNullOrEmpty()) breadcrumbs else breadcrumbs + screenTitle,\n                fragmentClass = fragmentClass,\n            )\n        } else {\n            result.add(\n                SettingsItem(\n                    key = pref.key ?: return@repeat,\n                    title = pref.title ?: return@repeat,\n                    breadcrumbs = breadcrumbs,\n                    fragmentClass = fragmentClass,\n                ),\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/search/SettingsSearchMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.search\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\n\nclass SettingsSearchMenuProvider(\n\tprivate val viewModel: SettingsSearchViewModel,\n) : MenuProvider, MenuItem.OnActionExpandListener, SearchView.OnQueryTextListener {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_search, menu)\n\t\tval menuItem = menu.findItem(R.id.action_search)\n\t\tmenuItem.setOnActionExpandListener(this)\n\t\tval searchView = menuItem.actionView as SearchView\n\t\tsearchView.setOnQueryTextListener(this)\n\t\tsearchView.queryHint = menuItem.title\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tif (viewModel.isSearchActive.value) {\n\t\t\tval menuItem = menu.findItem(R.id.action_search)\n\t\t\tmenuItem.expandActionView()\n\t\t\tval searchView = menuItem.actionView as SearchView\n\t\t\tsearchView.setQuery(viewModel.currentQuery, false)\n\t\t}\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = false\n\n\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\tviewModel.startSearch()\n\t\treturn true\n\t}\n\n\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean {\n\t\tviewModel.discardSearch()\n\t\treturn true\n\t}\n\n\toverride fun onQueryTextSubmit(query: String?): Boolean {\n\t\treturn true\n\t}\n\n\toverride fun onQueryTextChange(newText: String?): Boolean {\n\t\tviewModel.onQueryChanged(newText.orEmpty())\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/search/SettingsSearchViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.search\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SettingsSearchViewModel @Inject constructor(\n\tprivate val searchHelper: SettingsSearchHelper,\n) : BaseViewModel() {\n\n\tprivate val query = MutableStateFlow<String?>(null)\n\tprivate val allSettings by lazy {\n\t\tsearchHelper.inflatePreferences()\n\t}\n\n\tval content = query.map { q ->\n\t\tif (q == null) {\n\t\t\temptyList()\n\t\t} else {\n\t\t\tallSettings.filter { it.title.contains(q, ignoreCase = true) }\n\t\t}\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())\n\n\tval isSearchActive = query.map {\n\t\tit != null\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, query.value != null)\n\n\tval onNavigateToPreference = MutableEventFlow<SettingsItem>()\n\tval currentQuery: String\n\t\tget() = query.value.orEmpty()\n\n\tfun onQueryChanged(value: String) {\n\t\tif (query.value != null) {\n\t\t\tquery.value = value\n\t\t}\n\t}\n\n\tfun discardSearch() {\n\t\tquery.value = null\n\t}\n\n\tfun startSearch() {\n\t\tquery.value = query.value.orEmpty()\n\t}\n\n\tfun navigateToPreference(item: SettingsItem) {\n\t\tdiscardSearch()\n\t\tonNavigateToPreference.call(item)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsExt.kt",
    "content": "package org.koitharu.kotatsu.settings.sources\n\nimport android.view.inputmethod.EditorInfo\nimport androidx.preference.EditTextPreference\nimport androidx.preference.ListPreference\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport androidx.preference.SwitchPreferenceCompat\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.EmptyMangaRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.parsers.config.ConfigKey\nimport org.koitharu.kotatsu.parsers.network.UserAgents\nimport org.koitharu.kotatsu.parsers.util.mapToArray\nimport org.koitharu.kotatsu.settings.utils.AutoCompleteTextViewPreference\nimport org.koitharu.kotatsu.settings.utils.EditTextBindListener\nimport org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider\nimport org.koitharu.kotatsu.settings.utils.validation.DomainValidator\nimport org.koitharu.kotatsu.settings.utils.validation.HeaderValidator\n\nfun PreferenceFragmentCompat.addPreferencesFromRepository(repository: MangaRepository) = when (repository) {\n\tis ParserMangaRepository -> addPreferencesFromParserRepository(repository)\n\tis EmptyMangaRepository -> addPreferencesFromEmptyRepository()\n\telse -> Unit\n}\n\nprivate fun PreferenceFragmentCompat.addPreferencesFromParserRepository(repository: ParserMangaRepository) {\n\taddPreferencesFromResource(R.xml.pref_source_parser)\n\tval configKeys = repository.getConfigKeys()\n\tval screen = preferenceScreen\n\tfor (key in configKeys) {\n\t\tval preference: Preference = when (key) {\n\t\t\tis ConfigKey.Domain -> {\n\t\t\t\tval presetValues = key.presetValues\n\t\t\t\tif (presetValues.size <= 1) {\n\t\t\t\t\tEditTextPreference(screen.context)\n\t\t\t\t} else {\n\t\t\t\t\tAutoCompleteTextViewPreference(screen.context).apply {\n\t\t\t\t\t\tentries = presetValues.toStringArray()\n\t\t\t\t\t}\n\t\t\t\t}.apply {\n\t\t\t\t\tsummaryProvider = EditTextDefaultSummaryProvider(key.defaultValue)\n\t\t\t\t\tsetOnBindEditTextListener(\n\t\t\t\t\t\tEditTextBindListener(\n\t\t\t\t\t\t\tinputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI,\n\t\t\t\t\t\t\thint = key.defaultValue,\n\t\t\t\t\t\t\tvalidator = DomainValidator(),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tsetTitle(R.string.domain)\n\t\t\t\t\tsetDialogTitle(R.string.domain)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tis ConfigKey.UserAgent -> {\n\t\t\t\tAutoCompleteTextViewPreference(screen.context).apply {\n\t\t\t\t\tentries = arrayOf(\n\t\t\t\t\t\tUserAgents.FIREFOX_MOBILE,\n\t\t\t\t\t\tUserAgents.CHROME_MOBILE,\n\t\t\t\t\t\tUserAgents.FIREFOX_DESKTOP,\n\t\t\t\t\t\tUserAgents.CHROME_DESKTOP,\n\t\t\t\t\t)\n\t\t\t\t\tsummaryProvider = EditTextDefaultSummaryProvider(key.defaultValue)\n\t\t\t\t\tsetOnBindEditTextListener(\n\t\t\t\t\t\tEditTextBindListener(\n\t\t\t\t\t\t\tinputType = EditorInfo.TYPE_CLASS_TEXT,\n\t\t\t\t\t\t\thint = key.defaultValue,\n\t\t\t\t\t\t\tvalidator = HeaderValidator(),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\tsetTitle(R.string.user_agent)\n\t\t\t\t\tsetDialogTitle(R.string.user_agent)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tis ConfigKey.ShowSuspiciousContent -> {\n\t\t\t\tSwitchPreferenceCompat(screen.context).apply {\n\t\t\t\t\tsetDefaultValue(key.defaultValue)\n\t\t\t\t\tsetTitle(R.string.show_suspicious_content)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tis ConfigKey.SplitByTranslations -> {\n\t\t\t\tSwitchPreferenceCompat(screen.context).apply {\n\t\t\t\t\tsetDefaultValue(key.defaultValue)\n\t\t\t\t\tsetTitle(R.string.split_by_translations)\n\t\t\t\t\tsetSummary(R.string.split_by_translations_summary)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tis ConfigKey.PreferredImageServer -> {\n\t\t\t\tListPreference(screen.context).apply {\n\t\t\t\t\tentries = key.presetValues.values.mapToArray {\n\t\t\t\t\t\tit ?: context.getString(R.string.automatic)\n\t\t\t\t\t}\n\t\t\t\t\tentryValues = key.presetValues.keys.mapToArray { it.orEmpty() }\n\t\t\t\t\tsetDefaultValue(key.defaultValue.orEmpty())\n\t\t\t\t\tsetTitle(R.string.image_server)\n\t\t\t\t\tsetDialogTitle(R.string.image_server)\n\t\t\t\t\tsummaryProvider = ListPreference.SimpleSummaryProvider.getInstance()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpreference.isIconSpaceReserved = false\n\t\tpreference.key = key.key\n\t\tpreference.order = 10\n\t\tscreen.addPreference(preference)\n\t}\n}\n\nprivate fun PreferenceFragmentCompat.addPreferencesFromEmptyRepository() {\n\tval preference = Preference(requireContext())\n\tpreference.setIcon(R.drawable.ic_alert_outline)\n\tpreference.isPersistent = false\n\tpreference.isSelectable = false\n\tpreference.order = 200\n\tpreference.setSummary(R.string.unsupported_source)\n\tpreferenceScreen.addPreference(preference)\n}\n\nprivate fun Array<out String?>.toStringArray(): Array<String> {\n\treturn Array(size) { i -> this[i].orEmpty() }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.sources\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.appcompat.app.AlertDialog\nimport androidx.fragment.app.viewModels\nimport androidx.preference.EditTextPreference\nimport androidx.preference.EditTextPreferenceDialogFragmentCompat\nimport androidx.preference.Preference\nimport androidx.preference.SwitchPreferenceCompat\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.filterNotNull\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.parser.EmptyMangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.SourceSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.withArgs\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport java.io.File\n\n@AndroidEntryPoint\nclass SourceSettingsFragment : BasePreferenceFragment(0), Preference.OnPreferenceChangeListener {\n\n\tprivate val viewModel: SourceSettingsViewModel by viewModels()\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tcontext?.let { ctx ->\n\t\t\tsetTitle(viewModel.source.getTitle(ctx))\n\t\t}\n\t\tviewModel.onResume()\n\t}\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\tpreferenceManager.sharedPreferencesName = viewModel.source.name.replace(File.separatorChar, '$')\n\t\taddPreferencesFromResource(R.xml.pref_source)\n\t\taddPreferencesFromRepository(viewModel.repository)\n\t\tval isValidSource = viewModel.repository !is EmptyMangaRepository\n\n\t\tfindPreference<SwitchPreferenceCompat>(KEY_ENABLE)?.run {\n\t\t\tisVisible = isValidSource && !settings.isAllSourcesEnabled\n\t\t\tonPreferenceChangeListener = this@SourceSettingsFragment\n\t\t}\n\t\tfindPreference<Preference>(KEY_AUTH)?.run {\n\t\t\tval authProvider = (viewModel.repository as? ParserMangaRepository)?.getAuthProvider()\n\t\t\tisVisible = authProvider != null\n\t\t}\n\t\tfindPreference<Preference>(SourceSettings.KEY_SLOWDOWN)?.isVisible = isValidSource\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tviewModel.isAuthorized.filterNotNull().observe(viewLifecycleOwner) { isAuthorized ->\n\t\t\tfindPreference<Preference>(KEY_AUTH)?.isEnabled = !isAuthorized\n\t\t}\n\t\tviewModel.username.observe(viewLifecycleOwner) { username ->\n\t\t\tfindPreference<Preference>(KEY_AUTH)?.summary = username?.let {\n\t\t\t\tgetString(R.string.logged_in_as, it)\n\t\t\t}\n\t\t}\n\t\tviewModel.onError.observeEvent(\n\t\t\tviewLifecycleOwner,\n\t\t\tSnackbarErrorObserver(\n\t\t\t\tlistView,\n\t\t\t\tthis,\n\t\t\t\texceptionResolver,\n\t\t\t) { viewModel.onResume() },\n\t\t)\n\t\tviewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->\n\t\t\tfindPreference<Preference>(KEY_AUTH)?.isEnabled = !isLoading\n\t\t}\n\t\tviewModel.isEnabled.observe(viewLifecycleOwner) { enabled ->\n\t\t\tfindPreference<SwitchPreferenceCompat>(KEY_ENABLE)?.isChecked = enabled\n\t\t}\n\t\tviewModel.browserUrl.observe(viewLifecycleOwner) {\n\t\t\tfindPreference<Preference>(AppSettings.KEY_OPEN_BROWSER)?.run {\n\t\t\t\tisVisible = it != null\n\t\t\t\tsummary = it\n\t\t\t}\n\t\t}\n\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(listView))\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tKEY_AUTH -> {\n\t\t\t\trouter.openSourceAuth(viewModel.source)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_OPEN_BROWSER -> {\n\t\t\t\trouter.openBrowser(\n\t\t\t\t\turl = viewModel.browserUrl.value ?: return false,\n\t\t\t\t\tsource = viewModel.source,\n\t\t\t\t\ttitle = viewModel.source.getTitle(preference.context),\n\t\t\t\t)\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_COOKIES_CLEAR -> {\n\t\t\t\tviewModel.clearCookies()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\toverride fun onDisplayPreferenceDialog(preference: Preference) {\n\t\tif (preference.key == SourceSettings.KEY_DOMAIN) {\n\t\t\tif (parentFragmentManager.findFragmentByTag(DomainDialogFragment.DIALOG_FRAGMENT_TAG) != null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval f = DomainDialogFragment.newInstance(preference.key)\n\t\t\t@Suppress(\"DEPRECATION\")\n\t\t\tf.setTargetFragment(this, 0)\n\t\t\tf.show(parentFragmentManager, DomainDialogFragment.DIALOG_FRAGMENT_TAG)\n\t\t\treturn\n\t\t}\n\t\tsuper.onDisplayPreferenceDialog(preference)\n\t}\n\n\toverride fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {\n\t\twhen (preference.key) {\n\t\t\tKEY_ENABLE -> viewModel.setEnabled(newValue == true)\n\t\t\telse -> return false\n\t\t}\n\t\treturn true\n\t}\n\n\tclass DomainDialogFragment : EditTextPreferenceDialogFragmentCompat() {\n\n\t\toverride fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {\n\t\t\tsuper.onPrepareDialogBuilder(builder)\n\t\t\tbuilder.setNeutralButton(R.string.reset) { _, _ ->\n\t\t\t\tresetValue()\n\t\t\t}\n\t\t}\n\n\t\tprivate fun resetValue() {\n\t\t\tval editTextPreference = preference as EditTextPreference\n\t\t\tif (editTextPreference.callChangeListener(\"\")) {\n\t\t\t\teditTextPreference.text = \"\"\n\t\t\t}\n\t\t}\n\n\t\tcompanion object {\n\n\t\t\tconst val DIALOG_FRAGMENT_TAG: String = \"androidx.preference.PreferenceFragment.DIALOG\"\n\n\t\t\tfun newInstance(key: String) = DomainDialogFragment().withArgs(1) {\n\t\t\t\tputString(ARG_KEY, key)\n\t\t\t}\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val KEY_AUTH = \"auth\"\n\t\tprivate const val KEY_ENABLE = \"enable\"\n\n\t\tfun newInstance(source: MangaSource) = SourceSettingsFragment().withArgs(1) {\n\t\t\tputString(AppRouter.KEY_SOURCE, source.name)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourceSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.sources\n\nimport android.content.SharedPreferences\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport okhttp3.HttpUrl\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.MangaSource\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.core.parser.CachingMangaRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.prefs.SourceSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.parsers.MangaParserAuthProvider\nimport org.koitharu.kotatsu.parsers.exception.AuthRequiredException\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SourceSettingsViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tmangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val cookieJar: MutableCookieJar,\n\tprivate val mangaSourcesRepository: MangaSourcesRepository,\n) : BaseViewModel(), SharedPreferences.OnSharedPreferenceChangeListener {\n\n\tval source = MangaSource(savedStateHandle.get<String>(AppRouter.KEY_SOURCE))\n\tval repository = mangaRepositoryFactory.create(source)\n\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\tval username = MutableStateFlow<String?>(null)\n\tval isAuthorized = MutableStateFlow<Boolean?>(null)\n\tval browserUrl = MutableStateFlow<String?>(null)\n\tval isEnabled = mangaSourcesRepository.observeIsEnabled(source)\n\tprivate var usernameLoadJob: Job? = null\n\n\tinit {\n\t\twhen (repository) {\n\t\t\tis ParserMangaRepository -> {\n\t\t\t\tbrowserUrl.value = \"https://${repository.domain}\"\n\t\t\t\trepository.getConfig().subscribe(this)\n\t\t\t\tloadUsername(repository.getAuthProvider())\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCleared() {\n\t\twhen (repository) {\n\t\t\tis ParserMangaRepository -> {\n\t\t\t\trepository.getConfig().unsubscribe(this)\n\t\t\t}\n\t\t}\n\t\tsuper.onCleared()\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\tif (repository is CachingMangaRepository) {\n\t\t\tif (key != SourceSettings.KEY_SLOWDOWN && key != SourceSettings.KEY_SORT_ORDER) {\n\t\t\t\trepository.invalidateCache()\n\t\t\t}\n\t\t}\n\t\tif (repository is ParserMangaRepository) {\n\t\t\tif (key == SourceSettings.KEY_DOMAIN) {\n\t\t\t\tbrowserUrl.value = \"https://${repository.domain}\"\n\t\t\t}\n\t\t}\n\t}\n\n\tfun onResume() {\n\t\tif (usernameLoadJob?.isActive != true && repository is ParserMangaRepository) {\n\t\t\tloadUsername(repository.getAuthProvider())\n\t\t}\n\t}\n\n\tfun clearCookies() {\n\t\tif (repository !is ParserMangaRepository) return\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval url = HttpUrl.Builder()\n\t\t\t\t.scheme(\"https\")\n\t\t\t\t.host(repository.domain)\n\t\t\t\t.build()\n\t\t\tcookieJar.removeCookies(url, null)\n\t\t\tonActionDone.call(ReversibleAction(R.string.cookies_cleared, null))\n\t\t\tloadUsername(repository.getAuthProvider())\n\t\t}\n\t}\n\n\tfun setEnabled(value: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tmangaSourcesRepository.setSourcesEnabled(setOf(source), value)\n\t\t}\n\t}\n\n\tprivate fun loadUsername(authProvider: MangaParserAuthProvider?) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\ttry {\n\t\t\t\tusername.value = null\n\t\t\t\tisAuthorized.value = null\n\t\t\t\tisAuthorized.value = authProvider?.isAuthorized()\n\t\t\t\tusername.value = authProvider?.getUsername()\n\t\t\t} catch (_: AuthRequiredException) {\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.sources\n\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport androidx.preference.ListPreference\nimport androidx.preference.Preference\nimport androidx.preference.TwoStatePreference\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat\nimport org.koitharu.kotatsu.explore.data.SourcesSortOrder\nimport org.koitharu.kotatsu.parsers.util.names\n\n@AndroidEntryPoint\nclass SourcesSettingsFragment : BasePreferenceFragment(R.string.remote_sources),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\tprivate val viewModel by viewModels<SourcesSettingsViewModel>()\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_sources)\n\t\tfindPreference<ListPreference>(AppSettings.KEY_SOURCES_ORDER)?.run {\n\t\t\tentryValues = SourcesSortOrder.entries.names()\n\t\t\tentries = SourcesSortOrder.entries.map { context.getString(it.titleResId) }.toTypedArray()\n\t\t\tsetDefaultValueCompat(SourcesSortOrder.MANUAL.name)\n\t\t}\n        findPreference<ListPreference>(AppSettings.KEY_INCOGNITO_NSFW)?.run {\n            entryValues = TriStateOption.entries.names()\n            setDefaultValueCompat(TriStateOption.ASK.name)\n        }\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tfindPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.let { pref ->\n\t\t\tviewModel.enabledSourcesCount.observe(viewLifecycleOwner) {\n\t\t\t\tpref.summary = if (it >= 0) {\n\t\t\t\t\tresources.getQuantityStringSafe(R.plurals.items, it, it)\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfindPreference<Preference>(AppSettings.KEY_SOURCES_CATALOG)?.let { pref ->\n\t\t\tviewModel.availableSourcesCount.observe(viewLifecycleOwner) {\n\t\t\t\tpref.summary = when {\n\t\t\t\t\tit == 0 -> getString(R.string.all_sources_enabled)\n\t\t\t\t\tit > 0 -> getString(R.string.available_d, it)\n\t\t\t\t\telse -> null\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfindPreference<TwoStatePreference>(AppSettings.KEY_HANDLE_LINKS)?.let { pref ->\n\t\t\tviewModel.isLinksEnabled.observe(viewLifecycleOwner) {\n\t\t\t\tpref.isChecked = it\n\t\t\t}\n\t\t}\n\t\tupdateEnableAllDependencies()\n\t\tsettings.subscribe(this)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {\n\t\tAppSettings.KEY_SOURCES_CATALOG -> {\n\t\t\trouter.openSourcesCatalog()\n\t\t\ttrue\n\t\t}\n\n\t\tAppSettings.KEY_HANDLE_LINKS -> {\n\t\t\tviewModel.setLinksEnabled((preference as TwoStatePreference).isChecked)\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onPreferenceTreeClick(preference)\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_SOURCES_ENABLED_ALL -> updateEnableAllDependencies()\n\t\t}\n\t}\n\n\tprivate fun updateEnableAllDependencies() {\n\t\tfindPreference<Preference>(AppSettings.KEY_SOURCES_CATALOG)?.isEnabled = !settings.isAllSourcesEnabled\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.sources\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT\nimport android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED\nimport android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SourcesSettingsViewModel @Inject constructor(\n\tsourcesRepository: MangaSourcesRepository,\n\t@ApplicationContext private val context: Context,\n) : BaseViewModel() {\n\n\tprivate val linksHandlerActivity = ComponentName(context, \"org.koitharu.kotatsu.details.ui.DetailsByLinkActivity\")\n\n\tval enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)\n\n\tval availableSourcesCount = sourcesRepository.observeAvailableSourcesCount()\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)\n\n\tval isLinksEnabled = MutableStateFlow(isLinksEnabled())\n\n\tfun setLinksEnabled(isEnabled: Boolean) {\n\t\tcontext.packageManager.setComponentEnabledSetting(\n\t\t\tlinksHandlerActivity,\n\t\t\tif (isEnabled) COMPONENT_ENABLED_STATE_ENABLED else COMPONENT_ENABLED_STATE_DISABLED,\n\t\t\tPackageManager.DONT_KILL_APP,\n\t\t)\n\t\tisLinksEnabled.value = isLinksEnabled()\n\t}\n\n\tprivate fun isLinksEnabled(): Boolean {\n\t\tval state = context.packageManager.getComponentEnabledSetting(linksHandlerActivity)\n\t\treturn state == COMPONENT_ENABLED_STATE_ENABLED || state == COMPONENT_ENABLED_STATE_DEFAULT\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.adapter\n\nimport org.koitharu.kotatsu.core.ui.ReorderableListAdapter\nimport org.koitharu.kotatsu.settings.sources.model.SourceConfigItem\n\nclass SourceConfigAdapter(\n\tlistener: SourceConfigListener,\n) : ReorderableListAdapter<SourceConfigItem>() {\n\n\tinit {\n\t\twith(delegatesManager) {\n\t\t\taddDelegate(sourceConfigItemDelegate2(listener))\n\t\t\taddDelegate(sourceConfigEmptySearchDelegate())\n\t\t\taddDelegate(sourceConfigTipDelegate(listener))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.adapter\n\nimport android.view.View\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getSummary\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.ui.list.OnTipCloseListener\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.databinding.ItemSourceConfigBinding\nimport org.koitharu.kotatsu.databinding.ItemTipBinding\nimport org.koitharu.kotatsu.settings.sources.model.SourceConfigItem\n\nfun sourceConfigItemDelegate2(\n\tlistener: SourceConfigListener,\n) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigBinding>(\n\t{ layoutInflater, parent ->\n\t\tItemSourceConfigBinding.inflate(\n\t\t\tlayoutInflater,\n\t\t\tparent,\n\t\t\tfalse,\n\t\t)\n\t},\n) {\n\n\tval iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small)\n\tval eventListener = View.OnClickListener { v ->\n\t\twhen (v.id) {\n\t\t\tR.id.imageView_add -> listener.onItemEnabledChanged(item, true)\n\t\t\tR.id.imageView_remove -> listener.onItemEnabledChanged(item, false)\n\t\t\tR.id.imageView_menu -> showSourceMenu(v, item, listener)\n\t\t}\n\t}\n\tbinding.imageViewRemove.setOnClickListener(eventListener)\n\tbinding.imageViewAdd.setOnClickListener(eventListener)\n\tbinding.imageViewMenu.setOnClickListener(eventListener)\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.source.getTitle(context)\n\t\tbinding.imageViewAdd.isGone = item.isEnabled || !item.isAvailable\n\t\tbinding.imageViewRemove.isVisible = item.isEnabled && item.isDisableAvailable\n\t\tbinding.imageViewMenu.isVisible = item.isEnabled\n\t\tbinding.textViewTitle.drawableStart = if (item.isPinned) iconPinned else null\n\t\tbinding.textViewDescription.text = item.source.getSummary(context)\n\t\tbinding.imageViewIcon.setImageAsync(item.source)\n\t}\n}\n\nfun sourceConfigTipDelegate(\n\tlistener: OnTipCloseListener<SourceConfigItem.Tip>,\n) = adapterDelegateViewBinding<SourceConfigItem.Tip, SourceConfigItem, ItemTipBinding>(\n\t{ layoutInflater, parent -> ItemTipBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.buttonClose.setOnClickListener {\n\t\tlistener.onCloseTip(item)\n\t}\n\n\tbind {\n\t\tbinding.imageViewIcon.setImageResource(item.iconResId)\n\t\tbinding.textView.setText(item.textResId)\n\t}\n}\n\nfun sourceConfigEmptySearchDelegate() =\n\tadapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>(\n\t\tR.layout.item_sources_empty,\n\t) { }\n\nprivate fun showSourceMenu(\n\tanchor: View,\n\titem: SourceConfigItem.SourceItem,\n\tlistener: SourceConfigListener,\n) {\n\tval menu = PopupMenu(anchor.context, anchor)\n\tmenu.inflate(R.menu.popup_source_config)\n\tmenu.menu.findItem(R.id.action_shortcut)\n\t\t?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(anchor.context)\n\tmenu.menu.findItem(R.id.action_pin)?.isVisible = item.isEnabled\n\tmenu.menu.findItem(R.id.action_pin)?.isChecked = item.isPinned\n\tmenu.menu.findItem(R.id.action_lift)?.isVisible = item.isDraggable\n\tmenu.setOnMenuItemClickListener {\n\t\twhen (it.itemId) {\n\t\t\tR.id.action_settings -> listener.onItemSettingsClick(item)\n\t\t\tR.id.action_lift -> listener.onItemLiftClick(item)\n\t\t\tR.id.action_shortcut -> listener.onItemShortcutClick(item)\n\t\t\tR.id.action_pin -> listener.onItemPinClick(item)\n\t\t}\n\t\ttrue\n\t}\n\tmenu.show()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigListener.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.adapter\n\nimport org.koitharu.kotatsu.core.ui.list.OnTipCloseListener\nimport org.koitharu.kotatsu.settings.sources.model.SourceConfigItem\n\ninterface SourceConfigListener : OnTipCloseListener<SourceConfigItem.Tip> {\n\n\tfun onItemSettingsClick(item: SourceConfigItem.SourceItem)\n\n\tfun onItemLiftClick(item: SourceConfigItem.SourceItem)\n\n\tfun onItemShortcutClick(item: SourceConfigItem.SourceItem)\n\n\tfun onItemPinClick(item: SourceConfigItem.SourceItem)\n\n\tfun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/auth/SourceAuthActivity.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.auth\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.browser.BaseBrowserActivity\nimport org.koitharu.kotatsu.browser.BrowserCallback\nimport org.koitharu.kotatsu.browser.BrowserClient\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.parser.ParserMangaRepository\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.parsers.MangaParserAuthProvider\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\n\n@AndroidEntryPoint\nclass SourceAuthActivity : BaseBrowserActivity(), BrowserCallback {\n\n\tprivate lateinit var authProvider: MangaParserAuthProvider\n\n\tprivate var authCheckJob: Job? = null\n\n\toverride fun onCreate2(savedInstanceState: Bundle?, source: MangaSource, repository: ParserMangaRepository?) {\n\t\tif (repository == null) {\n\t\t\tfinishAfterTransition()\n\t\t\treturn\n\t\t}\n\t\tauthProvider = repository.getAuthProvider() ?: run {\n\t\t\tToast.makeText(\n\t\t\t\tthis,\n\t\t\t\tgetString(R.string.auth_not_supported_by, source.getTitle(this)),\n\t\t\t\tToast.LENGTH_SHORT,\n\t\t\t).show()\n\t\t\tfinishAfterTransition()\n\t\t\treturn\n\t\t}\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tviewBinding.webView.webViewClient = BrowserClient(this, adBlock)\n\t\tlifecycleScope.launch {\n\t\t\ttry {\n\t\t\t\tproxyProvider.applyWebViewConfig()\n\t\t\t} catch (e: Exception) {\n\t\t\t\tSnackbar.make(viewBinding.webView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()\n\t\t\t}\n\t\t\tif (savedInstanceState == null) {\n\t\t\t\tval url = authProvider.authUrl\n\t\t\t\tonTitleChanged(\n\t\t\t\t\tsource.getTitle(this@SourceAuthActivity),\n\t\t\t\t\tgetString(R.string.loading_),\n\t\t\t\t)\n\t\t\t\tviewBinding.webView.loadUrl(url)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {\n\t\tandroid.R.id.home -> {\n\t\t\tviewBinding.webView.stopLoading()\n\t\t\tsetResult(RESULT_CANCELED)\n\t\t\tfinishAfterTransition()\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onOptionsItemSelected(item)\n\t}\n\n\toverride fun onLoadingStateChanged(isLoading: Boolean) {\n\t\tsuper.onLoadingStateChanged(isLoading)\n\t\tif (isLoading) {\n\t\t\treturn\n\t\t}\n\t\tval prevJob = authCheckJob\n\t\tauthCheckJob = lifecycleScope.launch {\n\t\t\tprevJob?.join()\n\t\t\tval isAuthorized = runCatchingCancellable {\n\t\t\t\tauthProvider.isAuthorized()\n\t\t\t}.getOrDefault(false)\n\t\t\tif (isAuthorized) {\n\t\t\t\tToast.makeText(this@SourceAuthActivity, R.string.auth_complete, Toast.LENGTH_SHORT).show()\n\t\t\t\tsetResult(RESULT_OK)\n\t\t\t\tfinishAfterTransition()\n\t\t\t}\n\t\t}\n\t}\n\n\tclass Contract : ActivityResultContract<MangaSource, Boolean>() {\n\n\t\toverride fun createIntent(context: Context, input: MangaSource) = AppRouter.sourceAuthIntent(context, input)\n\n\t\toverride fun parseResult(resultCode: Int, intent: Intent?) = resultCode == RESULT_OK\n\t}\n\n\tcompanion object {\n\t\tconst val TAG = \"SourceAuthActivity\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItem.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\n\nsealed interface SourceCatalogItem : ListModel {\n\n\tdata class Source(\n\t\tval source: MangaParserSource,\n\t) : SourceCatalogItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is Source && other.source == source\n\t\t}\n\t}\n\n\tdata class Hint(\n\t\t@DrawableRes val icon: Int,\n\t\t@StringRes val title: Int,\n\t\t@StringRes val text: Int,\n\t) : SourceCatalogItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is Hint && other.title == title\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePaddingRelative\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.getSummary\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.ui.image.FaviconDrawable\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.getThemeDimensionPixelOffset\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.databinding.ItemEmptyHintBinding\nimport org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport androidx.appcompat.R as appcompatR\n\nfun sourceCatalogItemSourceAD(\n\tlistener: OnListItemClickListener<SourceCatalogItem.Source>\n) = adapterDelegateViewBinding<SourceCatalogItem.Source, ListModel, ItemSourceCatalogBinding>(\n\t{ layoutInflater, parent ->\n\t\tItemSourceCatalogBinding.inflate(layoutInflater, parent, false)\n\t},\n) {\n\n\tbinding.imageViewAdd.setOnClickListener { v ->\n\t\tlistener.onItemLongClick(item, v)\n\t}\n\tbinding.root.setOnClickListener { v ->\n\t\tlistener.onItemClick(item, v)\n\t}\n\tval basePadding = context.getThemeDimensionPixelOffset(\n\t\tappcompatR.attr.listPreferredItemPaddingEnd,\n\t\tbinding.root.paddingStart,\n\t)\n\tbinding.root.updatePaddingRelative(\n\t\tend = (basePadding - context.resources.getDimensionPixelOffset(R.dimen.margin_small)).coerceAtLeast(0),\n\t)\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.source.getTitle(context)\n\t\tbinding.textViewDescription.text = item.source.getSummary(context)\n\t\tbinding.textViewDescription.drawableStart = if (item.source.isBroken) {\n\t\t\tContextCompat.getDrawable(context, R.drawable.ic_off_small)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t\tFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)\n\t\tbinding.imageViewIcon.setImageAsync(item.source)\n\t}\n}\n\nfun sourceCatalogItemHintAD() = adapterDelegateViewBinding<SourceCatalogItem.Hint, ListModel, ItemEmptyHintBinding>(\n\t{ inflater, parent -> ItemEmptyHintBinding.inflate(inflater, parent, false) },\n) {\n\n\tbinding.buttonRetry.isVisible = false\n\n\tbind {\n\t\tbinding.icon.setImageAsync(item.icon)\n\t\tbinding.textPrimary.setText(item.title)\n\t\tbinding.textSecondary.setTextAndVisible(item.text)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogPage.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.ContentType\n\ndata class SourceCatalogPage(\n\tval type: ContentType,\n\tval items: List<SourceCatalogItem>,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is SourceCatalogPage && other.type == type\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any {\n\t\treturn ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.graphics.Insets\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport com.google.android.material.appbar.AppBarLayout\nimport com.google.android.material.chip.Chip\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.combine\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.titleResId\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.FadingAppbarMediator\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView\nimport org.koitharu.kotatsu.core.ui.widgets.ChipsView.ChipModel\nimport org.koitharu.kotatsu.core.util.LocaleComparator\nimport org.koitharu.kotatsu.core.util.ext.getDisplayName\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.toLocale\nimport org.koitharu.kotatsu.databinding.ActivitySourcesCatalogBinding\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.parsers.model.ContentType\n\n@AndroidEntryPoint\nclass SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),\n\tOnListItemClickListener<SourceCatalogItem.Source>,\n\tAppBarOwner,\n\tMenuItem.OnActionExpandListener,\n\tChipsView.OnChipClickListener {\n\n\toverride val appBar: AppBarLayout\n\t\tget() = viewBinding.appbar\n\n\tprivate val viewModel by viewModels<SourcesCatalogViewModel>()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivitySourcesCatalogBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tval sourcesAdapter = SourcesCatalogAdapter(this)\n\t\twith(viewBinding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t\tadapter = sourcesAdapter\n\t\t}\n\t\tviewBinding.chipsFilter.onChipClickListener = this\n\t\tFadingAppbarMediator(viewBinding.appbar, viewBinding.toolbar).bind()\n\t\tviewModel.content.observe(this, sourcesAdapter)\n\t\tviewModel.onActionDone.observeEvent(\n\t\t\tthis,\n\t\t\tReversibleActionObserver(viewBinding.recyclerView),\n\t\t)\n\t\tcombine(viewModel.appliedFilter, viewModel.hasNewSources, viewModel.contentTypes, ::Triple).observe(this) {\n\t\t\tupdateFilers(it.first, it.second, it.third)\n\t\t}\n\t\taddMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this))\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tviewBinding.recyclerView.updatePadding(\n\t\t\tleft = bars.left,\n\t\t\tright = bars.right,\n\t\t\tbottom = bars.bottom,\n\t\t)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = bars.left,\n\t\t\tright = bars.right,\n\t\t\ttop = bars.top,\n\t\t)\n\t\treturn WindowInsetsCompat.Builder(insets)\n\t\t\t.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)\n\t\t\t.build()\n\t}\n\n\toverride fun onChipClick(chip: Chip, data: Any?) {\n\t\twhen (data) {\n\t\t\tis ContentType -> viewModel.setContentType(data, !chip.isChecked)\n\t\t\tis Boolean -> viewModel.setNewOnly(!chip.isChecked)\n\t\t\telse -> showLocalesMenu(chip)\n\t\t}\n\t}\n\n\toverride fun onItemClick(item: SourceCatalogItem.Source, view: View) {\n\t\trouter.openList(item.source, null, null)\n\t}\n\n\toverride fun onItemLongClick(item: SourceCatalogItem.Source, view: View): Boolean {\n\t\tviewModel.addSource(item.source)\n\t\treturn false\n\t}\n\n\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\tval sq = (item.actionView as? SearchView)?.query?.trim()?.toString().orEmpty()\n\t\tviewModel.performSearch(sq)\n\t\treturn true\n\t}\n\n\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean {\n\t\tviewModel.performSearch(null)\n\t\treturn true\n\t}\n\n\tprivate fun updateFilers(\n\t\tappliedFilter: SourcesCatalogFilter,\n\t\thasNewSources: Boolean,\n\t\tcontentTypes: List<ContentType>,\n\t) {\n\t\tval chips = ArrayList<ChipModel>(contentTypes.size + 2)\n\t\tchips += ChipModel(\n\t\t\ttitle = appliedFilter.locale?.toLocale().getDisplayName(this),\n\t\t\ticon = R.drawable.ic_language,\n\t\t\tisDropdown = true,\n\t\t)\n\t\tif (hasNewSources) {\n\t\t\tchips += ChipModel(\n\t\t\t\ttitle = getString(R.string._new),\n\t\t\t\ticon = R.drawable.ic_updated,\n\t\t\t\tisChecked = appliedFilter.isNewOnly,\n\t\t\t\tdata = true,\n\t\t\t)\n\t\t}\n\t\tcontentTypes.mapTo(chips) { type ->\n\t\t\tChipModel(\n\t\t\t\ttitle = getString(type.titleResId),\n\t\t\t\tisChecked = type in appliedFilter.types,\n\t\t\t\tdata = type,\n\t\t\t)\n\t\t}\n\t\tviewBinding.chipsFilter.setChips(chips)\n\t}\n\n\tprivate fun showLocalesMenu(anchor: View) {\n\t\tval locales = viewModel.locales.mapTo(ArrayList(viewModel.locales.size)) {\n\t\t\tit to it?.toLocale()\n\t\t}\n\t\tlocales.sortWith(compareBy(nullsFirst(LocaleComparator())) { it.second })\n\t\tval menu = PopupMenu(this, anchor)\n\t\tfor ((i, lc) in locales.withIndex()) {\n\t\t\tmenu.menu.add(Menu.NONE, Menu.NONE, i, lc.second.getDisplayName(this))\n\t\t}\n\t\tmenu.setOnMenuItemClickListener {\n\t\t\tviewModel.setLocale(locales.getOrNull(it.order)?.first)\n\t\t\ttrue\n\t\t}\n\t\tmenu.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogAdapter.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport android.content.Context\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\nclass SourcesCatalogAdapter(\n\tlistener: OnListItemClickListener<SourceCatalogItem.Source>,\n) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {\n\n\tinit {\n\t\taddDelegate(ListItemType.CHAPTER_LIST, sourceCatalogItemSourceAD(listener))\n\t\taddDelegate(ListItemType.HINT_EMPTY, sourceCatalogItemHintAD())\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t}\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn (items.getOrNull(position) as? SourceCatalogItem.Source)?.source?.getTitle(context)?.take(1)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogFilter.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport org.koitharu.kotatsu.parsers.model.ContentType\n\ndata class SourcesCatalogFilter(\n\tval types: Set<ContentType>,\n\tval locale: String?,\n\tval isNewOnly: Boolean,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport android.app.Activity\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\n\nclass SourcesCatalogMenuProvider(\n\tprivate val activity: Activity,\n\tprivate val viewModel: SourcesCatalogViewModel,\n\tprivate val expandListener: MenuItem.OnActionExpandListener,\n) : MenuProvider,\n\tMenuItem.OnActionExpandListener,\n\tSearchView.OnQueryTextListener {\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_sources_catalog, menu)\n\t\tval searchMenuItem = menu.findItem(R.id.action_search)\n\t\tsearchMenuItem.setOnActionExpandListener(this)\n\t\tval searchView = searchMenuItem.actionView as SearchView\n\t\tsearchView.setOnQueryTextListener(this)\n\t\tsearchView.setIconifiedByDefault(false)\n\t\tsearchView.queryHint = searchMenuItem.title\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = false\n\n\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\t(activity as? AppBarOwner)?.appBar?.setExpanded(true, true)\n\t\treturn expandListener.onMenuItemActionExpand(item)\n\t}\n\n\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean {\n\t\t(item.actionView as SearchView).setQuery(\"\", false)\n\t\treturn expandListener.onMenuItemActionCollapse(item)\n\t}\n\n\toverride fun onQueryTextSubmit(query: String?): Boolean = false\n\n\toverride fun onQueryTextChange(newText: String?): Boolean {\n\t\tviewModel.performSearch(newText?.trim().orEmpty())\n\t\treturn true\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.catalog\n\nimport androidx.annotation.WorkerThread\nimport androidx.lifecycle.viewModelScope\nimport androidx.room.invalidationTrackerFlow\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.TABLE_SOURCES\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.mapSortedByCount\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.explore.data.SourcesSortOrder\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.parsers.model.ContentType\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport java.util.EnumSet\nimport java.util.Locale\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SourcesCatalogViewModel @Inject constructor(\n\tprivate val repository: MangaSourcesRepository,\n\tdb: MangaDatabase,\n\tsettings: AppSettings,\n) : BaseViewModel() {\n\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\tval locales: Set<String?> = repository.allMangaSources.mapTo(HashSet<String?>()) { it.locale }.also {\n\t\tit.add(null)\n\t}\n\n\tprivate val searchQuery = MutableStateFlow<String?>(null)\n\tval appliedFilter = MutableStateFlow(\n\t\tSourcesCatalogFilter(\n\t\t\ttypes = emptySet(),\n\t\t\tlocale = Locale.getDefault().language.takeIf { it in locales },\n\t\t\tisNewOnly = false,\n\t\t),\n\t)\n\n\tval hasNewSources = repository.observeHasNewSources()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)\n\n\tval contentTypes = MutableStateFlow<List<ContentType>>(emptyList())\n\n\tval content: StateFlow<List<ListModel>> = combine(\n\t\tsearchQuery,\n\t\tappliedFilter,\n\t\tdb.invalidationTrackerFlow(TABLE_SOURCES),\n\t) { q, f, _ ->\n\t\tbuildSourcesList(f, q)\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tinit {\n\t\trepository.clearNewSourcesBadge()\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tcontentTypes.value = getContentTypes(settings.isNsfwContentDisabled)\n\t\t}\n\t}\n\n\tfun performSearch(query: String?) {\n\t\tsearchQuery.value = query?.trim()\n\t}\n\n\tfun setLocale(value: String?) {\n\t\tappliedFilter.value = appliedFilter.value.copy(locale = value)\n\t}\n\n\tfun addSource(source: MangaSource) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval rollback = repository.setSourcesEnabled(setOf(source), true)\n\t\t\tonActionDone.call(ReversibleAction(R.string.source_enabled, rollback))\n\t\t}\n\t}\n\n\tfun setContentType(value: ContentType, isAdd: Boolean) {\n\t\tval filter = appliedFilter.value\n\t\tval types = EnumSet.noneOf(ContentType::class.java)\n\t\ttypes.addAll(filter.types)\n\t\tif (isAdd) {\n\t\t\ttypes.add(value)\n\t\t} else {\n\t\t\ttypes.remove(value)\n\t\t}\n\t\tappliedFilter.value = filter.copy(types = types)\n\t}\n\n\tfun setNewOnly(value: Boolean) {\n\t\tappliedFilter.value = appliedFilter.value.copy(isNewOnly = value)\n\t}\n\n\tprivate suspend fun buildSourcesList(filter: SourcesCatalogFilter, query: String?): List<SourceCatalogItem> {\n\t\tval sources = repository.queryParserSources(\n\t\t\tisDisabledOnly = true,\n\t\t\tisNewOnly = filter.isNewOnly,\n\t\t\texcludeBroken = false,\n\t\t\ttypes = filter.types,\n\t\t\tquery = query,\n\t\t\tlocale = filter.locale,\n\t\t\tsortOrder = SourcesSortOrder.ALPHABETIC,\n\t\t)\n\t\treturn if (sources.isEmpty()) {\n\t\t\tlistOf(\n\t\t\t\tif (query == null) {\n\t\t\t\t\tSourceCatalogItem.Hint(\n\t\t\t\t\t\ticon = R.drawable.ic_empty_feed,\n\t\t\t\t\t\ttitle = R.string.no_manga_sources,\n\t\t\t\t\t\ttext = R.string.no_manga_sources_catalog_text,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tSourceCatalogItem.Hint(\n\t\t\t\t\t\ticon = R.drawable.ic_empty_feed,\n\t\t\t\t\t\ttitle = R.string.nothing_found,\n\t\t\t\t\t\ttext = R.string.no_manga_sources_found,\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\t\t} else {\n\t\t\tsources.map {\n\t\t\t\tSourceCatalogItem.Source(source = it)\n\t\t\t}\n\t\t}\n\t}\n\n\t@WorkerThread\n\tprivate fun getContentTypes(isNsfwDisabled: Boolean): List<ContentType> {\n\t\tval result = repository.allMangaSources.mapSortedByCount { it.contentType }\n\t\treturn if (isNsfwDisabled) {\n\t\t\tresult.filterNot { it == ContentType.HENTAI }\n\t\t} else {\n\t\t\tresult\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/manage/SourcesListProducer.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.manage\n\nimport android.content.Context\nimport androidx.room.InvalidationTracker\nimport dagger.hilt.android.ViewModelLifecycle\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.filter\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.db.TABLE_SOURCES\nimport org.koitharu.kotatsu.core.model.getTitle\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.model.unwrap\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.lifecycleScope\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.explore.data.SourcesSortOrder\nimport org.koitharu.kotatsu.parsers.model.MangaParserSource\nimport org.koitharu.kotatsu.parsers.util.mapToSet\nimport org.koitharu.kotatsu.settings.sources.model.SourceConfigItem\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass SourcesListProducer @Inject constructor(\n\tlifecycle: ViewModelLifecycle,\n\t@LocalizedAppContext private val context: Context,\n\tprivate val repository: MangaSourcesRepository,\n\tprivate val settings: AppSettings,\n) : InvalidationTracker.Observer(TABLE_SOURCES) {\n\n\tprivate val scope = lifecycle.lifecycleScope\n\tprivate var query: String = \"\"\n\tval list = MutableStateFlow(emptyList<SourceConfigItem>())\n\n\tprivate var job = scope.launch(Dispatchers.Default) {\n\t\tlist.value = buildList()\n\t}\n\n\tinit {\n\t\tsettings.observeChanges()\n\t\t\t.filter { it == AppSettings.KEY_TIPS_CLOSED || it == AppSettings.KEY_DISABLE_NSFW }\n\t\t\t.flowOn(Dispatchers.Default)\n\t\t\t.onEach { onInvalidated(emptySet()) }\n\t\t\t.launchIn(scope)\n\t}\n\n\toverride fun onInvalidated(tables: Set<String>) {\n\t\tval prevJob = job\n\t\tjob = scope.launch(Dispatchers.Default) {\n\t\t\tprevJob.cancelAndJoin()\n\t\t\tlist.update { buildList() }\n\t\t}\n\t}\n\n\tfun setQuery(value: String) {\n\t\tthis.query = value\n\t\tonInvalidated(emptySet())\n\t}\n\n\tprivate suspend fun buildList(): List<SourceConfigItem> {\n\t\tval enabledSources = repository.getEnabledSources().filter { it.unwrap() is MangaParserSource }\n\t\tval pinned = repository.getPinnedSources().mapToSet { it.name }\n\t\tval isNsfwDisabled = settings.isNsfwContentDisabled\n\t\tval isReorderAvailable = settings.sourcesSortOrder == SourcesSortOrder.MANUAL\n\t\tval isDisableAvailable = !settings.isAllSourcesEnabled\n\t\tval withTip = isReorderAvailable && settings.isTipEnabled(TIP_REORDER)\n\t\tval enabledSet = enabledSources.toSet()\n\t\tif (query.isNotEmpty()) {\n\t\t\treturn enabledSources.mapNotNull {\n\t\t\t\tif (!it.getTitle(context).contains(query, ignoreCase = true)) {\n\t\t\t\t\treturn@mapNotNull null\n\t\t\t\t}\n\t\t\t\tSourceConfigItem.SourceItem(\n\t\t\t\t\tsource = it,\n\t\t\t\t\tisEnabled = it in enabledSet,\n\t\t\t\t\tisDraggable = false,\n\t\t\t\t\tisAvailable = !isNsfwDisabled || !it.isNsfw(),\n\t\t\t\t\tisPinned = it.name in pinned,\n\t\t\t\t\tisDisableAvailable = isDisableAvailable,\n\t\t\t\t)\n\t\t\t}.ifEmpty {\n\t\t\t\tlistOf(SourceConfigItem.EmptySearchResult)\n\t\t\t}\n\t\t}\n\t\tval result = ArrayList<SourceConfigItem>(enabledSources.size + 1)\n\t\tif (enabledSources.isNotEmpty()) {\n\t\t\tif (withTip) {\n\t\t\t\tresult += SourceConfigItem.Tip(\n\t\t\t\t\tTIP_REORDER,\n\t\t\t\t\tR.drawable.ic_tap_reorder,\n\t\t\t\t\tR.string.sources_reorder_tip,\n\t\t\t\t)\n\t\t\t}\n\t\t\tenabledSources.mapTo(result) {\n\t\t\t\tSourceConfigItem.SourceItem(\n\t\t\t\t\tsource = it,\n\t\t\t\t\tisEnabled = true,\n\t\t\t\t\tisDraggable = isReorderAvailable,\n\t\t\t\t\tisAvailable = false,\n\t\t\t\t\tisPinned = it.name in pinned,\n\t\t\t\t\tisDisableAvailable = isDisableAvailable,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tcompanion object {\n\n\t\tconst val TIP_REORDER = \"src_reorder\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/manage/SourcesManageFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.manage\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.os.AppShortcutManager\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.container\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.getItem\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.viewLifecycleScope\nimport org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding\nimport org.koitharu.kotatsu.main.ui.owners.AppBarOwner\nimport org.koitharu.kotatsu.settings.SettingsActivity\nimport org.koitharu.kotatsu.settings.sources.SourceSettingsFragment\nimport org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter\nimport org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener\nimport org.koitharu.kotatsu.settings.sources.model.SourceConfigItem\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass SourcesManageFragment :\n\tBaseFragment<FragmentSettingsSourcesBinding>(),\n\tSourceConfigListener,\n\tRecyclerViewOwner {\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\t@Inject\n\tlateinit var shortcutManager: AppShortcutManager\n\n\tprivate var reorderHelper: ItemTouchHelper? = null\n\tprivate var sourcesAdapter: SourceConfigAdapter? = null\n\tprivate val viewModel by viewModels<SourcesManageViewModel>()\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerView\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentSettingsSourcesBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(\n\t\tbinding: FragmentSettingsSourcesBinding,\n\t\tsavedInstanceState: Bundle?,\n\t) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tsourcesAdapter = SourceConfigAdapter(this)\n\t\twith(binding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\tadapter = sourcesAdapter\n\t\t\treorderHelper = ItemTouchHelper(SourcesReorderCallback()).also {\n\t\t\t\tit.attachToRecyclerView(this)\n\t\t\t}\n\t\t}\n\t\tviewModel.content.observe(viewLifecycleOwner, checkNotNull(sourcesAdapter))\n\t\tviewModel.onActionDone.observeEvent(\n\t\t\tviewLifecycleOwner,\n\t\t\tReversibleActionObserver(binding.recyclerView),\n\t\t)\n\t\taddMenuProvider(SourcesMenuProvider())\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tval isTablet = !resources.getBoolean(R.bool.is_tablet)\n\t\tval isMaster = container?.id == R.id.container_master\n\t\tv.setPaddingRelative(\n\t\t\tif (isTablet && !isMaster) 0 else barsInsets.start(v),\n\t\t\t0,\n\t\t\tif (isTablet && isMaster) 0 else barsInsets.end(v),\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tactivity?.setTitle(R.string.manage_sources)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsourcesAdapter = null\n\t\treorderHelper = null\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onItemSettingsClick(item: SourceConfigItem.SourceItem) {\n\t\t(activity as? SettingsActivity)?.openFragment(\n\t\t\tfragmentClass = SourceSettingsFragment::class.java,\n\t\t\targs = Bundle(1).apply { putString(AppRouter.KEY_SOURCE, item.source.name) },\n\t\t\tisFromRoot = false,\n\t\t)\n\t}\n\n\toverride fun onItemLiftClick(item: SourceConfigItem.SourceItem) {\n\t\tviewModel.bringToTop(item.source)\n\t}\n\n\toverride fun onItemShortcutClick(item: SourceConfigItem.SourceItem) {\n\t\tviewLifecycleScope.launch {\n\t\t\tshortcutManager.requestPinShortcut(item.source)\n\t\t}\n\t}\n\n\toverride fun onItemPinClick(item: SourceConfigItem.SourceItem) {\n\t\tviewModel.setPinned(item.source, !item.isPinned)\n\t}\n\n\toverride fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {\n\t\tviewModel.setEnabled(item.source, isEnabled)\n\t}\n\n\toverride fun onCloseTip(tip: SourceConfigItem.Tip) {\n\t\tviewModel.onTipClosed(tip)\n\t}\n\n\tprivate inner class SourcesMenuProvider :\n\t\tMenuProvider,\n\t\tMenuItem.OnActionExpandListener,\n\t\tSearchView.OnQueryTextListener {\n\n\t\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\t\tmenuInflater.inflate(R.menu.opt_sources, menu)\n\t\t\tval searchMenuItem = menu.findItem(R.id.action_search)\n\t\t\tsearchMenuItem.setOnActionExpandListener(this)\n\t\t\tval searchView = searchMenuItem.actionView as SearchView\n\t\t\tsearchView.setOnQueryTextListener(this)\n\t\t\tsearchView.setIconifiedByDefault(false)\n\t\t\tsearchView.queryHint = searchMenuItem.title\n\t\t}\n\n\t\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n\t\t\tR.id.action_catalog -> {\n\t\t\t\trouter.openSourcesCatalog()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_disable_all -> {\n\t\t\t\tviewModel.disableAll()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_no_nsfw -> {\n\t\t\t\tsettings.isNsfwContentDisabled = !menuItem.isChecked\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\n\t\toverride fun onPrepareMenu(menu: Menu) {\n\t\t\tsuper.onPrepareMenu(menu)\n\t\t\tmenu.findItem(R.id.action_no_nsfw).isChecked = settings.isNsfwContentDisabled\n\t\t\tmenu.findItem(R.id.action_disable_all).isVisible = !settings.isAllSourcesEnabled\n\t\t\tmenu.findItem(R.id.action_catalog).isVisible = !settings.isAllSourcesEnabled\n\t\t}\n\n\t\toverride fun onMenuItemActionExpand(item: MenuItem): Boolean {\n\t\t\t(activity as? AppBarOwner)?.appBar?.setExpanded(false, true)\n\t\t\treturn true\n\t\t}\n\n\t\toverride fun onMenuItemActionCollapse(item: MenuItem): Boolean {\n\t\t\t(item.actionView as SearchView).setQuery(\"\", false)\n\t\t\treturn true\n\t\t}\n\n\t\toverride fun onQueryTextSubmit(query: String?): Boolean = false\n\n\t\toverride fun onQueryTextChange(newText: String?): Boolean {\n\t\t\tviewModel.performSearch(newText)\n\t\t\treturn true\n\t\t}\n\t}\n\n\tprivate inner class SourcesReorderCallback : ItemTouchHelper.SimpleCallback(\n\t\tItemTouchHelper.DOWN or ItemTouchHelper.UP,\n\t\tItemTouchHelper.LEFT or ItemTouchHelper.RIGHT,\n\t) {\n\n\t\toverride fun onMove(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tviewHolder: RecyclerView.ViewHolder,\n\t\t\ttarget: RecyclerView.ViewHolder,\n\t\t): Boolean = viewHolder.itemViewType == target.itemViewType\n\n\t\toverride fun onMoved(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tviewHolder: RecyclerView.ViewHolder,\n\t\t\tfromPos: Int,\n\t\t\ttarget: RecyclerView.ViewHolder,\n\t\t\ttoPos: Int,\n\t\t\tx: Int,\n\t\t\ty: Int,\n\t\t) {\n\t\t\tsuper.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)\n\t\t\tsourcesAdapter?.reorderItems(fromPos, toPos)\n\t\t}\n\n\t\toverride fun canDropOver(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tcurrent: RecyclerView.ViewHolder,\n\t\t\ttarget: RecyclerView.ViewHolder,\n\t\t): Boolean = current.itemViewType == target.itemViewType && viewModel.canReorder(\n\t\t\tcurrent.bindingAdapterPosition,\n\t\t\ttarget.bindingAdapterPosition,\n\t\t)\n\n\t\toverride fun getDragDirs(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tviewHolder: RecyclerView.ViewHolder,\n\t\t): Int {\n\t\t\tval item = viewHolder.getItem(SourceConfigItem.SourceItem::class.java)\n\t\t\treturn if (item != null && item.isDraggable) {\n\t\t\t\tsuper.getDragDirs(recyclerView, viewHolder)\n\t\t\t} else {\n\t\t\t\t0\n\t\t\t}\n\t\t}\n\n\t\toverride fun getSwipeDirs(\n\t\t\trecyclerView: RecyclerView,\n\t\t\tviewHolder: RecyclerView.ViewHolder,\n\t\t): Int {\n\t\t\tval item = viewHolder.getItem(SourceConfigItem.Tip::class.java)\n\t\t\treturn if (item != null) {\n\t\t\t\tsuper.getSwipeDirs(recyclerView, viewHolder)\n\t\t\t} else {\n\t\t\t\t0\n\t\t\t}\n\t\t}\n\n\t\toverride fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n\t\t\tval item = viewHolder.getItem(SourceConfigItem.Tip::class.java)\n\t\t\tif (item != null) {\n\t\t\t\tviewModel.onTipClosed(item)\n\t\t\t}\n\t\t}\n\n\t\toverride fun isLongPressDragEnabled() = true\n\n\t\toverride fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n\t\t\tsuper.clearView(recyclerView, viewHolder)\n\t\t\tviewModel.saveSourcesOrder(sourcesAdapter?.items ?: return)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/manage/SourcesManageViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.manage\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.removeObserverAsync\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.util.move\nimport org.koitharu.kotatsu.settings.sources.model.SourceConfigItem\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SourcesManageViewModel @Inject constructor(\n\tprivate val database: MangaDatabase,\n\tprivate val settings: AppSettings,\n\tprivate val repository: MangaSourcesRepository,\n\tprivate val listProducer: SourcesListProducer,\n) : BaseViewModel() {\n\n\tval content = listProducer.list\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\tprivate var commitJob: Job? = null\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tdatabase.invalidationTracker.addObserver(listProducer)\n\t\t}\n\t}\n\n\toverride fun onCleared() {\n\t\tsuper.onCleared()\n\t\tdatabase.invalidationTracker.removeObserverAsync(listProducer)\n\t}\n\n\tfun saveSourcesOrder(snapshot: List<SourceConfigItem>) {\n\t\tval prevJob = commitJob\n\t\tcommitJob = launchJob(Dispatchers.Default) {\n\t\t\tprevJob?.cancelAndJoin()\n\t\t\tval newSourcesList = snapshot.mapNotNull { x ->\n\t\t\t\tif (x is SourceConfigItem.SourceItem && x.isDraggable) {\n\t\t\t\t\tx.source\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t\t\trepository.setPositions(newSourcesList)\n\t\t}\n\t}\n\n\tfun canReorder(oldPos: Int, newPos: Int): Boolean {\n\t\tval snapshot = content.value\n\t\tval oldPosItem = snapshot.getOrNull(oldPos) as? SourceConfigItem.SourceItem ?: return false\n\t\tval newPosItem = snapshot.getOrNull(newPos) as? SourceConfigItem.SourceItem ?: return false\n\t\treturn oldPosItem.isEnabled && newPosItem.isEnabled && oldPosItem.isPinned == newPosItem.isPinned\n\t}\n\n\tfun setEnabled(source: MangaSource, isEnabled: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval rollback = repository.setSourcesEnabled(setOf(source), isEnabled)\n\t\t\tif (!isEnabled) {\n\t\t\t\tonActionDone.call(ReversibleAction(R.string.source_disabled, rollback))\n\t\t\t}\n\t\t}\n\t}\n\n\tfun setPinned(source: MangaSource, isPinned: Boolean) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval rollback = repository.setIsPinned(setOf(source), isPinned)\n\t\t\tval message = if (isPinned) R.string.source_pinned else R.string.source_unpinned\n\t\t\tonActionDone.call(ReversibleAction(message, rollback))\n\t\t}\n\t}\n\n\tfun bringToTop(source: MangaSource) {\n\t\tval snapshot = content.value\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tvar oldPos = -1\n\t\t\tvar newPos = -1\n\t\t\tfor ((i, x) in snapshot.withIndex()) {\n\t\t\t\tif (x !is SourceConfigItem.SourceItem) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (newPos == -1) {\n\t\t\t\t\tnewPos = i\n\t\t\t\t}\n\t\t\t\tif (x.source == source) {\n\t\t\t\t\toldPos = i\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\t@Suppress(\"KotlinConstantConditions\")\n\t\t\tif (oldPos != -1 && newPos != -1) {\n\t\t\t\treorderSources(oldPos, newPos)\n\t\t\t\tval revert = ReversibleAction(R.string.moved_to_top) {\n\t\t\t\t\treorderSources(newPos, oldPos)\n\t\t\t\t}\n\t\t\t\tcommitJob?.join()\n\t\t\t\tonActionDone.call(revert)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun disableAll() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.disableAllSources()\n\t\t}\n\t}\n\n\tfun performSearch(query: String?) {\n\t\tlistProducer.setQuery(query?.trim().orEmpty())\n\t}\n\n\tfun onTipClosed(item: SourceConfigItem.Tip) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tsettings.closeTip(item.key)\n\t\t}\n\t}\n\n\tprivate fun reorderSources(oldPos: Int, newPos: Int) {\n\t\tval snapshot = content.value.toMutableList()\n\t\tif ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) {\n\t\t\treturn\n\t\t}\n\t\tif ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) {\n\t\t\treturn\n\t\t}\n\t\tsnapshot.move(oldPos, newPos)\n\t\tsaveSourcesOrder(snapshot)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt",
    "content": "package org.koitharu.kotatsu.settings.sources.model\n\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.MangaSource\n\nsealed interface SourceConfigItem : ListModel {\n\n\tdata class SourceItem(\n\t\tval source: MangaSource,\n\t\tval isEnabled: Boolean,\n\t\tval isDraggable: Boolean,\n\t\tval isAvailable: Boolean,\n\t\tval isPinned: Boolean,\n\t\tval isDisableAvailable: Boolean,\n\t) : SourceConfigItem {\n\n\t\tval isNsfw: Boolean\n\t\t\tget() = source.isNsfw()\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is SourceItem && other.source == source\n\t\t}\n\t}\n\n\tdata class Tip(\n\t\tval key: String,\n\t\t@DrawableRes val iconResId: Int,\n\t\t@StringRes val textResId: Int,\n\t) : SourceConfigItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is Tip && other.key == key\n\t\t}\n\t}\n\n\tdata object EmptySearchResult : SourceConfigItem {\n\n\t\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\t\treturn other is EmptySearchResult\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/DirectoryAD.kt",
    "content": "package org.koitharu.kotatsu.settings.storage\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemStorageBinding\n\nfun directoryAD(\n\tclickListener: OnListItemClickListener<DirectoryModel>,\n) = adapterDelegateViewBinding<DirectoryModel, DirectoryModel, ItemStorageBinding>(\n\t{ layoutInflater, parent -> ItemStorageBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.root.setOnClickListener { v -> clickListener.onItemClick(item, v) }\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.title ?: getString(item.titleRes)\n\t\tbinding.textViewSubtitle.textAndVisible = item.file?.absolutePath\n\t\tbinding.imageViewIndicator.isChecked = item.isChecked\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/DirectoryDiffCallback.kt",
    "content": "package org.koitharu.kotatsu.settings.storage\n\nimport androidx.recyclerview.widget.DiffUtil.ItemCallback\n\nclass DirectoryDiffCallback : ItemCallback<DirectoryModel>() {\n\n\toverride fun areItemsTheSame(oldItem: DirectoryModel, newItem: DirectoryModel): Boolean {\n\t\treturn oldItem.file == newItem.file\n\t}\n\n\toverride fun areContentsTheSame(oldItem: DirectoryModel, newItem: DirectoryModel): Boolean {\n\t\treturn oldItem == newItem\n\t}\n\n\toverride fun getChangePayload(oldItem: DirectoryModel, newItem: DirectoryModel): Any? {\n\t\treturn if (oldItem.isChecked != newItem.isChecked) {\n\t\t\tUnit\n\t\t} else {\n\t\t\tsuper.getChangePayload(oldItem, newItem)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/DirectoryModel.kt",
    "content": "package org.koitharu.kotatsu.settings.storage\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport java.io.File\n\ndata class DirectoryModel(\n\tval title: String?,\n\t@StringRes val titleRes: Int,\n\tval file: File?,\n\tval isRemovable: Boolean,\n\tval isChecked: Boolean,\n\tval isAvailable: Boolean,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is DirectoryModel && other.file == file && other.title == title && other.titleRes == titleRes\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\treturn if (previousState is DirectoryModel && previousState.isChecked != isChecked) {\n\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t} else {\n\t\t\tsuper.getChangePayload(previousState)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/MangaDirectorySelectDialog.kt",
    "content": "package org.koitharu.kotatsu.settings.storage\n\nimport android.Manifest\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.ToastErrorObserver\nimport org.koitharu.kotatsu.core.os.OpenDocumentTreeHelper\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.databinding.DialogDirectorySelectBinding\n\n@AndroidEntryPoint\nclass MangaDirectorySelectDialog : AlertDialogFragment<DialogDirectorySelectBinding>(),\n\tOnListItemClickListener<DirectoryModel> {\n\n\tprivate val viewModel: MangaDirectorySelectViewModel by viewModels()\n\tprivate val pickFileTreeLauncher = OpenDocumentTreeHelper(\n\t\tactivityResultCaller = this,\n\t\tflags = Intent.FLAG_GRANT_READ_URI_PERMISSION\n\t\t\tor Intent.FLAG_GRANT_WRITE_URI_PERMISSION\n\t\t\tor Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,\n\t) {\n\t\tif (it != null) viewModel.onCustomDirectoryPicked(it)\n\t}\n\tprivate val permissionRequestLauncher = registerForActivityResult(\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n\t\t\tRequestStorageManagerPermissionContract()\n\t\t} else {\n\t\t\tActivityResultContracts.RequestPermission()\n\t\t},\n\t) {\n\t\tif (it) {\n\t\t\tviewModel.refresh()\n\t\t\tif (!pickFileTreeLauncher.tryLaunch(null)) {\n\t\t\t\tToast.makeText(\n\t\t\t\t\tcontext ?: return@registerForActivityResult,\n\t\t\t\t\tR.string.operation_not_supported,\n\t\t\t\t\tToast.LENGTH_SHORT,\n\t\t\t\t).show()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogDirectorySelectBinding {\n\t\treturn DialogDirectorySelectBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: DialogDirectorySelectBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval adapter = AsyncListDifferDelegationAdapter(DirectoryDiffCallback(), directoryAD(this))\n\t\tbinding.root.adapter = adapter\n\t\tviewModel.items.observe(viewLifecycleOwner) { adapter.items = it }\n\t\tviewModel.onDismissDialog.observeEvent(viewLifecycleOwner) { dismiss() }\n\t\tviewModel.onPickDirectory.observeEvent(viewLifecycleOwner) { pickCustomDirectory() }\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, ToastErrorObserver(binding.root, this))\n\t}\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder)\n\t\t\t.setCancelable(true)\n\t\t\t.setTitle(R.string.manga_save_location)\n\t\t\t.setNegativeButton(android.R.string.cancel, null)\n\t}\n\n\toverride fun onItemClick(item: DirectoryModel, view: View) {\n\t\tviewModel.onItemClick(item)\n\t}\n\n\tprivate fun pickCustomDirectory() {\n\t\tif (!permissionRequestLauncher.tryLaunch(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {\n\t\t\tToast.makeText(context ?: return, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/MangaDirectorySelectViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.storage\n\nimport android.net.Uri\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.core.util.ext.isWriteable\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MangaDirectorySelectViewModel @Inject constructor(\n\tprivate val storageManager: LocalStorageManager,\n\tprivate val settings: AppSettings,\n) : BaseViewModel() {\n\n\tval items = MutableStateFlow(emptyList<DirectoryModel>())\n\tval onDismissDialog = MutableEventFlow<Unit>()\n\tval onPickDirectory = MutableEventFlow<Unit>()\n\n\tinit {\n\t\trefresh()\n\t}\n\n\tfun onItemClick(item: DirectoryModel) {\n\t\tif (item.file != null) {\n\t\t\tsettings.mangaStorageDir = item.file\n\t\t\tonDismissDialog.call(Unit)\n\t\t} else {\n\t\t\tonPickDirectory.call(Unit)\n\t\t}\n\t}\n\n\tfun onCustomDirectoryPicked(uri: Uri) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tstorageManager.takePermissions(uri)\n\t\t\tval dir = storageManager.resolveUri(uri)\n\t\t\tif (!dir.isWriteable()) {\n\t\t\t\tthrow AccessDeniedException(dir)\n\t\t\t}\n\t\t\tif (dir !in storageManager.getApplicationStorageDirs()) {\n\t\t\t\tsettings.mangaStorageDir = dir\n\t\t\t\tstorageManager.setDirIsNoMedia(dir)\n\t\t\t}\n\t\t\tonDismissDialog.call(Unit)\n\t\t}\n\t}\n\n\tfun refresh() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval defaultValue = storageManager.getDefaultWriteableDir()\n\t\t\tval available = storageManager.getWriteableDirs()\n\t\t\titems.value = buildList(available.size + 1) {\n\t\t\t\tavailable.mapTo(this) { dir ->\n\t\t\t\t\tDirectoryModel(\n\t\t\t\t\t\ttitle = storageManager.getDirectoryDisplayName(dir, isFullPath = false),\n\t\t\t\t\t\ttitleRes = 0,\n\t\t\t\t\t\tfile = dir,\n\t\t\t\t\t\tisChecked = dir == defaultValue,\n\t\t\t\t\t\tisAvailable = true,\n\t\t\t\t\t\tisRemovable = false,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthis += DirectoryModel(\n\t\t\t\t\ttitle = null,\n\t\t\t\t\ttitleRes = R.string.pick_custom_directory,\n\t\t\t\t\tfile = null,\n\t\t\t\t\tisChecked = false,\n\t\t\t\t\tisAvailable = true,\n\t\t\t\t\tisRemovable = false,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/RequestStorageManagerPermissionContract.kt",
    "content": "package org.koitharu.kotatsu.settings.storage\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.Settings\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.annotation.RequiresApi\nimport androidx.core.net.toUri\n\n\n@RequiresApi(Build.VERSION_CODES.R)\nclass RequestStorageManagerPermissionContract : ActivityResultContract<String, Boolean>() {\n\n\toverride fun createIntent(context: Context, input: String): Intent {\n\t\tval intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)\n\t\tintent.addCategory(\"android.intent.category.DEFAULT\")\n\t\tintent.data = \"package:${context.packageName}\".toUri()\n\t\treturn intent\n\t}\n\n\toverride fun parseResult(resultCode: Int, intent: Intent?): Boolean {\n\t\treturn Environment.isExternalStorageManager()\n\t}\n\n\toverride fun getSynchronousResult(context: Context, input: String): SynchronousResult<Boolean>? {\n\t\treturn if (Environment.isExternalStorageManager()) {\n\t\t\tSynchronousResult(true)\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/DirectoryConfigAD.kt",
    "content": "package org.koitharu.kotatsu.settings.storage.directories\n\nimport android.view.View\nimport androidx.core.content.ContextCompat\nimport androidx.core.text.bold\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.color\nimport androidx.core.view.isGone\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.setTooltipCompat\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.ItemStorageConfig2Binding\n\nfun directoryConfigAD(\n    clickListener: OnListItemClickListener<DirectoryConfigModel>,\n) = adapterDelegateViewBinding<DirectoryConfigModel, DirectoryConfigModel, ItemStorageConfig2Binding>(\n    { layoutInflater, parent -> ItemStorageConfig2Binding.inflate(layoutInflater, parent, false) },\n) {\n\n    binding.buttonRemove.setOnClickListener { v -> clickListener.onItemClick(item, v) }\n    binding.buttonRemove.setTooltipCompat(binding.buttonRemove.contentDescription)\n\n    bind {\n        binding.textViewTitle.text = item.title\n        binding.textViewSubtitle.text = item.path.absolutePath\n        binding.buttonRemove.isGone = item.isAppPrivate\n        binding.buttonRemove.isEnabled = !item.isDefault\n        binding.spacer.visibility = if (item.isAppPrivate) {\n            View.INVISIBLE\n        } else {\n            View.GONE\n        }\n        binding.textViewInfo.textAndVisible = buildSpannedString {\n            if (item.isDefault) {\n                bold {\n                    append(getString(R.string.download_default_directory))\n                }\n            }\n            if (!item.isAccessible) {\n                if (isNotEmpty()) appendLine()\n                color(\n                    context.getThemeColor(\n                        androidx.appcompat.R.attr.colorError,\n                        ContextCompat.getColor(context, R.color.common_red),\n                    ),\n                ) {\n                    append(getString(R.string.no_write_permission_to_file))\n                }\n            }\n            if (item.isAppPrivate) {\n                if (isNotEmpty()) appendLine()\n                append(getString(R.string.private_app_directory_warning))\n            }\n        }\n        binding.indicatorSize.max = FileSize.BYTES.convert(item.available, FileSize.KILOBYTES).toInt()\n        binding.indicatorSize.progress = FileSize.BYTES.convert(item.size, FileSize.KILOBYTES).toInt()\n        binding.textViewSize.text = context.getString(\n            R.string.available_pattern,\n            FileSize.BYTES.format(context, item.available),\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/DirectoryConfigDiffCallback.kt",
    "content": "package org.koitharu.kotatsu.settings.storage.directories\n\nimport androidx.recyclerview.widget.DiffUtil.ItemCallback\n\nclass DirectoryConfigDiffCallback : ItemCallback<DirectoryConfigModel>() {\n\n\toverride fun areItemsTheSame(oldItem: DirectoryConfigModel, newItem: DirectoryConfigModel): Boolean {\n\t\treturn oldItem.path == newItem.path\n\t}\n\n\toverride fun areContentsTheSame(oldItem: DirectoryConfigModel, newItem: DirectoryConfigModel): Boolean {\n\t\treturn oldItem == newItem\n\t}\n\n\toverride fun getChangePayload(oldItem: DirectoryConfigModel, newItem: DirectoryConfigModel): Any? {\n\t\treturn if (oldItem.isDefault != newItem.isDefault) {\n\t\t\tUnit\n\t\t} else {\n\t\t\tsuper.getChangePayload(oldItem, newItem)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/DirectoryConfigModel.kt",
    "content": "package org.koitharu.kotatsu.settings.storage.directories\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport java.io.File\n\ndata class DirectoryConfigModel(\n    val title: String,\n    val path: File,\n    val isDefault: Boolean,\n    val isAppPrivate: Boolean,\n    val isAccessible: Boolean,\n    val size: Long,\n    val available: Long,\n) : ListModel {\n\n    override fun areItemsTheSame(other: ListModel): Boolean {\n        return other is DirectoryConfigModel && path == other.path\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/MangaDirectoriesActivity.kt",
    "content": "package org.koitharu.kotatsu.settings.storage.directories\n\nimport android.Manifest\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport com.google.android.material.snackbar.Snackbar\nimport com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.os.OpenDocumentTreeHelper\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\nimport org.koitharu.kotatsu.databinding.ActivityMangaDirectoriesBinding\nimport org.koitharu.kotatsu.settings.storage.RequestStorageManagerPermissionContract\n\n@AndroidEntryPoint\nclass MangaDirectoriesActivity : BaseActivity<ActivityMangaDirectoriesBinding>(),\n\tOnListItemClickListener<DirectoryConfigModel>, View.OnClickListener {\n\n\tprivate val viewModel: MangaDirectoriesViewModel by viewModels()\n\tprivate val pickFileTreeLauncher = OpenDocumentTreeHelper(\n\t\tactivityResultCaller = this,\n\t\tflags = Intent.FLAG_GRANT_READ_URI_PERMISSION\n\t\t\tor Intent.FLAG_GRANT_WRITE_URI_PERMISSION\n\t\t\tor Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,\n\t) {\n\t\tif (it != null) viewModel.onCustomDirectoryPicked(it)\n\t}\n\tprivate val permissionRequestLauncher = registerForActivityResult(\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n\t\t\tRequestStorageManagerPermissionContract()\n\t\t} else {\n\t\t\tActivityResultContracts.RequestPermission()\n\t\t},\n\t) {\n\t\tif (it) {\n\t\t\tviewModel.updateList()\n\t\t\tif (!pickFileTreeLauncher.tryLaunch(null)) {\n\t\t\t\tSnackbar.make(\n\t\t\t\t\tviewBinding.recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,\n\t\t\t\t).show()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityMangaDirectoriesBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tval adapter = AsyncListDifferDelegationAdapter(DirectoryConfigDiffCallback(), directoryConfigAD(this))\n        val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing_large)\n        viewBinding.recyclerView.adapter = adapter\n        viewBinding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing, withBottomPadding = false))\n\t\tviewBinding.fabAdd.setOnClickListener(this)\n\t\tviewModel.items.observe(this) { adapter.items = it }\n\t\tviewModel.isLoading.observe(this) { viewBinding.progressBar.isVisible = it }\n\t\tviewModel.onError.observeEvent(\n\t\t\tthis,\n\t\t\tSnackbarErrorObserver(viewBinding.root, null, exceptionResolver) {\n\t\t\t\tif (it) viewModel.updateList()\n\t\t\t},\n\t\t)\n\t}\n\n\toverride fun onItemClick(item: DirectoryConfigModel, view: View) {\n\t\tviewModel.onRemoveClick(item.path)\n\t}\n\n\toverride fun onClick(v: View?) {\n\t\tif (!permissionRequestLauncher.tryLaunch(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {\n\t\t\tSnackbar.make(\n\t\t\t\tviewBinding.recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,\n\t\t\t).show()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tviewBinding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\trightMargin = topMargin + barsInsets.right\n\t\t\tleftMargin = topMargin + barsInsets.left\n\t\t\tbottomMargin = topMargin + barsInsets.bottom\n\t\t}\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\ttop = barsInsets.top,\n\t\t)\n\t\tviewBinding.recyclerView.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/MangaDirectoriesViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.storage.directories\n\nimport android.net.Uri\nimport android.os.StatFs\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.computeSize\nimport org.koitharu.kotatsu.core.util.ext.isReadable\nimport org.koitharu.kotatsu.core.util.ext.isWriteable\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport java.io.File\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MangaDirectoriesViewModel @Inject constructor(\n    private val storageManager: LocalStorageManager,\n    private val settings: AppSettings,\n) : BaseViewModel() {\n\n    val items = MutableStateFlow(emptyList<DirectoryConfigModel>())\n    private var loadingJob: Job? = null\n\n    init {\n        loadList()\n    }\n\n    fun updateList() {\n        loadList()\n    }\n\n    fun onCustomDirectoryPicked(uri: Uri) {\n        launchLoadingJob(Dispatchers.Default) {\n            loadingJob?.cancelAndJoin()\n            storageManager.takePermissions(uri)\n            val dir = storageManager.resolveUri(uri)\n            if (!dir.canRead()) {\n                throw AccessDeniedException(dir)\n            }\n            if (dir !in storageManager.getApplicationStorageDirs()) {\n                settings.userSpecifiedMangaDirectories += dir\n                loadList()\n            }\n        }\n    }\n\n    fun onRemoveClick(directory: File) {\n        settings.userSpecifiedMangaDirectories -= directory\n        if (settings.mangaStorageDir == directory) {\n            settings.mangaStorageDir = null\n        }\n        loadList()\n    }\n\n    private fun loadList() {\n        val prevJob = loadingJob\n        loadingJob = launchJob(Dispatchers.Default) {\n            prevJob?.cancelAndJoin()\n            val downloadDir = storageManager.getDefaultWriteableDir()\n            val applicationDirs = storageManager.getApplicationStorageDirs()\n            val customDirs = settings.userSpecifiedMangaDirectories - applicationDirs\n            items.value = buildList(applicationDirs.size + customDirs.size) {\n                applicationDirs.mapTo(this) { dir ->\n                    dir.toDirectoryModel(\n                        isDefault = dir == downloadDir,\n                        isAppPrivate = true,\n                    )\n                }\n                customDirs.mapTo(this) { dir ->\n                    dir.toDirectoryModel(\n                        isDefault = dir == downloadDir,\n                        isAppPrivate = false,\n                    )\n                }\n            }\n        }\n    }\n\n    private suspend fun File.toDirectoryModel(\n        isDefault: Boolean,\n        isAppPrivate: Boolean,\n    ) = DirectoryConfigModel(\n        title = storageManager.getDirectoryDisplayName(this, isFullPath = false),\n        path = this,\n        isDefault = isDefault,\n        isAccessible = isReadable() && isWriteable(),\n        isAppPrivate = isAppPrivate,\n        size = computeSize(),\n        available = StatFs(this.absolutePath).availableBytes,\n    )\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/tracker/TrackerSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.tracker\n\nimport android.content.ActivityNotFoundException\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.provider.Settings\nimport android.text.style.URLSpan\nimport android.view.View\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.inSpans\nimport androidx.fragment.app.viewModels\nimport androidx.preference.ListPreference\nimport androidx.preference.MultiSelectListPreference\nimport androidx.preference.Preference\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.TrackerDownloadStrategy\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat\nimport org.koitharu.kotatsu.parsers.util.names\nimport org.koitharu.kotatsu.settings.utils.DozeHelper\nimport org.koitharu.kotatsu.settings.utils.MultiSummaryProvider\nimport org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity\nimport org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass TrackerSettingsFragment :\n\tBasePreferenceFragment(R.string.check_for_new_chapters),\n\tSharedPreferences.OnSharedPreferenceChangeListener {\n\n\tprivate val viewModel by viewModels<TrackerSettingsViewModel>()\n\tprivate val dozeHelper = DozeHelper(this)\n\n\t@Inject\n\tlateinit var notificationHelper: TrackerNotificationHelper\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_tracker)\n\n\t\tfindPreference<MultiSelectListPreference>(AppSettings.KEY_TRACK_SOURCES)\n\t\t\t?.summaryProvider = MultiSummaryProvider(R.string.dont_check)\n\t\tval warningPreference = findPreference<Preference>(AppSettings.KEY_TRACK_WARNING)\n\t\tif (warningPreference != null) {\n\t\t\twarningPreference.summary = buildSpannedString {\n\t\t\t\tappend(getString(R.string.tracker_warning))\n\t\t\t\tappend(\" \")\n\t\t\t\tinSpans(URLSpan(\"https://dontkillmyapp.com/\")) {\n\t\t\t\t\tappend(getString(R.string.read_more))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfindPreference<ListPreference>(AppSettings.KEY_TRACKER_DOWNLOAD)?.run {\n\t\t\tentryValues = TrackerDownloadStrategy.entries.names()\n\t\t\tsetDefaultValueCompat(TrackerDownloadStrategy.DISABLED.name)\n\t\t}\n\t\tdozeHelper.updatePreference()\n\t\tupdateCategoriesEnabled()\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\t\tupdateNotificationsSummary()\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tsettings.subscribe(this)\n\t\tviewModel.categoriesCount.observe(viewLifecycleOwner, ::onCategoriesCountChanged)\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsettings.unsubscribe(this)\n\t\tsuper.onDestroyView()\n\t}\n\n\toverride fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_TRACKER_NOTIFICATIONS -> updateNotificationsSummary()\n\t\t\tAppSettings.KEY_TRACK_SOURCES,\n\t\t\tAppSettings.KEY_TRACKER_ENABLED -> updateCategoriesEnabled()\n\t\t}\n\t}\n\n\toverride fun onPreferenceTreeClick(preference: Preference): Boolean {\n\t\treturn when (preference.key) {\n\t\t\tAppSettings.KEY_NOTIFICATIONS_SETTINGS -> when {\n\t\t\t\tBuild.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {\n\t\t\t\t\tval intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)\n\t\t\t\t\t\t.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)\n\t\t\t\t\tstartActivitySafe(intent)\n\t\t\t\t\ttrue\n\t\t\t\t}\n\n\t\t\t\t!notificationHelper.getAreNotificationsEnabled() -> {\n\t\t\t\t\tval intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)\n\t\t\t\t\t\t.setData(Uri.fromParts(\"package\", requireContext().packageName, null))\n\t\t\t\t\tstartActivitySafe(intent)\n\t\t\t\t\ttrue\n\t\t\t\t}\n\n\t\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t\t}\n\n\t\t\tAppSettings.KEY_TRACK_CATEGORIES -> {\n\t\t\t\trouter.showTrackerCategoriesConfigSheet()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_IGNORE_DOZE -> {\n\t\t\t\tdozeHelper.startIgnoreDoseActivity()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tAppSettings.KEY_TRACKER_DEBUG -> {\n\t\t\t\tstartActivity(Intent(preference.context, TrackerDebugActivity::class.java))\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onPreferenceTreeClick(preference)\n\t\t}\n\t}\n\n\tprivate fun updateNotificationsSummary() {\n\t\tval pref = findPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_SETTINGS) ?: return\n\t\tpref.setSummary(\n\t\t\twhen {\n\t\t\t\tnotificationHelper.getAreNotificationsEnabled() -> R.string.show_notification_new_chapters_on\n\t\t\t\telse -> R.string.show_notification_new_chapters_off\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate fun updateCategoriesEnabled() {\n\t\tval pref = findPreference<Preference>(AppSettings.KEY_TRACK_CATEGORIES) ?: return\n\t\tpref.isEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources\n\t}\n\n\tprivate fun onCategoriesCountChanged(count: IntArray?) {\n\t\tval pref = findPreference<Preference>(AppSettings.KEY_TRACK_CATEGORIES) ?: return\n\t\tpref.summary = count?.let {\n\t\t\tgetString(R.string.enabled_d_of_d, count[0], count[1])\n\t\t}\n\t}\n\n\tprivate fun startActivitySafe(intent: Intent): Boolean = try {\n\t\tstartActivity(intent)\n\t\ttrue\n\t} catch (_: ActivityNotFoundException) {\n\t\tSnackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\tfalse\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/tracker/TrackerSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.tracker\n\nimport androidx.room.InvalidationTracker\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport okio.Closeable\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES\nimport org.koitharu.kotatsu.core.db.removeObserverAsync\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.tracker.domain.TrackingRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass TrackerSettingsViewModel @Inject constructor(\n\tprivate val repository: TrackingRepository,\n\tprivate val database: MangaDatabase,\n) : BaseViewModel() {\n\n\tval categoriesCount = MutableStateFlow<IntArray?>(null)\n\n\tinit {\n\t\tupdateCategoriesCount()\n\t\tval databaseObserver = DatabaseObserver(this)\n\t\taddCloseable(databaseObserver)\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tdatabase.invalidationTracker.addObserver(databaseObserver)\n\t\t}\n\t}\n\n\tprivate fun updateCategoriesCount() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tcategoriesCount.value = repository.getCategoriesCount()\n\t\t}\n\t}\n\n\tprivate class DatabaseObserver(private var vm: TrackerSettingsViewModel?) :\n\t\tInvalidationTracker.Observer(arrayOf(TABLE_FAVOURITE_CATEGORIES)),\n\t\tCloseable {\n\n\t\toverride fun onInvalidated(tables: Set<String>) {\n\t\t\tvm?.updateCategoriesCount()\n\t\t}\n\n\t\toverride fun close() {\n\t\t\t(vm ?: return).database.invalidationTracker.removeObserverAsync(this)\n\t\t\tvm = null\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigAdapter.kt",
    "content": "package org.koitharu.kotatsu.settings.tracker.categories\n\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\n\nclass TrackerCategoriesConfigAdapter(\n\tlistener: OnListItemClickListener<FavouriteCategory>,\n) : BaseListAdapter<FavouriteCategory>() {\n\n\tinit {\n\t\tdelegatesManager.addDelegate(trackerCategoryAD(listener))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigSheet.kt",
    "content": "package org.koitharu.kotatsu.settings.tracker.categories\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.databinding.SheetBaseBinding\n\n@AndroidEntryPoint\nclass TrackerCategoriesConfigSheet :\n\tBaseAdaptiveSheet<SheetBaseBinding>(),\n\tOnListItemClickListener<FavouriteCategory> {\n\n\tprivate val viewModel by viewModels<TrackerCategoriesConfigViewModel>()\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetBaseBinding {\n\t\treturn SheetBaseBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: SheetBaseBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.headerBar.setTitle(R.string.favourites_categories)\n\t\tval adapter = TrackerCategoriesConfigAdapter(this)\n\t\tbinding.recyclerView.adapter = adapter\n\n\t\tviewModel.content.observe(viewLifecycleOwner, adapter)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tviewBinding?.recyclerView?.updatePadding(\n\t\t\tbottom = insets.getInsets(typeMask).bottom,\n\t\t)\n\t\treturn insets.consume(v, typeMask, bottom = true)\n\t}\n\n\toverride fun onItemClick(item: FavouriteCategory, view: View) {\n\t\tviewModel.toggleItem(item)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.tracker.categories\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport javax.inject.Inject\n\n@HiltViewModel\nclass TrackerCategoriesConfigViewModel @Inject constructor(\n\tprivate val favouritesRepository: FavouritesRepository,\n) : BaseViewModel() {\n\n\tval content = favouritesRepository.observeCategories()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tprivate var updateJob: Job? = null\n\n\tfun toggleItem(category: FavouriteCategory) {\n\t\tval prevJob = updateJob\n\t\tupdateJob = launchJob(Dispatchers.Default) {\n\t\t\tprevJob?.join()\n\t\t\tfavouritesRepository.updateCategoryTracking(category.id, !category.isTrackingEnabled)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoryAD.kt",
    "content": "package org.koitharu.kotatsu.settings.tracker.categories\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding\n\nfun trackerCategoryAD(\n\tlistener: OnListItemClickListener<FavouriteCategory>,\n) = adapterDelegateViewBinding<FavouriteCategory, FavouriteCategory, ItemCategoryCheckableMultipleBinding>(\n\t{ layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) },\n) {\n\tval eventListener = AdapterDelegateClickListenerAdapter(this, listener)\n\titemView.setOnClickListener(eventListener)\n\n\tbind {\n\t\tbinding.root.text = item.title\n\t\tbinding.root.isChecked = item.isTrackingEnabled\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/BackupsSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.userdata\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.fragment.app.viewModels\nimport androidx.preference.Preference\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.backups.domain.BackupUtils\nimport org.koitharu.kotatsu.backups.ui.backup.BackupService\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.tryLaunch\n\n@AndroidEntryPoint\nclass BackupsSettingsFragment : BasePreferenceFragment(R.string.backup_restore),\n    ActivityResultCallback<Uri?> {\n\n    private val viewModel: BackupsSettingsViewModel by viewModels()\n\n    private val backupSelectCall = registerForActivityResult(\n        ActivityResultContracts.OpenDocument(),\n        this,\n    )\n\n    private val backupCreateCall = registerForActivityResult(\n        ActivityResultContracts.CreateDocument(\"application/zip\"),\n    ) { uri ->\n        if (uri != null) {\n            if (!BackupService.start(requireContext(), uri)) {\n                Snackbar.make(\n                    listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,\n                ).show()\n            }\n        }\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_backups)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        bindPeriodicalBackupSummary()\n        viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))\n    }\n\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        return when (preference.key) {\n            AppSettings.KEY_BACKUP -> {\n                if (!backupCreateCall.tryLaunch(BackupUtils.generateFileName(preference.context))) {\n                    Snackbar.make(\n                        listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,\n                    ).show()\n                }\n                true\n            }\n\n            AppSettings.KEY_RESTORE -> {\n                if (!backupSelectCall.tryLaunch(arrayOf(\"*/*\"))) {\n                    Snackbar.make(\n                        listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT,\n                    ).show()\n                }\n                true\n            }\n\n            else -> super.onPreferenceTreeClick(preference)\n        }\n    }\n\n    override fun onActivityResult(result: Uri?) {\n        if (result != null) {\n            router.showBackupRestoreDialog(result)\n        }\n    }\n\n    private fun bindPeriodicalBackupSummary() {\n        val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_ENABLED) ?: return\n        val entries = resources.getStringArray(R.array.backup_frequency)\n        val entryValues = resources.getStringArray(R.array.values_backup_frequency)\n        viewModel.periodicalBackupFrequency.observe(viewLifecycleOwner) { freq ->\n            preference.summary = if (freq == 0L) {\n                getString(R.string.disabled)\n            } else {\n                val index = entryValues.indexOf(freq.toString())\n                entries.getOrNull(index)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/BackupsSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.userdata\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport javax.inject.Inject\n\n@HiltViewModel\nclass BackupsSettingsViewModel @Inject constructor(\n    private val settings: AppSettings,\n) : BaseViewModel() {\n\n    val periodicalBackupFrequency = settings.observeAsFlow(\n        key = AppSettings.KEY_BACKUP_PERIODICAL_ENABLED,\n        valueProducer = { isPeriodicalBackupEnabled },\n    ).flatMapLatest { isEnabled ->\n        if (isEnabled) {\n            settings.observeAsFlow(\n                key = AppSettings.KEY_BACKUP_PERIODICAL_FREQUENCY,\n                valueProducer = { periodicalBackupFrequency },\n            )\n        } else {\n            flowOf(0)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/storage/DataCleanupSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings.userdata.storage\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport androidx.preference.Preference\nimport com.google.android.material.snackbar.Snackbar\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.StateFlow\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.local.data.CacheDir\n\n@AndroidEntryPoint\nclass DataCleanupSettingsFragment : BasePreferenceFragment(R.string.data_removal) {\n\n    private val viewModel by viewModels<DataCleanupSettingsViewModel>()\n    private val loadingPrefs = HashSet<String>()\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_data_cleanup)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.PAGES]))\n        findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.THUMBS]))\n        findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR)?.bindBytesSizeSummary(viewModel.httpCacheSize)\n        findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref ->\n            viewModel.searchHistoryCount.observe(viewLifecycleOwner) {\n                pref.summary = if (it < 0) {\n                    view.context.getString(R.string.loading_)\n                } else {\n                    pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)\n                }\n            }\n        }\n        findPreference<Preference>(AppSettings.KEY_UPDATES_FEED_CLEAR)?.let { pref ->\n            viewModel.feedItemsCount.observe(viewLifecycleOwner) {\n                pref.summary = if (it < 0) {\n                    view.context.getString(R.string.loading_)\n                } else {\n                    pref.context.resources.getQuantityStringSafe(R.plurals.items, it, it)\n                }\n            }\n        }\n        findPreference<Preference>(AppSettings.KEY_WEBVIEW_CLEAR)?.isVisible = viewModel.isBrowserDataCleanupEnabled\n\n        viewModel.loadingKeys.observe(viewLifecycleOwner) { keys ->\n            loadingPrefs.addAll(keys)\n            loadingPrefs.forEach { prefKey ->\n                findPreference<Preference>(prefKey)?.isEnabled = prefKey !in keys\n            }\n        }\n        viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(listView, this))\n        viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(listView))\n        viewModel.onChaptersCleanedUp.observeEvent(viewLifecycleOwner, ::onChaptersCleanedUp)\n    }\n\n    override fun onPreferenceTreeClick(preference: Preference): Boolean = when (preference.key) {\n        AppSettings.KEY_COOKIES_CLEAR -> {\n            clearCookies()\n            true\n        }\n\n        AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {\n            clearSearchHistory()\n            true\n        }\n\n        AppSettings.KEY_PAGES_CACHE_CLEAR -> {\n            viewModel.clearCache(preference.key, CacheDir.PAGES)\n            true\n        }\n\n        AppSettings.KEY_THUMBS_CACHE_CLEAR -> {\n            viewModel.clearCache(preference.key, CacheDir.THUMBS, CacheDir.FAVICONS)\n            true\n        }\n\n        AppSettings.KEY_HTTP_CACHE_CLEAR -> {\n            viewModel.clearHttpCache()\n            true\n        }\n\n        AppSettings.KEY_CHAPTERS_CLEAR -> {\n            cleanupChapters()\n            true\n        }\n\n        AppSettings.KEY_WEBVIEW_CLEAR -> {\n            viewModel.clearBrowserData()\n            true\n        }\n\n        AppSettings.KEY_CLEAR_MANGA_DATA -> {\n            viewModel.clearMangaData()\n            true\n        }\n\n        AppSettings.KEY_UPDATES_FEED_CLEAR -> {\n            viewModel.clearUpdatesFeed()\n            true\n        }\n\n        else -> super.onPreferenceTreeClick(preference)\n    }\n\n    private fun onChaptersCleanedUp(result: Pair<Int, Long>) {\n        val c = context ?: return\n        val text = if (result.first == 0 && result.second == 0L) {\n            c.getString(R.string.no_chapters_deleted)\n        } else {\n            c.getString(\n                R.string.chapters_deleted_pattern,\n                c.resources.getQuantityStringSafe(R.plurals.chapters, result.first, result.first),\n                FileSize.BYTES.format(c, result.second),\n            )\n        }\n        Snackbar.make(listView, text, Snackbar.LENGTH_SHORT).show()\n    }\n\n    private fun Preference.bindBytesSizeSummary(stateFlow: StateFlow<Long>) {\n        stateFlow.observe(viewLifecycleOwner) { size ->\n            summary = if (size < 0) {\n                context.getString(R.string.computing_)\n            } else {\n                FileSize.BYTES.format(context, size)\n            }\n        }\n    }\n\n    private fun clearSearchHistory() {\n        buildAlertDialog(context ?: return) {\n            setTitle(R.string.clear_search_history)\n            setMessage(R.string.text_clear_search_history_prompt)\n            setNegativeButton(android.R.string.cancel, null)\n            setPositiveButton(R.string.clear) { _, _ ->\n                viewModel.clearSearchHistory()\n            }\n        }.show()\n    }\n\n    private fun clearCookies() {\n        buildAlertDialog(context ?: return) {\n            setTitle(R.string.clear_cookies)\n            setMessage(R.string.text_clear_cookies_prompt)\n            setNegativeButton(android.R.string.cancel, null)\n            setPositiveButton(R.string.clear) { _, _ ->\n                viewModel.clearCookies()\n            }\n        }.show()\n    }\n\n    private fun cleanupChapters() {\n        buildAlertDialog(context ?: return) {\n            setTitle(R.string.delete_read_chapters)\n            setMessage(R.string.delete_read_chapters_prompt)\n            setNegativeButton(android.R.string.cancel, null)\n            setPositiveButton(R.string.delete) { _, _ ->\n                viewModel.cleanupChapters()\n            }\n        }.show()\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/storage/DataCleanupSettingsViewModel.kt",
    "content": "package org.koitharu.kotatsu.settings.userdata.storage\n\nimport android.annotation.SuppressLint\nimport android.webkit.WebStorage\nimport androidx.webkit.WebStorageCompat\nimport androidx.webkit.WebViewFeature\nimport coil3.ImageLoader\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.runInterruptible\nimport okhttp3.Cache\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.network.cookies.MutableCookieJar\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.local.data.CacheDir\nimport org.koitharu.kotatsu.local.data.LocalStorageManager\nimport org.koitharu.kotatsu.local.domain.DeleteReadChaptersUseCase\nimport org.koitharu.kotatsu.search.domain.MangaSearchRepository\nimport org.koitharu.kotatsu.tracker.domain.TrackingRepository\nimport java.util.EnumMap\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n@HiltViewModel\nclass DataCleanupSettingsViewModel @Inject constructor(\n    private val storageManager: LocalStorageManager,\n    private val httpCache: Cache,\n    private val searchRepository: MangaSearchRepository,\n    private val trackingRepository: TrackingRepository,\n    private val cookieJar: MutableCookieJar,\n    private val deleteReadChaptersUseCase: DeleteReadChaptersUseCase,\n    private val mangaDataRepositoryProvider: Provider<MangaDataRepository>,\n    private val coil: ImageLoader,\n) : BaseViewModel() {\n\n    val onActionDone = MutableEventFlow<ReversibleAction>()\n    val loadingKeys = MutableStateFlow(emptySet<String>())\n\n    val searchHistoryCount = MutableStateFlow(-1)\n    val feedItemsCount = MutableStateFlow(-1)\n    val httpCacheSize = MutableStateFlow(-1L)\n    val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)\n\n    val onChaptersCleanedUp = MutableEventFlow<Pair<Int, Long>>()\n\n    val isBrowserDataCleanupEnabled: Boolean\n        get() = WebViewFeature.isFeatureSupported(WebViewFeature.DELETE_BROWSING_DATA)\n\n    init {\n        CacheDir.entries.forEach {\n            cacheSizes[it] = MutableStateFlow(-1L)\n        }\n        launchJob(Dispatchers.Default) {\n            searchHistoryCount.value = searchRepository.getSearchHistoryCount()\n        }\n        launchJob(Dispatchers.Default) {\n            feedItemsCount.value = trackingRepository.getLogsCount()\n        }\n        CacheDir.entries.forEach { cache ->\n            launchJob(Dispatchers.Default) {\n                checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)\n            }\n        }\n        launchJob(Dispatchers.Default) {\n            httpCacheSize.value = runInterruptible { httpCache.size() }\n        }\n    }\n\n    fun clearCache(key: String, vararg caches: CacheDir) {\n        launchJob(Dispatchers.Default) {\n            try {\n                loadingKeys.update { it + key }\n                for (cache in caches) {\n                    storageManager.clearCache(cache)\n                    checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)\n                    if (cache == CacheDir.THUMBS) {\n                        coil.memoryCache?.clear()\n                    }\n                }\n            } finally {\n                loadingKeys.update { it - key }\n            }\n        }\n    }\n\n    fun clearHttpCache() {\n        launchJob(Dispatchers.Default) {\n            try {\n                loadingKeys.update { it + AppSettings.KEY_HTTP_CACHE_CLEAR }\n                val size = runInterruptible(Dispatchers.IO) {\n                    httpCache.evictAll()\n                    httpCache.size()\n                }\n                httpCacheSize.value = size\n            } finally {\n                loadingKeys.update { it - AppSettings.KEY_HTTP_CACHE_CLEAR }\n            }\n        }\n    }\n\n    fun clearSearchHistory() {\n        launchJob(Dispatchers.Default) {\n            searchRepository.clearSearchHistory()\n            searchHistoryCount.value = searchRepository.getSearchHistoryCount()\n            onActionDone.call(ReversibleAction(R.string.search_history_cleared, null))\n        }\n    }\n\n    fun clearCookies() {\n        launchJob {\n            cookieJar.clear()\n            onActionDone.call(ReversibleAction(R.string.cookies_cleared, null))\n        }\n    }\n\n    @SuppressLint(\"RequiresFeature\")\n    fun clearBrowserData() {\n        launchJob {\n            try {\n                loadingKeys.update { it + AppSettings.KEY_WEBVIEW_CLEAR }\n                val storage = WebStorage.getInstance()\n                suspendCoroutine { cont ->\n                    WebStorageCompat.deleteBrowsingData(storage) {\n                        cont.resume(Unit)\n                    }\n                }\n                onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))\n            } finally {\n                loadingKeys.update { it - AppSettings.KEY_WEBVIEW_CLEAR }\n            }\n        }\n    }\n\n    fun clearUpdatesFeed() {\n        launchJob(Dispatchers.Default) {\n            try {\n                loadingKeys.update { it + AppSettings.KEY_UPDATES_FEED_CLEAR }\n                trackingRepository.clearLogs()\n                feedItemsCount.value = trackingRepository.getLogsCount()\n                onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))\n            } finally {\n                loadingKeys.update { it - AppSettings.KEY_UPDATES_FEED_CLEAR }\n            }\n        }\n    }\n\n    fun clearMangaData() {\n        launchJob(Dispatchers.Default) {\n            try {\n                loadingKeys.update { it + AppSettings.KEY_CLEAR_MANGA_DATA }\n                trackingRepository.gc()\n                val repository = mangaDataRepositoryProvider.get()\n                repository.cleanupLocalManga()\n                repository.cleanupDatabase()\n                onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))\n            } finally {\n                loadingKeys.update { it - AppSettings.KEY_CLEAR_MANGA_DATA }\n            }\n        }\n    }\n\n    fun cleanupChapters() {\n        launchJob(Dispatchers.Default) {\n            try {\n                loadingKeys.update { it + AppSettings.KEY_CHAPTERS_CLEAR }\n                val oldSize = storageManager.computeStorageSize()\n                val chaptersCount = deleteReadChaptersUseCase.invoke()\n                val newSize = storageManager.computeStorageSize()\n                onChaptersCleanedUp.call(chaptersCount to oldSize - newSize)\n            } finally {\n                loadingKeys.update { it - AppSettings.KEY_CHAPTERS_CLEAR }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/storage/StorageUsage.kt",
    "content": "package org.koitharu.kotatsu.settings.userdata.storage\n\ndata class StorageUsage(\n\tval savedManga: Item,\n\tval pagesCache: Item,\n\tval otherCache: Item,\n\tval available: Item,\n) {\n\tdata class Item(\n\t\tval bytes: Long,\n\t\tval percent: Float,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/storage/StorageUsagePreference.kt",
    "content": "package org.koitharu.kotatsu.settings.userdata.storage\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport androidx.annotation.StringRes\nimport androidx.core.widget.TextViewCompat\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceViewHolder\nimport kotlinx.coroutines.flow.FlowCollector\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView\nimport org.koitharu.kotatsu.core.util.FileSize\nimport org.koitharu.kotatsu.core.util.KotatsuColors\nimport org.koitharu.kotatsu.databinding.PreferenceMemoryUsageBinding\n\nclass StorageUsagePreference @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n) : Preference(context, attrs), FlowCollector<StorageUsage?> {\n\n\tprivate val labelPattern = context.getString(R.string.memory_usage_pattern)\n\tprivate var usage: StorageUsage? = null\n\n\tinit {\n\t\tlayoutResource = R.layout.preference_memory_usage\n\t\tisSelectable = false\n\t\tisPersistent = false\n\t}\n\n\toverride fun onBindViewHolder(holder: PreferenceViewHolder) {\n\t\tsuper.onBindViewHolder(holder)\n\t\tval binding = PreferenceMemoryUsageBinding.bind(holder.itemView)\n\t\tval storageSegment = SegmentedBarView.Segment(\n\t\t\tusage?.savedManga?.percent ?: 0f,\n\t\t\tKotatsuColors.segmentColorRandom(context, Color.BLUE),\n\t\t)\n\t\tval pagesSegment = SegmentedBarView.Segment(\n\t\t\tusage?.pagesCache?.percent ?: 0f,\n\t\t\tKotatsuColors.segmentColorRandom(context, Color.GREEN),\n\t\t)\n\t\tval otherSegment = SegmentedBarView.Segment(\n\t\t\tusage?.otherCache?.percent ?: 0f,\n\t\t\tKotatsuColors.segmentColorRandom(context, Color.GRAY),\n\t\t)\n\n\t\twith(binding) {\n\t\t\tbar.animateSegments(listOf(storageSegment, pagesSegment, otherSegment).filter { it.percent > 0f })\n\t\t\tlabelStorage.text = formatLabel(usage?.savedManga, R.string.saved_manga)\n\t\t\tlabelPagesCache.text = formatLabel(usage?.pagesCache, R.string.pages_cache)\n\t\t\tlabelOtherCache.text = formatLabel(usage?.otherCache, R.string.other_cache)\n\t\t\tlabelAvailable.text = formatLabel(usage?.available, R.string.available, R.string.available)\n\n\t\t\tTextViewCompat.setCompoundDrawableTintList(labelStorage, ColorStateList.valueOf(storageSegment.color))\n\t\t\tTextViewCompat.setCompoundDrawableTintList(labelPagesCache, ColorStateList.valueOf(pagesSegment.color))\n\t\t\tTextViewCompat.setCompoundDrawableTintList(labelOtherCache, ColorStateList.valueOf(otherSegment.color))\n\t\t}\n\t}\n\n\toverride suspend fun emit(value: StorageUsage?) {\n\t\tusage = value\n\t\tnotifyChanged()\n\t}\n\n\tprivate fun formatLabel(\n\t\titem: StorageUsage.Item?,\n\t\t@StringRes labelResId: Int,\n\t\t@StringRes emptyResId: Int = R.string.computing_,\n\t): String {\n\t\treturn if (item != null) {\n\t\t\tlabelPattern.format(\n\t\t\t\tFileSize.BYTES.format(context, item.bytes),\n\t\t\t\tcontext.getString(labelResId),\n\t\t\t)\n\t\t} else {\n\t\t\tcontext.getString(emptyResId)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/ActivityListPreference.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.util.AttributeSet\nimport androidx.preference.ListPreference\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\n\n@Suppress(\"unused\")\nclass ActivityListPreference : ListPreference {\n\n\tvar activityIntent: Intent? = null\n\n\tconstructor(\n\t\tcontext: Context,\n\t\tattrs: AttributeSet?,\n\t\tdefStyleAttr: Int,\n\t\tdefStyleRes: Int\n\t) : super(context, attrs, defStyleAttr, defStyleRes)\n\n\tconstructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)\n\tconstructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n\tconstructor(context: Context) : super(context)\n\n\toverride fun onClick() {\n\t\tval intent = activityIntent\n\t\tif (intent == null) {\n\t\t\tsuper.onClick()\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tcontext.startActivity(intent)\n\t\t} catch (e: ActivityNotFoundException) {\n\t\t\te.printStackTraceDebug()\n\t\t\tsuper.onClick()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/AutoCompleteTextViewPreference.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ArrayAdapter\nimport android.widget.AutoCompleteTextView\nimport android.widget.EditText\nimport androidx.annotation.ArrayRes\nimport androidx.annotation.AttrRes\nimport androidx.annotation.StyleRes\nimport androidx.core.content.withStyledAttributes\nimport androidx.preference.EditTextPreference\nimport org.koitharu.kotatsu.R\n\nclass AutoCompleteTextViewPreference @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = R.attr.autoCompleteTextViewPreferenceStyle,\n\t@StyleRes defStyleRes: Int = R.style.Preference_AutoCompleteTextView,\n) : EditTextPreference(context, attrs, defStyleAttr, defStyleRes) {\n\n\tprivate val autoCompleteBindListener = AutoCompleteBindListener()\n\tvar entries: Array<String> = emptyArray()\n\n\tinit {\n\t\tsuper.setOnBindEditTextListener(autoCompleteBindListener)\n\t\tcontext.withStyledAttributes(attrs, R.styleable.AutoCompleteTextViewPreference, defStyleAttr, defStyleRes) {\n\t\t\tval entriesId = getResourceId(R.styleable.AutoCompleteTextViewPreference_android_entries, 0)\n\t\t\tif (entriesId != 0) {\n\t\t\t\tsetEntries(entriesId)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun setEntries(@ArrayRes arrayResId: Int) {\n\t\tthis.entries = context.resources.getStringArray(arrayResId)\n\t}\n\n\tfun setEntries(entries: Collection<String>) {\n\t\tthis.entries = entries.toTypedArray()\n\t}\n\n\toverride fun setOnBindEditTextListener(onBindEditTextListener: OnBindEditTextListener?) {\n\t\tautoCompleteBindListener.delegate = onBindEditTextListener\n\t}\n\n\tprivate inner class AutoCompleteBindListener : OnBindEditTextListener {\n\n\t\tvar delegate: OnBindEditTextListener? = null\n\n\t\toverride fun onBindEditText(editText: EditText) {\n\t\t\tdelegate?.onBindEditText(editText)\n\t\t\tif (editText !is AutoCompleteTextView || entries.isEmpty()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\teditText.threshold = 0\n\t\t\teditText.setAdapter(ArrayAdapter(editText.context, android.R.layout.simple_spinner_dropdown_item, entries))\n\t\t\t(editText.parent as? ViewGroup)?.findViewById<View>(R.id.dropdown)?.setOnClickListener {\n\t\t\t\teditText.showDropDown()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/DozeHelper.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.annotation.SuppressLint\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.os.PowerManager\nimport android.provider.Settings\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.net.toUri\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.powerManager\n\n@SuppressLint(\"BatteryLife\")\nclass DozeHelper(\n\tprivate val fragment: PreferenceFragmentCompat,\n) {\n\n\tprivate val startForDozeResult = fragment.registerForActivityResult(\n\t\tActivityResultContracts.StartActivityForResult(),\n\t) {\n\t\tupdatePreference()\n\t}\n\n\tfun updatePreference() {\n\t\tval preference = fragment.findPreference<Preference>(AppSettings.KEY_IGNORE_DOZE) ?: return\n\t\tpreference.isVisible = isDozeIgnoreAvailable()\n\t}\n\n\tfun startIgnoreDoseActivity(): Boolean {\n\t\tval context = fragment.context ?: return false\n\t\tval packageName = context.packageName\n\t\tval powerManager = context.powerManager ?: return false\n\t\treturn if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {\n\t\t\ttry {\n\t\t\t\tval intent = Intent(\n\t\t\t\t\tSettings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,\n\t\t\t\t\t\"package:$packageName\".toUri(),\n\t\t\t\t)\n\t\t\t\tstartForDozeResult.launch(intent)\n\t\t\t\ttrue\n\t\t\t} catch (e: ActivityNotFoundException) {\n\t\t\t\tSnackbar.make(fragment.listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()\n\t\t\t\tfalse\n\t\t\t}\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tprivate fun isDozeIgnoreAvailable(): Boolean {\n\t\tval context = fragment.context ?: return false\n\t\tval packageName = context.packageName\n\t\tval powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager\n\t\treturn !powerManager.isIgnoringBatteryOptimizations(packageName)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/EditTextBindListener.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.widget.EditText\nimport androidx.preference.EditTextPreference\nimport org.koitharu.kotatsu.core.util.EditTextValidator\n\nclass EditTextBindListener(\n\tprivate val inputType: Int,\n\tprivate val hint: String?,\n\tprivate val validator: EditTextValidator?,\n) : EditTextPreference.OnBindEditTextListener {\n\n\toverride fun onBindEditText(editText: EditText) {\n\t\teditText.inputType = inputType\n\t\teditText.hint = hint\n\t\tvalidator?.attachToEditText(editText)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/EditTextDefaultSummaryProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport androidx.preference.EditTextPreference\nimport androidx.preference.Preference\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\n\nclass EditTextDefaultSummaryProvider(\n\tprivate val defaultValue: String,\n) : Preference.SummaryProvider<EditTextPreference> {\n\n\toverride fun provideSummary(\n\t\tpreference: EditTextPreference,\n\t): CharSequence = preference.text.ifNullOrEmpty {\n\t\tpreference.context.getString(R.string.default_s, defaultValue)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/EditTextFallbackSummaryProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport androidx.annotation.StringRes\nimport androidx.preference.EditTextPreference\nimport androidx.preference.Preference\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\n\nclass EditTextFallbackSummaryProvider(\n\t@StringRes private val fallbackResId: Int,\n) : Preference.SummaryProvider<EditTextPreference> {\n\n\toverride fun provideSummary(\n\t\tpreference: EditTextPreference,\n\t): CharSequence = preference.text.ifNullOrEmpty {\n\t\tpreference.context.getString(fallbackResId)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/LinksPreference.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.TextView\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceViewHolder\n\nclass LinksPreference @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = androidx.preference.R.attr.preferenceStyle,\n\tdefStyleRes: Int = 0,\n) : Preference(context, attrs, defStyleAttr, defStyleRes) {\n\toverride fun onBindViewHolder(holder: PreferenceViewHolder) {\n\t\tsuper.onBindViewHolder(holder)\n\t\tval summaryView = holder.findViewById(android.R.id.summary) as TextView\n\t\tsummaryView.movementMethod = LinkMovementMethodCompat.getInstance()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/MultiAutoCompleteTextViewPreference.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.ArrayAdapter\nimport android.widget.EditText\nimport android.widget.Filter\nimport android.widget.MultiAutoCompleteTextView\nimport androidx.annotation.AttrRes\nimport androidx.annotation.MainThread\nimport androidx.annotation.StyleRes\nimport androidx.annotation.WorkerThread\nimport androidx.preference.EditTextPreference\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.parsers.util.replaceWith\n\nclass MultiAutoCompleteTextViewPreference @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\t@AttrRes defStyleAttr: Int = R.attr.multiAutoCompleteTextViewPreferenceStyle,\n\t@StyleRes defStyleRes: Int = R.style.Preference_MultiAutoCompleteTextView,\n) : EditTextPreference(context, attrs, defStyleAttr, defStyleRes) {\n\n\tprivate val autoCompleteBindListener = AutoCompleteBindListener()\n\n\tvar autoCompleteProvider: AutoCompleteProvider? = null\n\n\tinit {\n\t\tsuper.setOnBindEditTextListener(autoCompleteBindListener)\n\t}\n\n\toverride fun setOnBindEditTextListener(onBindEditTextListener: OnBindEditTextListener?) {\n\t\tautoCompleteBindListener.delegate = onBindEditTextListener\n\t}\n\n\tprivate inner class AutoCompleteBindListener : OnBindEditTextListener {\n\n\t\tvar delegate: OnBindEditTextListener? = null\n\n\t\toverride fun onBindEditText(editText: EditText) {\n\t\t\tdelegate?.onBindEditText(editText)\n\t\t\tif (editText !is MultiAutoCompleteTextView) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\teditText.setTokenizer(MultiAutoCompleteTextView.CommaTokenizer())\n\t\t\teditText.setAdapter(\n\t\t\t\tautoCompleteProvider?.let {\n\t\t\t\t\tCompletionAdapter(editText.context, it, ArrayList())\n\t\t\t\t}\n\t\t\t)\n\t\t\teditText.threshold = 1\n\t\t}\n\t}\n\n\tinterface AutoCompleteProvider {\n\n\t\tsuspend fun getSuggestions(query: String): List<String>\n\t}\n\n\tclass SimpleSummaryProvider(\n\t\tprivate val emptySummary: CharSequence?,\n\t) : SummaryProvider<MultiAutoCompleteTextViewPreference> {\n\n\t\toverride fun provideSummary(preference: MultiAutoCompleteTextViewPreference): CharSequence? {\n\t\t\treturn if (preference.text.isNullOrEmpty()) {\n\t\t\t\temptySummary\n\t\t\t} else {\n\t\t\t\tpreference.text?.trimEnd(' ', ',')\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class CompletionAdapter(\n\t\tcontext: Context,\n\t\tprivate val completionProvider: AutoCompleteProvider,\n\t\tprivate val dataset: MutableList<String>,\n\t) : ArrayAdapter<String>(context, android.R.layout.simple_dropdown_item_1line, dataset) {\n\n\t\toverride fun getFilter(): Filter {\n\t\t\treturn CompletionFilter(this, completionProvider)\n\t\t}\n\n\t\tfun publishResults(results: List<String>) {\n\t\t\tdataset.replaceWith(results)\n\t\t\tnotifyDataSetChanged()\n\t\t}\n\t}\n\n\tprivate class CompletionFilter(\n\t\tprivate val adapter: CompletionAdapter,\n\t\tprivate val provider: AutoCompleteProvider,\n\t) : Filter() {\n\n\t\t@WorkerThread\n\t\toverride fun performFiltering(constraint: CharSequence?): FilterResults {\n\t\t\tval query = constraint?.toString().orEmpty()\n\t\t\tval suggestions = runBlocking { provider.getSuggestions(query) }\n\t\t\treturn CompletionResults(suggestions)\n\t\t}\n\n\t\t@MainThread\n\t\toverride fun publishResults(constraint: CharSequence?, results: FilterResults) {\n\t\t\tval completions = (results as CompletionResults).completions\n\t\t\tadapter.publishResults(completions)\n\t\t}\n\n\t\tprivate class CompletionResults(\n\t\t\tval completions: List<String>,\n\t\t) : FilterResults() {\n\n\t\t\tinit {\n\t\t\t\tvalues = completions\n\t\t\t\tcount = completions.size\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/MultiSummaryProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport androidx.annotation.StringRes\nimport androidx.preference.MultiSelectListPreference\nimport androidx.preference.Preference\n\nclass MultiSummaryProvider(@StringRes private val emptySummaryId: Int) :\n\tPreference.SummaryProvider<MultiSelectListPreference> {\n\n\toverride fun provideSummary(preference: MultiSelectListPreference): CharSequence {\n\t\tval values = preference.values\n\t\treturn if (values.isEmpty()) {\n\t\t\treturn preference.context.getString(emptySummaryId)\n\t\t} else {\n\t\t\tvalues.joinToString(\", \") {\n\t\t\t\tpreference.entries.getOrNull(preference.findIndexOfValue(it))\n\t\t\t\t\t?: preference.context.getString(androidx.preference.R.string.not_set)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/PasswordSummaryProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.text.TextUtils\nimport androidx.preference.EditTextPreference\nimport androidx.preference.Preference\n\nclass PasswordSummaryProvider : Preference.SummaryProvider<EditTextPreference> {\n\n\tprivate val delegate = EditTextPreference.SimpleSummaryProvider.getInstance()\n\n\toverride fun provideSummary(preference: EditTextPreference): CharSequence? {\n\t\tval summary = delegate.provideSummary(preference)\n\t\treturn if (summary != null && !TextUtils.isEmpty(preference.text)) {\n\t\t\tString(CharArray(summary.length) { '\\u2022' })\n\t\t} else {\n\t\t\tsummary\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/PercentSummaryProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport androidx.preference.Preference\nimport org.koitharu.kotatsu.R\n\nclass PercentSummaryProvider : Preference.SummaryProvider<SliderPreference> {\n\n\tprivate var percentPattern: String? = null\n\n\toverride fun provideSummary(preference: SliderPreference): CharSequence {\n\t\tval pattern = percentPattern ?: preference.context.getString(R.string.percent_string_pattern).also {\n\t\t\tpercentPattern = it\n\t\t}\n\t\treturn pattern.format(preference.value.toString())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.Context\nimport android.content.Intent\nimport android.media.RingtoneManager\nimport android.net.Uri\nimport android.provider.Settings\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\n\nclass RingtonePickContract(@StringRes private val titleResId: Int) : ActivityResultContract<Uri?, Uri?>() {\n\n\toverride fun createIntent(context: Context, input: Uri?): Intent {\n\t\tval intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)\n\t\tintent.putExtra(\n\t\t\tRingtoneManager.EXTRA_RINGTONE_TYPE,\n\t\t\tRingtoneManager.TYPE_NOTIFICATION,\n\t\t)\n\t\tintent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)\n\t\tintent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)\n\t\tintent.putExtra(\n\t\t\tRingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,\n\t\t\tSettings.System.DEFAULT_NOTIFICATION_URI,\n\t\t)\n\t\tif (titleResId != 0) {\n\t\t\tintent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, context.getString(titleResId))\n\t\t}\n\t\tintent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, input)\n\t\treturn intent\n\t}\n\n\toverride fun parseResult(resultCode: Int, intent: Intent?): Uri? {\n\t\treturn intent?.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/SliderPreference.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.Context\nimport android.content.res.TypedArray\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.util.AttributeSet\nimport androidx.core.content.withStyledAttributes\nimport androidx.customview.view.AbsSavedState\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceViewHolder\nimport com.google.android.material.slider.Slider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.setValueRounded\n\nclass SliderPreference @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = R.attr.sliderPreferenceStyle,\n\tdefStyleRes: Int = R.style.Preference_Slider,\n) : Preference(context, attrs, defStyleAttr, defStyleRes) {\n\n\tprivate var valueFrom: Int = 0\n\tprivate var valueTo: Int = 100\n\tprivate var stepSize: Int = 1\n\tprivate var currentValue: Int = 0\n\tprivate var isTickVisible: Boolean = false\n\n\tvar value: Int\n\t\tget() = currentValue\n\t\tset(value) = setValueInternal(value, notifyChanged = true)\n\n\tprivate val sliderListener = Slider.OnChangeListener { _, value, fromUser ->\n\t\tif (fromUser) {\n\t\t\tsyncValueInternal(value.toInt())\n\t\t}\n\t}\n\n\tinit {\n\t\tcontext.withStyledAttributes(\n\t\t\tattrs,\n\t\t\tR.styleable.SliderPreference,\n\t\t\tdefStyleAttr,\n\t\t\tdefStyleRes,\n\t\t) {\n\t\t\tvalueFrom = getFloat(\n\t\t\t\tR.styleable.SliderPreference_android_valueFrom,\n\t\t\t\tvalueFrom.toFloat(),\n\t\t\t).toInt()\n\t\t\tvalueTo = getFloat(R.styleable.SliderPreference_android_valueTo, valueTo.toFloat()).toInt()\n\t\t\tstepSize = getFloat(R.styleable.SliderPreference_android_stepSize, stepSize.toFloat()).toInt()\n\t\t\tisTickVisible = getBoolean(R.styleable.SliderPreference_tickVisible, isTickVisible)\n\t\t\tif (getBoolean(R.styleable.SliderPreference_useSimpleSummaryProvider, false)) {\n\t\t\t\tsummaryProvider = SimpleSummaryProvider\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onBindViewHolder(holder: PreferenceViewHolder) {\n\t\tsuper.onBindViewHolder(holder)\n\t\tval slider = holder.findViewById(R.id.slider) as? Slider ?: return\n\t\tslider.removeOnChangeListener(sliderListener)\n\t\tslider.addOnChangeListener(sliderListener)\n\t\tslider.valueFrom = valueFrom.toFloat()\n\t\tslider.valueTo = valueTo.toFloat()\n\t\tslider.stepSize = stepSize.toFloat()\n\t\tslider.isTickVisible = isTickVisible\n\t\tslider.setValueRounded(currentValue.toFloat())\n\t\tslider.isEnabled = isEnabled\n\t}\n\n\toverride fun onSetInitialValue(defaultValue: Any?) {\n\t\tvalue = getPersistedInt(defaultValue as? Int ?: 0)\n\t}\n\n\toverride fun onGetDefaultValue(a: TypedArray, index: Int): Any {\n\t\treturn a.getInt(index, 0)\n\t}\n\n\toverride fun onSaveInstanceState(): Parcelable? {\n\t\tval superState = super.onSaveInstanceState()\n\t\tif (superState == null || isPersistent) {\n\t\t\treturn superState\n\t\t}\n\t\treturn SavedState(\n\t\t\tsuperState = superState,\n\t\t\tvalueFrom = valueFrom,\n\t\t\tvalueTo = valueTo,\n\t\t\tcurrentValue = currentValue,\n\t\t)\n\t}\n\n\toverride fun onRestoreInstanceState(state: Parcelable?) {\n\t\tif (state !is SavedState) {\n\t\t\tsuper.onRestoreInstanceState(state)\n\t\t\treturn\n\t\t}\n\t\tsuper.onRestoreInstanceState(state.superState)\n\t\tvalueFrom = state.valueFrom\n\t\tvalueTo = state.valueTo\n\t\tcurrentValue = state.currentValue\n\t\tnotifyChanged()\n\t}\n\n\tprivate fun setValueInternal(sliderValue: Int, notifyChanged: Boolean) {\n\t\tval newValue = sliderValue.coerceIn(valueFrom, valueTo)\n\t\tif (newValue != currentValue) {\n\t\t\tcurrentValue = newValue\n\t\t\tpersistInt(newValue)\n\t\t\tif (notifyChanged) {\n\t\t\t\tnotifyChanged()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun syncValueInternal(sliderValue: Int) {\n\t\tif (sliderValue != currentValue) {\n\t\t\tif (callChangeListener(sliderValue)) {\n\t\t\t\tsetValueInternal(sliderValue, notifyChanged = true)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate object SimpleSummaryProvider : SummaryProvider<SliderPreference> {\n\n\t\toverride fun provideSummary(preference: SliderPreference) = preference.value.toString()\n\t}\n\n\tprivate class SavedState : AbsSavedState {\n\n\t\tval valueFrom: Int\n\t\tval valueTo: Int\n\t\tval currentValue: Int\n\n\t\tconstructor(\n\t\t\tsuperState: Parcelable,\n\t\t\tvalueFrom: Int,\n\t\t\tvalueTo: Int,\n\t\t\tcurrentValue: Int,\n\t\t) : super(superState) {\n\t\t\tthis.valueFrom = valueFrom\n\t\t\tthis.valueTo = valueTo\n\t\t\tthis.currentValue = currentValue\n\t\t}\n\n\t\tconstructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) {\n\t\t\tvalueFrom = source.readInt()\n\t\t\tvalueTo = source.readInt()\n\t\t\tcurrentValue = source.readInt()\n\t\t}\n\n\t\toverride fun writeToParcel(out: Parcel, flags: Int) {\n\t\t\tsuper.writeToParcel(out, flags)\n\t\t\tout.writeInt(valueFrom)\n\t\t\tout.writeInt(valueTo)\n\t\t\tout.writeInt(currentValue)\n\t\t}\n\n\t\tcompanion object {\n\t\t\t@Suppress(\"unused\")\n\t\t\t@JvmField\n\t\t\tval CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {\n\t\t\t\toverride fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader)\n\n\t\t\t\toverride fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/SplitSwitchPreference.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.preference.PreferenceViewHolder\nimport androidx.preference.SwitchPreferenceCompat\nimport org.koitharu.kotatsu.R\n\nclass SplitSwitchPreference @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = androidx.preference.R.attr.switchPreferenceCompatStyle,\n\tdefStyleRes: Int = 0\n) : SwitchPreferenceCompat(context, attrs, defStyleAttr, defStyleRes) {\n\n\tinit {\n\t\tlayoutResource = R.layout.preference_split_switch\n\t}\n\n\tvar onContainerClickListener: OnPreferenceClickListener? = null\n\n\tprivate val containerClickListener = View.OnClickListener {\n\t\tonContainerClickListener?.onPreferenceClick(this)\n\t}\n\n\toverride fun onBindViewHolder(holder: PreferenceViewHolder) {\n\t\tsuper.onBindViewHolder(holder)\n\t\tholder.findViewById(R.id.press_container)?.setOnClickListener(containerClickListener)\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/TagsAutoCompleteProvider.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport javax.inject.Inject\nimport org.koitharu.kotatsu.core.db.MangaDatabase\n\nclass TagsAutoCompleteProvider @Inject constructor(\n\tprivate val db: MangaDatabase,\n) : MultiAutoCompleteTextViewPreference.AutoCompleteProvider {\n\n\toverride suspend fun getSuggestions(query: String): List<String> {\n\t\tif (query.isEmpty()) {\n\t\t\treturn emptyList()\n\t\t}\n\t\tval tags = db.getTagsDao().findTags(query = \"$query%\", limit = 6)\n\t\tval set = HashSet<String>()\n\t\tval result = ArrayList<String>(tags.size)\n\t\tfor (tag in tags) {\n\t\t\tif (set.add(tag.title)) {\n\t\t\t\tresult.add(tag.title)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/ThemeChooserPreference.kt",
    "content": "package org.koitharu.kotatsu.settings.utils\n\nimport android.content.Context\nimport android.content.res.TypedArray\nimport android.os.Build\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewTreeObserver\nimport android.widget.HorizontalScrollView\nimport androidx.appcompat.view.ContextThemeWrapper\nimport androidx.core.view.isEmpty\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePaddingRelative\nimport androidx.customview.view.AbsSavedState\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceViewHolder\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.ColorScheme\nimport org.koitharu.kotatsu.databinding.ItemColorSchemeBinding\nimport org.koitharu.kotatsu.databinding.PreferenceThemeBinding\nimport java.lang.ref.WeakReference\nimport com.google.android.material.R as materialR\n\nclass ThemeChooserPreference @JvmOverloads constructor(\n\tcontext: Context,\n\tattrs: AttributeSet? = null,\n\tdefStyleAttr: Int = R.attr.themeChooserPreferenceStyle,\n\tdefStyleRes: Int = R.style.Preference_ThemeChooser,\n) : Preference(context, attrs, defStyleAttr, defStyleRes) {\n\n\tprivate val entries = ColorScheme.getAvailableList()\n\tprivate var currentValue: ColorScheme = ColorScheme.default\n\tprivate val lastScrollPosition = intArrayOf(-1)\n\tprivate val itemClickListener = View.OnClickListener {\n\t\tval tag = it.tag as? ColorScheme ?: return@OnClickListener\n\t\tsetValueInternal(tag.name, true)\n\t}\n\tprivate var scrollPersistListener: ScrollPersistListener? = null\n\n\tvar value: String\n\t\tget() = currentValue.name\n\t\tset(value) = setValueInternal(value, notifyChanged = true)\n\n\toverride fun onBindViewHolder(holder: PreferenceViewHolder) {\n\t\tsuper.onBindViewHolder(holder)\n\t\tval binding = PreferenceThemeBinding.bind(holder.itemView)\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\t\tbinding.scrollView.suppressLayout(true)\n\t\t\tbinding.linear.suppressLayout(true)\n\t\t}\n\t\tbinding.linear.removeAllViews()\n\t\tfor (theme in entries) {\n\t\t\tval context = ContextThemeWrapper(context, theme.styleResId)\n\t\t\tval item =\n\t\t\t\tItemColorSchemeBinding.inflate(LayoutInflater.from(context), binding.linear, false)\n\t\t\tif (binding.linear.isEmpty()) {\n\t\t\t\titem.root.updatePaddingRelative(start = 0)\n\t\t\t}\n\t\t\tval isSelected = theme == currentValue\n\t\t\titem.card.isChecked = isSelected\n\t\t\titem.card.strokeWidth = if (isSelected) context.resources.getDimensionPixelSize(\n\t\t\t\tmaterialR.dimen.m3_comp_outlined_card_outline_width,\n\t\t\t) else 0\n\t\t\titem.textViewTitle.setText(theme.titleResId)\n\t\t\titem.root.tag = theme\n\t\t\titem.card.tag = theme\n\t\t\titem.imageViewCheck.isVisible = theme == currentValue\n\t\t\titem.root.setOnClickListener(itemClickListener)\n\t\t\titem.card.setOnClickListener(itemClickListener)\n\t\t\tbinding.linear.addView(item.root)\n\t\t\tif (isSelected) {\n\t\t\t\titem.root.requestFocus()\n\t\t\t}\n\t\t}\n\t\tif (lastScrollPosition[0] >= 0) {\n\t\t\tval scroller = Scroller(binding.scrollView, lastScrollPosition[0])\n\t\t\tscroller.run()\n\t\t\tbinding.scrollView.post(scroller)\n\t\t}\n\t\tbinding.scrollView.viewTreeObserver.run {\n\t\t\tscrollPersistListener?.let { removeOnScrollChangedListener(it) }\n\t\t\tscrollPersistListener =\n\t\t\t\tScrollPersistListener(WeakReference(binding.scrollView), lastScrollPosition)\n\t\t\taddOnScrollChangedListener(scrollPersistListener)\n\t\t}\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\t\tbinding.linear.suppressLayout(false)\n\t\t\tbinding.scrollView.suppressLayout(false)\n\t\t}\n\t}\n\n\toverride fun onSetInitialValue(defaultValue: Any?) {\n\t\tvalue = getPersistedString(\n\t\t\twhen (defaultValue) {\n\t\t\t\tis String -> ColorScheme.safeValueOf(defaultValue) ?: ColorScheme.default\n\t\t\t\tis ColorScheme -> defaultValue\n\t\t\t\telse -> ColorScheme.default\n\t\t\t}.name,\n\t\t)\n\t}\n\n\toverride fun onGetDefaultValue(a: TypedArray, index: Int): Any {\n\t\treturn a.getInt(index, 0)\n\t}\n\n\toverride fun onSaveInstanceState(): Parcelable? {\n\t\tval superState = super.onSaveInstanceState() ?: return null\n\t\treturn SavedState(\n\t\t\tsuperState = superState,\n\t\t\tscrollPosition = lastScrollPosition[0],\n\t\t)\n\t}\n\n\toverride fun onRestoreInstanceState(state: Parcelable?) {\n\t\tif (state !is SavedState) {\n\t\t\tsuper.onRestoreInstanceState(state)\n\t\t\treturn\n\t\t}\n\t\tsuper.onRestoreInstanceState(state.superState)\n\t\tlastScrollPosition[0] = state.scrollPosition\n\t}\n\n\tprivate fun setValueInternal(enumName: String, notifyChanged: Boolean) {\n\t\tval newValue = ColorScheme.safeValueOf(enumName) ?: return\n\t\tif (newValue != currentValue) {\n\t\t\tcurrentValue = newValue\n\t\t\tpersistString(newValue.name)\n\t\t\tif (notifyChanged) {\n\t\t\t\tnotifyChanged()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class SavedState : AbsSavedState {\n\n\t\tval scrollPosition: Int\n\n\t\tconstructor(\n\t\t\tsuperState: Parcelable,\n\t\t\tscrollPosition: Int,\n\t\t) : super(superState) {\n\t\t\tthis.scrollPosition = scrollPosition\n\t\t}\n\n\t\tconstructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) {\n\t\t\tscrollPosition = source.readInt()\n\t\t}\n\n\t\toverride fun writeToParcel(out: Parcel, flags: Int) {\n\t\t\tsuper.writeToParcel(out, flags)\n\t\t\tout.writeInt(scrollPosition)\n\t\t}\n\n\t\tcompanion object {\n\t\t\t@Suppress(\"unused\")\n\t\t\t@JvmField\n\t\t\tval CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {\n\t\t\t\toverride fun createFromParcel(`in`: Parcel) =\n\t\t\t\t\tSavedState(`in`, SavedState::class.java.classLoader)\n\n\t\t\t\toverride fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class ScrollPersistListener(\n\t\tprivate val scrollViewRef: WeakReference<HorizontalScrollView>,\n\t\tprivate val lastScrollPosition: IntArray,\n\t) : ViewTreeObserver.OnScrollChangedListener {\n\n\t\toverride fun onScrollChanged() {\n\t\t\tval scrollView = scrollViewRef.get() ?: return\n\t\t\tlastScrollPosition[0] = scrollView.scrollX\n\t\t}\n\t}\n\n\tprivate class Scroller(\n\t\tprivate val scrollView: HorizontalScrollView,\n\t\tprivate val position: Int,\n\t) : Runnable {\n\n\t\toverride fun run() {\n\t\t\tscrollView.scrollTo(position, 0)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/DomainValidator.kt",
    "content": "package org.koitharu.kotatsu.settings.utils.validation\n\nimport okhttp3.HttpUrl\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.EditTextValidator\n\nclass DomainValidator : EditTextValidator() {\n\n\toverride fun validate(text: String): ValidationResult {\n\t\tval trimmed = text.trim()\n\t\tif (trimmed.isEmpty()) {\n\t\t\treturn ValidationResult.Success\n\t\t}\n\t\treturn if (!isValidDomain(trimmed)) {\n\t\t\tValidationResult.Failed(context.getString(R.string.invalid_domain_message))\n\t\t} else {\n\t\t\tValidationResult.Success\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\tfun isValidDomain(value: String): Boolean = runCatching {\n\t\t\trequire(value.isNotEmpty())\n\t\t\tval parts = value.split(':')\n\t\t\trequire(parts.size <= 2)\n\t\t\tval urlBuilder = HttpUrl.Builder()\n\t\t\turlBuilder.host(parts.first())\n\t\t\tif (parts.size == 2) {\n\t\t\t\turlBuilder.port(parts[1].toInt())\n\t\t\t}\n\t\t}.isSuccess\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/HeaderValidator.kt",
    "content": "package org.koitharu.kotatsu.settings.utils.validation\n\nimport okhttp3.Headers\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.network.CommonHeaders\nimport org.koitharu.kotatsu.core.util.EditTextValidator\n\nclass HeaderValidator : EditTextValidator() {\n\n\tprivate val headers = Headers.Builder()\n\n\toverride fun validate(text: String): ValidationResult {\n\t\tval trimmed = text.trim()\n\t\tif (trimmed.isEmpty()) {\n\t\t\treturn ValidationResult.Success\n\t\t}\n\t\treturn if (!validateImpl(trimmed)) {\n\t\t\tValidationResult.Failed(context.getString(R.string.invalid_value_message))\n\t\t} else {\n\t\t\tValidationResult.Success\n\t\t}\n\t}\n\n\tprivate fun validateImpl(value: String): Boolean = runCatching {\n\t\theaders[CommonHeaders.USER_AGENT] = value\n\t}.isSuccess\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/PortNumberValidator.kt",
    "content": "package org.koitharu.kotatsu.settings.utils.validation\n\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.EditTextValidator\n\nclass PortNumberValidator : EditTextValidator() {\n\n\toverride fun validate(text: String): ValidationResult {\n\t\tval trimmed = text.trim()\n\t\tif (trimmed.isEmpty()) {\n\t\t\treturn ValidationResult.Success\n\t\t}\n\t\treturn if (!checkCharacters(trimmed)) {\n\t\t\tValidationResult.Failed(context.getString(R.string.invalid_port_number))\n\t\t} else {\n\t\t\tValidationResult.Success\n\t\t}\n\t}\n\n\tprivate fun checkCharacters(value: String): Boolean {\n\t\tval intValue = value.toIntOrNull() ?: return false\n\t\treturn intValue in 1..65535\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/validation/UrlValidator.kt",
    "content": "package org.koitharu.kotatsu.settings.utils.validation\n\nimport android.webkit.URLUtil\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.EditTextValidator\n\nclass UrlValidator : EditTextValidator() {\n\n\toverride fun validate(text: String): ValidationResult {\n\t\tval trimmed = text.trim()\n\t\tif (trimmed.isEmpty()) {\n\t\t\treturn ValidationResult.Success\n\t\t}\n\t\treturn if (!isValidUrl(trimmed)) {\n\t\t\tValidationResult.Failed(context.getString(R.string.invalid_server_address_message))\n\t\t} else {\n\t\t\tValidationResult.Success\n\t\t}\n\t}\n\n\tprivate fun isValidUrl(str: String): Boolean {\n\t\treturn URLUtil.isValidUrl(str) || DomainValidator.isValidDomain(str)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/work/PeriodicWorkScheduler.kt",
    "content": "package org.koitharu.kotatsu.settings.work\n\ninterface PeriodicWorkScheduler {\n\n\tsuspend fun schedule()\n\n\tsuspend fun unschedule()\n\n\tsuspend fun isScheduled(): Boolean\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/settings/work/WorkScheduleManager.kt",
    "content": "package org.koitharu.kotatsu.settings.work\n\nimport android.content.SharedPreferences\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker\nimport org.koitharu.kotatsu.tracker.work.TrackWorker\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass WorkScheduleManager @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val suggestionScheduler: SuggestionsWorker.Scheduler,\n\tprivate val trackerScheduler: TrackWorker.Scheduler,\n) : SharedPreferences.OnSharedPreferenceChangeListener {\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n\t\twhen (key) {\n\t\t\tAppSettings.KEY_TRACKER_ENABLED,\n\t\t\tAppSettings.KEY_TRACKER_FREQUENCY,\n\t\t\tAppSettings.KEY_TRACKER_WIFI_ONLY -> updateWorker(\n\t\t\t\tscheduler = trackerScheduler,\n\t\t\t\tisEnabled = settings.isTrackerEnabled,\n\t\t\t\tforce = key != AppSettings.KEY_TRACKER_ENABLED,\n\t\t\t)\n\n\t\t\tAppSettings.KEY_SUGGESTIONS,\n\t\t\tAppSettings.KEY_SUGGESTIONS_WIFI_ONLY -> updateWorker(\n\t\t\t\tscheduler = suggestionScheduler,\n\t\t\t\tisEnabled = settings.isSuggestionsEnabled,\n\t\t\t\tforce = key != AppSettings.KEY_SUGGESTIONS,\n\t\t\t)\n\t\t}\n\t}\n\n\tfun init() {\n\t\tsettings.subscribe(this)\n\t\tprocessLifecycleScope.launch(Dispatchers.Default) {\n\t\t\tupdateWorkerImpl(trackerScheduler, settings.isTrackerEnabled, true) // always force due to adaptive interval\n\t\t\tupdateWorkerImpl(suggestionScheduler, settings.isSuggestionsEnabled, false)\n\t\t}\n\t}\n\n\tprivate fun updateWorker(scheduler: PeriodicWorkScheduler, isEnabled: Boolean, force: Boolean) {\n\t\tprocessLifecycleScope.launch(Dispatchers.Default) {\n\t\t\tupdateWorkerImpl(scheduler, isEnabled, force)\n\t\t}\n\t}\n\n\tprivate suspend fun updateWorkerImpl(scheduler: PeriodicWorkScheduler, isEnabled: Boolean, force: Boolean) {\n\t\tif (force || scheduler.isScheduled() != isEnabled) {\n\t\t\tif (isEnabled) {\n\t\t\t\tscheduler.schedule()\n\t\t\t} else {\n\t\t\t\tscheduler.unschedule()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsDao.kt",
    "content": "package org.koitharu.kotatsu.stats.data\n\nimport androidx.room.Dao\nimport androidx.room.MapColumn\nimport androidx.room.Query\nimport androidx.room.RawQuery\nimport androidx.room.Upsert\nimport androidx.sqlite.db.SimpleSQLiteQuery\nimport androidx.sqlite.db.SupportSQLiteQuery\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport kotlin.collections.forEach\n\n@Dao\nabstract class StatsDao {\n\n\t@Query(\"SELECT * FROM stats WHERE manga_id = :mangaId ORDER BY started_at\")\n\tabstract suspend fun findAll(mangaId: Long): List<StatsEntity>\n\n\t@Query(\"SELECT IFNULL(SUM(pages),0) FROM stats WHERE manga_id = :mangaId\")\n\tabstract suspend fun getReadPagesCount(mangaId: Long): Int\n\n\t@Query(\"SELECT IFNULL(SUM(duration)/SUM(pages), 0) FROM stats WHERE manga_id = :mangaId\")\n\tabstract suspend fun getAverageTimePerPage(mangaId: Long): Long\n\n\t@Query(\"SELECT IFNULL(SUM(duration)/SUM(pages), 0) FROM stats\")\n\tabstract suspend fun getAverageTimePerPage(): Long\n\n\t@Query(\"DELETE FROM stats\")\n\tabstract suspend fun clear()\n\n\t@Query(\"SELECT COUNT(*) FROM stats WHERE manga_id = :mangaId\")\n\tabstract fun observeRowCount(mangaId: Long): Flow<Int>\n\n\t@Upsert\n\tabstract suspend fun upsert(entity: StatsEntity)\n\n\tsuspend fun getDurationStats(\n\t\tfromDate: Long,\n\t\tisNsfw: Boolean?,\n\t\tfavouriteCategories: Set<Long>\n\t): Map<MangaEntity, Long> {\n\t\tval conditions = ArrayList<String>()\n\t\tconditions.add(\"(SELECT deleted_at FROM history WHERE history.manga_id = stats.manga_id) = 0\")\n\t\tconditions.add(\"stats.started_at >= $fromDate\")\n\t\tif (favouriteCategories.isNotEmpty()) {\n\t\t\tval ids = favouriteCategories.joinToString(\",\")\n\t\t\tconditions.add(\"stats.manga_id IN (SELECT manga_id FROM favourites WHERE category_id IN ($ids))\")\n\t\t}\n\t\tif (isNsfw != null) {\n\t\t\tval flag = if (isNsfw) 1 else 0\n\t\t\tconditions.add(\"manga.nsfw = $flag\")\n\t\t}\n\t\tval where = conditions.joinToString(separator = \" AND \")\n\t\tval query = SimpleSQLiteQuery(\n\t\t\t\"SELECT manga.*, SUM(duration) AS d FROM stats LEFT JOIN manga ON manga.manga_id = stats.manga_id WHERE $where GROUP BY manga.manga_id ORDER BY d DESC\",\n\t\t)\n\t\treturn getDurationStatsImpl(query)\n\t}\n\n\t@RawQuery\n\tprotected abstract suspend fun getDurationStatsImpl(\n\t\tquery: SupportSQLiteQuery\n\t): Map<@MapColumn(\"manga\") MangaEntity, @MapColumn(\"d\") Long>\n\n\t@Query(\"SELECT * FROM stats ORDER BY started_at LIMIT :limit OFFSET :offset\")\n\tprotected abstract suspend fun findAll(offset: Int, limit: Int): List<StatsEntity>\n\tfun dumpEnabled(): Flow<StatsEntity> = flow {\n\t\tval window = 10\n\t\tvar offset = 0\n\t\twhile (currentCoroutineContext().isActive) {\n\t\t\tval list = findAll(offset, window)\n\t\t\tif (list.isEmpty()) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset += window\n\t\t\tlist.forEach { emit(it) }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsEntity.kt",
    "content": "package org.koitharu.kotatsu.stats.data\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport org.koitharu.kotatsu.history.data.HistoryEntity\n\n@Entity(\n\ttableName = \"stats\",\n\tprimaryKeys = [\"manga_id\", \"started_at\"],\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = HistoryEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t],\n)\ndata class StatsEntity(\n\t@ColumnInfo(name = \"manga_id\") val mangaId: Long,\n\t@ColumnInfo(name = \"started_at\") val startedAt: Long,\n\t@ColumnInfo(name = \"duration\") val duration: Long,\n\t@ColumnInfo(name = \"pages\") val pages: Int,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/data/StatsRepository.kt",
    "content": "package org.koitharu.kotatsu.stats.data\n\nimport androidx.room.withTransaction\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.map\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.stats.domain.StatsPeriod\nimport org.koitharu.kotatsu.stats.domain.StatsRecord\nimport java.util.NavigableMap\nimport java.util.TreeMap\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\n\nclass StatsRepository @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val db: MangaDatabase,\n) {\n\n\tsuspend fun getReadingStats(period: StatsPeriod, categories: Set<Long>): List<StatsRecord> {\n\t\tval fromDate = if (period == StatsPeriod.ALL) {\n\t\t\t0L\n\t\t} else {\n\t\t\tSystem.currentTimeMillis() - TimeUnit.DAYS.toMillis(period.days.toLong())\n\t\t}\n\t\tval stats = db.getStatsDao().getDurationStats(fromDate, null, categories)\n\t\tval result = ArrayList<StatsRecord>(stats.size)\n\t\tvar other = StatsRecord(null, 0)\n\t\tval total = stats.values.sum()\n\t\tfor ((mangaEntity, duration) in stats) {\n\t\t\tval manga = mangaEntity.toManga(emptySet(), null)\n\t\t\tval percent = duration.toDouble() / total\n\t\t\tif (percent < 0.05) {\n\t\t\t\tother = other.copy(duration = other.duration + duration)\n\t\t\t} else {\n\t\t\t\tresult += StatsRecord(\n\t\t\t\t\tmanga = manga,\n\t\t\t\t\tduration = duration,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (other.duration != 0L) {\n\t\t\tresult += other\n\t\t}\n\t\treturn result\n\t}\n\n\tsuspend fun getTimePerPage(mangaId: Long): Long = db.withTransaction {\n\t\tval dao = db.getStatsDao()\n\t\tval pages = dao.getReadPagesCount(mangaId)\n\t\tval time = if (pages >= 10) {\n\t\t\tdao.getAverageTimePerPage(mangaId)\n\t\t} else {\n\t\t\tdao.getAverageTimePerPage()\n\t\t}\n\t\ttime\n\t}\n\n\tsuspend fun getTotalPagesRead(mangaId: Long): Int {\n\t\treturn db.getStatsDao().getReadPagesCount(mangaId)\n\t}\n\n\tsuspend fun getMangaTimeline(mangaId: Long): NavigableMap<Long, Int> {\n\t\tval entities = db.getStatsDao().findAll(mangaId)\n\t\tval map = TreeMap<Long, Int>()\n\t\tfor (e in entities) {\n\t\t\tmap[e.startedAt] = e.pages\n\t\t}\n\t\treturn map\n\t}\n\n\tsuspend fun clearStats() {\n\t\tdb.getStatsDao().clear()\n\t}\n\n\tfun observeHasStats(mangaId: Long): Flow<Boolean> = settings.observeAsFlow(AppSettings.KEY_STATS_ENABLED) {\n\t\tisStatsEnabled\n\t}.flatMapLatest { isEnabled ->\n\t\tif (isEnabled) {\n\t\t\tdb.getStatsDao().observeRowCount(mangaId).map { it > 0 }\n\t\t} else {\n\t\t\tflowOf(false)\n\t\t}\n\t}.distinctUntilChanged()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/domain/StatsCollector.kt",
    "content": "package org.koitharu.kotatsu.stats.domain\n\nimport androidx.collection.LongSparseArray\nimport androidx.collection.set\nimport dagger.hilt.android.ViewModelLifecycle\nimport dagger.hilt.android.scopes.ViewModelScoped\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.reader.ui.ReaderState\nimport org.koitharu.kotatsu.stats.data.StatsEntity\nimport javax.inject.Inject\n\n@ViewModelScoped\nclass StatsCollector @Inject constructor(\n\tprivate val db: MangaDatabase,\n\tprivate val settings: AppSettings,\n\tlifecycle: ViewModelLifecycle,\n) {\n\n\tprivate val viewModelScope = RetainedLifecycleCoroutineScope(lifecycle)\n\tprivate val stats = LongSparseArray<Entry>(1)\n\n\t@Synchronized\n\tfun onStateChanged(mangaId: Long, state: ReaderState) {\n\t\tif (!settings.isStatsEnabled) {\n\t\t\treturn\n\t\t}\n\t\tval now = System.currentTimeMillis()\n\t\tval entry = stats[mangaId]\n\t\tif (entry == null) {\n\t\t\tstats[mangaId] = Entry(\n\t\t\t\tstate = state,\n\t\t\t\tstats = StatsEntity(\n\t\t\t\t\tmangaId = mangaId,\n\t\t\t\t\tstartedAt = now,\n\t\t\t\t\tduration = 0,\n\t\t\t\t\tpages = 0,\n\t\t\t\t),\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tval pagesDelta = if (entry.state.page != state.page || entry.state.chapterId != state.chapterId) 1 else 0\n\t\tval newEntry = entry.copy(\n\t\t\tstats = StatsEntity(\n\t\t\t\tmangaId = mangaId,\n\t\t\t\tstartedAt = entry.stats.startedAt,\n\t\t\t\tduration = now - entry.stats.startedAt,\n\t\t\t\tpages = entry.stats.pages + pagesDelta,\n\t\t\t),\n\t\t)\n\t\tstats[mangaId] = newEntry\n\t\tcommit(newEntry.stats)\n\t}\n\n\t@Synchronized\n\tfun onPause(mangaId: Long) {\n\t\tstats.remove(mangaId)\n\t}\n\n\tprivate fun commit(entity: StatsEntity) {\n\t\tviewModelScope.launch(Dispatchers.Default) {\n\t\t\trunCatchingCancellable {\n\t\t\t\tdb.getStatsDao().upsert(entity)\n\t\t\t}.onFailure { e ->\n\t\t\t\te.printStackTraceDebug()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate data class Entry(\n\t\tval state: ReaderState,\n\t\tval stats: StatsEntity,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/domain/StatsPeriod.kt",
    "content": "package org.koitharu.kotatsu.stats.domain\n\nimport androidx.annotation.StringRes\nimport org.koitharu.kotatsu.R\n\nenum class StatsPeriod(\n\t@StringRes val titleResId: Int,\n\tval days: Int,\n) {\n\n\tDAY(R.string.day, 1),\n\tWEEK(R.string.week, 7),\n\tMONTH(R.string.month, 30),\n\tMONTHS_3(R.string.three_months, 90),\n\tALL(R.string.all_time, Int.MAX_VALUE),\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/domain/StatsRecord.kt",
    "content": "package org.koitharu.kotatsu.stats.domain\n\nimport org.koitharu.kotatsu.details.data.ReadingTime\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.util.concurrent.TimeUnit\n\ndata class StatsRecord(\n\tval manga: Manga?,\n\tval duration: Long,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is StatsRecord && other.manga == manga\n\t}\n\n\tval time: ReadingTime\n\n\tinit {\n\t\tval minutes = TimeUnit.MILLISECONDS.toMinutes(duration).toInt()\n\t\ttime = ReadingTime(\n\t\t\tminutes = minutes % 60,\n\t\t\thours = minutes / 60,\n\t\t\tisContinue = false,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsAD.kt",
    "content": "package org.koitharu.kotatsu.stats.ui\n\nimport android.content.res.ColorStateList\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.KotatsuColors\nimport org.koitharu.kotatsu.databinding.ItemStatsBinding\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.stats.domain.StatsRecord\n\nfun statsAD(\n\tlistener: OnListItemClickListener<Manga>,\n) = adapterDelegateViewBinding<StatsRecord, StatsRecord, ItemStatsBinding>(\n\t{ layoutInflater, parent -> ItemStatsBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tbinding.root.setOnClickListener { v ->\n\t\tlistener.onItemClick(item.manga ?: return@setOnClickListener, v)\n\t}\n\n\tbind {\n\t\tbinding.textViewTitle.text = item.manga?.title ?: getString(R.string.other_manga)\n\t\tbinding.textViewSummary.text = item.time.format(context.resources)\n\t\tbinding.imageViewBadge.imageTintList = ColorStateList.valueOf(KotatsuColors.ofManga(context, item.manga))\n\t\tbinding.root.isClickable = item.manga != null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsActivity.kt",
    "content": "package org.koitharu.kotatsu.stats.ui\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewStub\nimport android.widget.CompoundButton\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.graphics.Insets\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePaddingRelative\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport com.google.android.material.chip.Chip\nimport com.google.android.material.chip.ChipDrawable\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.util.KotatsuColors\nimport org.koitharu.kotatsu.core.util.ext.end\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.setTextAndVisible\nimport org.koitharu.kotatsu.core.util.ext.showOrHide\nimport org.koitharu.kotatsu.core.util.ext.start\nimport org.koitharu.kotatsu.databinding.ActivityStatsBinding\nimport org.koitharu.kotatsu.databinding.ItemEmptyStateBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.stats.domain.StatsPeriod\nimport org.koitharu.kotatsu.stats.domain.StatsRecord\nimport org.koitharu.kotatsu.stats.ui.views.PieChartView\n\n@AndroidEntryPoint\nclass StatsActivity : BaseActivity<ActivityStatsBinding>(),\n\tOnListItemClickListener<Manga>,\n\tPieChartView.OnSegmentClickListener,\n\tAsyncListDiffer.ListListener<StatsRecord>,\n\tViewStub.OnInflateListener,\n\tView.OnClickListener,\n\tCompoundButton.OnCheckedChangeListener {\n\n\tprivate val viewModel: StatsViewModel by viewModels()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityStatsBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tval adapter = BaseListAdapter<StatsRecord>()\n\t\t\t.addDelegate(ListItemType.FEED, statsAD(this))\n\t\t\t.addListListener(this)\n\t\tviewBinding.recyclerView.adapter = adapter\n\t\tviewBinding.chart.onSegmentClickListener = this\n\t\tviewBinding.stubEmpty.setOnInflateListener(this)\n\t\tviewBinding.chipPeriod.setOnClickListener(this)\n\n\t\tviewModel.isLoading.observe(this) {\n\t\t\tviewBinding.progressBar.showOrHide(it)\n\t\t}\n\t\tviewModel.period.observe(this) {\n\t\t\tviewBinding.chipPeriod.setText(it.titleResId)\n\t\t}\n\t\tviewModel.favoriteCategories.observe(this, ::createCategoriesChips)\n\t\tviewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView))\n\t\tviewModel.readingStats.observe(this) {\n\t\t\tval sum = it.sumOf { it.duration }\n\t\t\tviewBinding.chart.setData(\n\t\t\t\tit.map { v ->\n\t\t\t\t\tPieChartView.Segment(\n\t\t\t\t\t\tvalue = (v.duration / 1000).toInt(),\n\t\t\t\t\t\tlabel = v.manga?.title ?: getString(R.string.other_manga),\n\t\t\t\t\t\tpercent = (v.duration.toDouble() / sum).toFloat(),\n\t\t\t\t\t\tcolor = KotatsuColors.ofManga(this, v.manga),\n\t\t\t\t\t\ttag = v.manga,\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\t\t\tadapter.emit(it)\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(\n\t\tv: View,\n\t\tinsets: WindowInsetsCompat\n\t): WindowInsetsCompat {\n\t\tval bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n\t\tval isTablet = viewBinding.guidelineCenter != null\n\t\tviewBinding.appbar.updatePaddingRelative(\n\t\t\tstart = bars.start(v),\n\t\t\ttop = bars.top,\n\t\t\tend = if (isTablet) 0 else bars.end(v),\n\t\t)\n\t\tval badgePadding = resources.getDimensionPixelOffset(R.dimen.list_spacing_large)\n\t\tviewBinding.scrollViewChips.updatePaddingRelative(\n\t\t\tstart = badgePadding + if (isTablet) 0 else bars.start(v),\n\t\t\tend = badgePadding + bars.end(v),\n\t\t\ttop = if (isTablet) bars.top else 0,\n\t\t)\n\t\tviewBinding.recyclerView.updatePaddingRelative(\n\t\t\tstart = if (isTablet) 0 else bars.start(v),\n\t\t\tend = bars.end(v),\n\t\t\tbottom = bars.bottom,\n\t\t)\n\t\tviewBinding.chart.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n\t\t\tval baseMargin = topMargin\n\t\t\tbottomMargin = if (isTablet) baseMargin + bars.bottom else baseMargin\n\t\t\tmarginStart = baseMargin + bars.start(v)\n\t\t\tmarginEnd = if (isTablet) baseMargin else baseMargin + bars.end(v)\n\t\t}\n\t\treturn WindowInsetsCompat.Builder(insets)\n\t\t\t.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)\n\t\t\t.build()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.chip_period -> showPeriodSelector()\n\t\t}\n\t}\n\n\toverride fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {\n\t\tval category = buttonView.tag as? FavouriteCategory ?: return\n\t\tviewModel.setCategoryChecked(category, isChecked)\n\t}\n\n\toverride fun onItemClick(item: Manga, view: View) {\n\t\trouter.showStatisticSheet(item)\n\t}\n\n\toverride fun onSegmentClick(view: PieChartView, segment: PieChartView.Segment) {\n\t\tval manga = segment.tag as? Manga ?: return\n\t\tonItemClick(manga, view)\n\t}\n\n\toverride fun onCreateOptionsMenu(menu: Menu?): Boolean {\n\t\tmenuInflater.inflate(R.menu.opt_stats, menu)\n\t\treturn super.onCreateOptionsMenu(menu)\n\t}\n\n\toverride fun onOptionsItemSelected(item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_clear -> {\n\t\t\t\tshowClearConfirmDialog()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onOptionsItemSelected(item)\n\t\t}\n\t}\n\n\toverride fun onCurrentListChanged(previousList: MutableList<StatsRecord>, currentList: MutableList<StatsRecord>) {\n\t\tval isEmpty = currentList.isEmpty()\n\t\twith(viewBinding) {\n\t\t\tchart.isGone = isEmpty\n\t\t\trecyclerView.isGone = isEmpty\n\t\t\tstubEmpty.isVisible = isEmpty\n\t\t}\n\t}\n\n\toverride fun onInflate(stub: ViewStub?, inflated: View) {\n\t\tval stubBinding = ItemEmptyStateBinding.bind(inflated)\n\t\tstubBinding.icon.setImageAsync(R.drawable.ic_empty_history)\n\t\tstubBinding.textPrimary.setText(R.string.text_empty_holder_primary)\n\t\tstubBinding.textSecondary.setTextAndVisible(R.string.empty_stats_text)\n\t\tstubBinding.buttonRetry.isVisible = false\n\t}\n\n\tprivate fun createCategoriesChips(categories: List<FavouriteCategory>) {\n\t\tval container = viewBinding.layoutChips\n\t\tif (container.childCount > 1) {\n\t\t\t// avoid duplication\n\t\t\treturn\n\t\t}\n\t\tval checkedIds = viewModel.selectedCategories.value\n\t\tfor (category in categories) {\n\t\t\tval chip = Chip(this)\n\t\t\tval drawable = ChipDrawable.createFromAttributes(this, null, 0, R.style.Widget_Kotatsu_Chip_Filter)\n\t\t\tchip.setChipDrawable(drawable)\n\t\t\tchip.text = category.title\n\t\t\tchip.tag = category\n\t\t\tchip.isChecked = category.id in checkedIds\n\t\t\tchip.setOnCheckedChangeListener(this)\n\t\t\tcontainer.addView(chip)\n\t\t}\n\t}\n\n\tprivate fun showClearConfirmDialog() {\n\t\tbuildAlertDialog(this, isCentered = true) {\n\t\t\tsetMessage(R.string.clear_stats_confirm)\n\t\t\tsetTitle(R.string.clear_stats)\n\t\t\tsetIcon(R.drawable.ic_delete_all)\n\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\tsetPositiveButton(R.string.clear) { _, _ -> viewModel.clearStats() }\n\t\t}.show()\n\t}\n\n\tprivate fun showPeriodSelector() {\n\t\tval menu = PopupMenu(this, viewBinding.chipPeriod)\n\t\tval selected = viewModel.period.value\n\t\tfor ((i, branch) in StatsPeriod.entries.withIndex()) {\n\t\t\tval item = menu.menu.add(R.id.group_period, Menu.NONE, i, branch.titleResId)\n\t\t\titem.isCheckable = true\n\t\t\titem.isChecked = selected.ordinal == i\n\t\t}\n\t\tmenu.menu.setGroupCheckable(R.id.group_period, true, true)\n\n\t\tmenu.setOnMenuItemClickListener {\n\t\t\tStatsPeriod.entries.getOrNull(it.order)?.also {\n\t\t\t\tviewModel.period.value = it\n\t\t\t} != null\n\t\t}\n\t\tmenu.show()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsViewModel.kt",
    "content": "package org.koitharu.kotatsu.stats.ui\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.take\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.model.FavouriteCategory\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.stats.data.StatsRepository\nimport org.koitharu.kotatsu.stats.domain.StatsPeriod\nimport org.koitharu.kotatsu.stats.domain.StatsRecord\nimport javax.inject.Inject\n\n@HiltViewModel\nclass StatsViewModel @Inject constructor(\n\tprivate val repository: StatsRepository,\n\tfavouritesRepository: FavouritesRepository,\n) : BaseViewModel() {\n\n\tval period = MutableStateFlow(StatsPeriod.WEEK)\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\tval selectedCategories = MutableStateFlow<Set<Long>>(emptySet())\n\tval favoriteCategories = favouritesRepository.observeCategories()\n\t\t.take(1)\n\n\tval readingStats = MutableStateFlow<List<StatsRecord>>(emptyList())\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tcombine<StatsPeriod, Set<Long>, Pair<StatsPeriod, Set<Long>>>(\n\t\t\t\tperiod,\n\t\t\t\tselectedCategories,\n\t\t\t\t::Pair,\n\t\t\t).collectLatest { p ->\n\t\t\t\treadingStats.value = withLoading {\n\t\t\t\t\trepository.getReadingStats(p.first, p.second)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfun setCategoryChecked(category: FavouriteCategory, checked: Boolean) {\n\t\tval snapshot = selectedCategories.value.toMutableSet()\n\t\tif (checked) {\n\t\t\tsnapshot.add(category.id)\n\t\t} else {\n\t\t\tsnapshot.remove(category.id)\n\t\t}\n\t\tselectedCategories.value = snapshot\n\t}\n\n\tfun clearStats() {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\trepository.clearStats()\n\t\t\treadingStats.value = emptyList()\n\t\t\tonActionDone.call(ReversibleAction(R.string.stats_cleared, null))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/sheet/MangaStatsSheet.kt",
    "content": "package org.koitharu.kotatsu.stats.ui.sheet\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.collection.IntList\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet\nimport org.koitharu.kotatsu.core.util.KotatsuColors\nimport org.koitharu.kotatsu.core.util.ext.consume\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.textAndVisible\nimport org.koitharu.kotatsu.databinding.SheetStatsMangaBinding\nimport org.koitharu.kotatsu.parsers.util.format\nimport org.koitharu.kotatsu.stats.ui.views.BarChartView\n\n@AndroidEntryPoint\nclass MangaStatsSheet : BaseAdaptiveSheet<SheetStatsMangaBinding>(), View.OnClickListener {\n\n\tprivate val viewModel: MangaStatsViewModel by viewModels()\n\n\toverride fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetStatsMangaBinding {\n\t\treturn SheetStatsMangaBinding.inflate(inflater, container, false)\n\t}\n\n\toverride fun onViewBindingCreated(binding: SheetStatsMangaBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.textViewTitle.text = viewModel.manga.title\n\t\tbinding.chartView.barColor = KotatsuColors.ofManga(binding.root.context, viewModel.manga)\n\t\tviewModel.stats.observe(viewLifecycleOwner, ::onStatsChanged)\n\t\tviewModel.startDate.observe(viewLifecycleOwner) {\n\t\t\tbinding.textViewStart.textAndVisible = it?.format(binding.root.context)\n\t\t}\n\t\tviewModel.totalPagesRead.observe(viewLifecycleOwner) {\n\t\t\tbinding.textViewPages.text = getString(R.string.pages_read_s, it.format())\n\t\t}\n\t\tbinding.buttonOpen.setOnClickListener(this)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tviewBinding?.scrollView?.updatePadding(\n\t\t\tbottom = insets.getInsets(typeMask).bottom,\n\t\t)\n\t\treturn insets.consume(v, typeMask, bottom = true)\n\t}\n\n\toverride fun onClick(v: View) {\n\t\trouter.openDetails(viewModel.manga)\n\t}\n\n\tprivate fun onStatsChanged(stats: IntList) {\n\t\tval chartView = viewBinding?.chartView ?: return\n\t\tif (stats.isEmpty()) {\n\t\t\tchartView.setData(emptyList())\n\t\t\treturn\n\t\t}\n\t\tval bars = ArrayList<BarChartView.Bar>(stats.size)\n\t\tstats.forEach { pages ->\n\t\t\tbars.add(\n\t\t\t\tBarChartView.Bar(\n\t\t\t\t\tvalue = pages,\n\t\t\t\t\tlabel = pages.toString(),\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tchartView.setData(bars)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/sheet/MangaStatsViewModel.kt",
    "content": "package org.koitharu.kotatsu.stats.ui.sheet\n\nimport androidx.collection.MutableIntList\nimport androidx.collection.emptyIntList\nimport androidx.lifecycle.SavedStateHandle\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.core.model.parcelable.ParcelableManga\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.model.DateTimeAgo\nimport org.koitharu.kotatsu.core.util.ext.calculateTimeAgo\nimport org.koitharu.kotatsu.core.util.ext.require\nimport org.koitharu.kotatsu.stats.data.StatsRepository\nimport java.time.Instant\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MangaStatsViewModel @Inject constructor(\n\tsavedStateHandle: SavedStateHandle,\n\tprivate val repository: StatsRepository,\n) : BaseViewModel() {\n\n\tval manga = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga\n\n\tval stats = MutableStateFlow(emptyIntList())\n\tval startDate = MutableStateFlow<DateTimeAgo?>(null)\n\tval totalPagesRead = MutableStateFlow(0)\n\n\tinit {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval timeline = repository.getMangaTimeline(manga.id)\n\t\t\tif (timeline.isEmpty()) {\n\t\t\t\tstartDate.value = null\n\t\t\t\tstats.value = emptyIntList()\n\t\t\t} else {\n\t\t\t\tval startDay = TimeUnit.MILLISECONDS.toDays(timeline.firstKey())\n\t\t\t\tval endDay = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis())\n\t\t\t\tval res = MutableIntList((endDay - startDay).toInt() + 1)\n\t\t\t\tfor (day in startDay..endDay) {\n\t\t\t\t\tval from = TimeUnit.DAYS.toMillis(day)\n\t\t\t\t\tval to = TimeUnit.DAYS.toMillis(day + 1)\n\t\t\t\t\tres.add(timeline.subMap(from, true, to, false).values.sum())\n\t\t\t\t}\n\t\t\t\tstats.value = res\n\t\t\t\tstartDate.value = calculateTimeAgo(Instant.ofEpochMilli(timeline.firstKey()))\n\t\t\t}\n\t\t}\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\ttotalPagesRead.value = repository.getTotalPagesRead(manga.id)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/BarChartView.kt",
    "content": "package org.koitharu.kotatsu.stats.ui.views\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.DashPathEffect\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.annotation.ColorInt\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\nimport org.koitharu.kotatsu.parsers.util.replaceWith\nimport org.koitharu.kotatsu.parsers.util.toIntUp\nimport kotlin.math.roundToInt\nimport kotlin.random.Random\nimport androidx.appcompat.R as appcompatR\nimport com.google.android.material.R as materialR\n\nclass BarChartView @JvmOverloads constructor(\n\tcontext: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0\n) : View(context, attrs, defStyleAttr) {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate val rawData = ArrayList<Bar>()\n\tprivate val bars = ArrayList<Bar>()\n\tprivate var maxValue: Int = 0\n\tprivate val minBarSpacing = context.resources.resolveDp(12f)\n\tprivate val minSpace = context.resources.resolveDp(20f)\n\tprivate val barWidth = context.resources.resolveDp(12f)\n\tprivate val outlineColor = context.getThemeColor(materialR.attr.colorOutline)\n\tprivate val dottedEffect = DashPathEffect(\n\t\tfloatArrayOf(\n\t\t\tcontext.resources.resolveDp(6f),\n\t\t\tcontext.resources.resolveDp(6f),\n\t\t),\n\t\t0f,\n\t)\n\tprivate val chartBounds = RectF()\n\n\t@ColorInt\n\tvar barColor: Int = context.getThemeColor(appcompatR.attr.colorAccent)\n\t\tset(value) {\n\t\t\tfield = value\n\t\t\tinvalidate()\n\t\t}\n\n\tinit {\n\t\tpaint.strokeWidth = context.resources.resolveDp(1f)\n\t\tif (isInEditMode) {\n\t\t\tsetData(\n\t\t\t\tList(Random.nextInt(20, 60)) {\n\t\t\t\t\tBar(\n\t\t\t\t\t\tvalue = Random.nextInt(-20, 400).coerceAtLeast(0),\n\t\t\t\t\t\tlabel = it.toString(),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\toverride fun onDraw(canvas: Canvas) {\n\t\tsuper.onDraw(canvas)\n\t\tif (bars.isEmpty() || chartBounds.isEmpty) {\n\t\t\treturn\n\t\t}\n\t\tval spacing = (chartBounds.width() - (barWidth * bars.size.toFloat())) / (bars.size + 1).toFloat()\n\t\t// dashed horizontal lines\n\t\tpaint.color = outlineColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawLine(chartBounds.left, chartBounds.bottom, chartBounds.right, chartBounds.bottom, paint)\n\t\tpaint.pathEffect = dottedEffect\n\t\tfor (i in (0..maxValue).step(computeValueStep())) {\n\t\t\tval y = chartBounds.top + (chartBounds.height() * i / maxValue.toFloat())\n\t\t\tcanvas.drawLine(paddingLeft.toFloat(), y, (width - paddingLeft - paddingRight).toFloat(), y, paint)\n\t\t}\n\t\t// bottom line\n\t\tpaint.color = outlineColor\n\t\tpaint.style = Paint.Style.STROKE\n\t\tcanvas.drawLine(chartBounds.left, chartBounds.bottom, chartBounds.right, chartBounds.bottom, paint)\n\t\t// bars\n\t\tpaint.style = Paint.Style.FILL\n\t\tpaint.color = barColor\n\t\tpaint.pathEffect = null\n\t\tval corner = barWidth / 2f\n\t\tfor ((i, bar) in bars.withIndex()) {\n\t\t\tif (bar.value == 0) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tval h = (chartBounds.height() * bar.value / maxValue.toFloat()).coerceAtLeast(barWidth)\n\t\t\tval x = spacing + i * (barWidth + spacing) + paddingLeft\n\t\t\tcanvas.drawRoundRect(x, chartBounds.bottom - h, x + barWidth, chartBounds.bottom, corner, corner, paint)\n\t\t}\n\t}\n\n\toverride fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n\t\tsuper.onLayout(changed, left, top, right, bottom)\n\t\tinvalidateBounds()\n\t}\n\n\tfun setData(value: List<Bar>) {\n\t\trawData.replaceWith(value)\n\t\tcompressBars()\n\t\tinvalidate()\n\t}\n\n\tprivate fun compressBars() {\n\t\tif (rawData.isEmpty() || width <= 0) {\n\t\t\tmaxValue = 0\n\t\t\tbars.clear()\n\t\t\treturn\n\t\t}\n\t\tval fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing\n\t\tval windowSize = (fullWidth / width.toFloat()).toIntUp()\n\t\tbars.replaceWith(\n\t\t\trawData.chunked(windowSize) { it.average() },\n\t\t)\n\t\tmaxValue = bars.maxOf { it.value }\n\t}\n\n\tprivate fun computeValueStep(): Int {\n\t\tval h = chartBounds.height()\n\t\tvar step = 1\n\t\twhile (h / (maxValue / step).toFloat() <= minSpace) {\n\t\t\tstep++\n\t\t}\n\t\treturn step\n\t}\n\n\tprivate fun invalidateBounds() {\n\t\tval inset = paint.strokeWidth\n\t\tchartBounds.set(\n\t\t\tpaddingLeft.toFloat() + inset,\n\t\t\tpaddingTop.toFloat() + inset,\n\t\t\t(width - paddingLeft - paddingRight).toFloat() - inset,\n\t\t\t(height - paddingTop - paddingBottom).toFloat() - inset,\n\t\t)\n\t\tcompressBars()\n\t}\n\n\tprivate fun Collection<Bar>.average(): Bar {\n\t\treturn when (size) {\n\t\t\t0 -> Bar(0, \"\")\n\t\t\t1 -> first()\n\t\t\telse -> Bar(\n\t\t\t\tvalue = (sumOf { it.value } / size.toFloat()).roundToInt(),\n\t\t\t\tlabel = \"%s - %s\".format(first().label, last().label),\n\t\t\t)\n\t\t}\n\t}\n\n\tclass Bar(\n\t\tval value: Int,\n\t\tval label: String,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/PieChartView.kt",
    "content": "package org.koitharu.kotatsu.stats.ui.views\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimport android.view.View\nimport androidx.appcompat.widget.TooltipCompat\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.PointerIconCompat\nimport androidx.core.view.ViewCompat\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.core.util.ext.resolveDp\nimport org.koitharu.kotatsu.parsers.util.replaceWith\nimport kotlin.math.atan2\nimport kotlin.math.sqrt\n\nclass PieChartView @JvmOverloads constructor(\n\tcontext: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0\n) : View(context, attrs, defStyleAttr), GestureDetector.OnGestureListener {\n\n\tprivate val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n\tprivate val activePointerIcon = PointerIconCompat.getSystemIcon(context, PointerIconCompat.TYPE_HAND)\n\tprivate val segments = ArrayList<Segment>()\n\tprivate val chartBounds = RectF()\n\tprivate val clearColor = context.getThemeColor(android.R.attr.colorBackground)\n\tprivate val touchDetector = GestureDetector(context, this)\n\tprivate var hoverSegment = -1\n\tprivate var highlightedSegment = -1\n\n\tvar onSegmentClickListener: OnSegmentClickListener? = null\n\n\tinit {\n\t\ttouchDetector.setIsLongpressEnabled(false)\n\t\tpaint.strokeWidth = context.resources.resolveDp(2f)\n\t}\n\n\toverride fun onDraw(canvas: Canvas) {\n\t\tsuper.onDraw(canvas)\n\t\tvar angle = 0f\n\t\tfor ((i, segment) in segments.withIndex()) {\n\t\t\tpaint.color = segment.color\n\t\t\tif (i == highlightedSegment) {\n\t\t\t\tpaint.color = ColorUtils.setAlphaComponent(paint.color, 180)\n\t\t\t} else if (i == hoverSegment) {\n\t\t\t\tpaint.color = ColorUtils.setAlphaComponent(paint.color, 200)\n\t\t\t}\n\t\t\tpaint.style = Paint.Style.FILL\n\t\t\tval sweepAngle = segment.percent * 360f\n\t\t\tcanvas.drawArc(\n\t\t\t\tchartBounds,\n\t\t\t\tangle,\n\t\t\t\tsweepAngle,\n\t\t\t\ttrue,\n\t\t\t\tpaint,\n\t\t\t)\n\t\t\tpaint.color = clearColor\n\t\t\tpaint.style = Paint.Style.STROKE\n\t\t\tcanvas.drawArc(\n\t\t\t\tchartBounds,\n\t\t\t\tangle,\n\t\t\t\tsweepAngle,\n\t\t\t\ttrue,\n\t\t\t\tpaint,\n\t\t\t)\n\t\t\tangle += sweepAngle\n\t\t}\n\t\tpaint.style = Paint.Style.FILL\n\t\tpaint.color = clearColor\n\t\tcanvas.drawCircle(chartBounds.centerX(), chartBounds.centerY(), chartBounds.height() / 4f, paint)\n\t}\n\n\toverride fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n\t\tsuper.onSizeChanged(w, h, oldw, oldh)\n\t\tval size = minOf(w, h).toFloat()\n\t\tval inset = paint.strokeWidth\n\t\tchartBounds.set(inset, inset, size - inset, size - inset)\n\t\tchartBounds.offset(\n\t\t\t(w - size) / 2f,\n\t\t\t(h - size) / 2f,\n\t\t)\n\t}\n\n\t@SuppressLint(\"ClickableViewAccessibility\")\n\toverride fun onTouchEvent(event: MotionEvent): Boolean {\n\t\tif (event.actionMasked == MotionEvent.ACTION_CANCEL || event.actionMasked == MotionEvent.ACTION_UP) {\n\t\t\thighlightedSegment = -1\n\t\t\tinvalidate()\n\t\t}\n\t\treturn super.onTouchEvent(event) || touchDetector.onTouchEvent(event)\n\t}\n\n\toverride fun onDown(e: MotionEvent): Boolean {\n\t\tif (onSegmentClickListener == null) {\n\t\t\treturn false\n\t\t}\n\t\tval segment = findSegmentIndex(e.x, e.y)\n\t\tif (segment != highlightedSegment) {\n\t\t\thighlightedSegment = segment\n\t\t\tinvalidate()\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\n\toverride fun onShowPress(e: MotionEvent) = Unit\n\n\toverride fun onSingleTapUp(e: MotionEvent): Boolean {\n\t\tonSegmentClickListener?.run {\n\t\t\tval segment = segments.getOrNull(findSegmentIndex(e.x, e.y))\n\t\t\tif (segment != null) {\n\t\t\t\tonSegmentClick(this@PieChartView, segment)\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\toverride fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean = false\n\n\toverride fun onLongPress(e: MotionEvent) = Unit\n\n\toverride fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean = false\n\n\toverride fun onHoverEvent(event: MotionEvent): Boolean {\n\t\tval segment = when (event.actionMasked) {\n\t\t\tMotionEvent.ACTION_HOVER_ENTER,\n\t\t\tMotionEvent.ACTION_HOVER_MOVE -> findSegmentIndex(event.x, event.y)\n\n\t\t\tMotionEvent.ACTION_HOVER_EXIT -> -1\n\t\t\telse -> hoverSegment\n\t\t}\n\t\tif (hoverSegment != segment) {\n\t\t\thoverSegment = segment\n\t\t\tTooltipCompat.setTooltipText(this, segments.getOrNull(segment)?.label)\n\t\t\tViewCompat.setPointerIcon(this, if (segment == -1) null else activePointerIcon)\n\t\t\tinvalidate()\n\t\t}\n\t\treturn super.onHoverEvent(event) || segment != -1\n\t}\n\n\tfun setData(value: List<Segment>) {\n\t\tsegments.replaceWith(value)\n\t\tinvalidate()\n\t}\n\n\tprivate fun findSegmentIndex(x: Float, y: Float): Int {\n\t\tval dy = (y - chartBounds.centerY()).toDouble()\n\t\tval dx = (x - chartBounds.centerX()).toDouble()\n\t\tval distance = sqrt(dx * dx + dy * dy).toFloat()\n\t\tif (distance < chartBounds.height() / 4f || distance > chartBounds.centerX()) {\n\t\t\treturn -1\n\t\t}\n\t\tvar touchAngle = Math.toDegrees(atan2(dy, dx)).toFloat()\n\t\tif (touchAngle < 0) {\n\t\t\ttouchAngle += 360\n\t\t}\n\t\tvar angle = 0f\n\t\tfor ((i, segment) in segments.withIndex()) {\n\t\t\tval sweepAngle = segment.percent * 360f\n\t\t\tif (touchAngle in angle..(angle + sweepAngle)) {\n\t\t\t\treturn i\n\t\t\t}\n\t\t\tangle += sweepAngle\n\t\t}\n\t\treturn -1\n\t}\n\n\tclass Segment(\n\t\tval value: Int,\n\t\tval label: String,\n\t\tval percent: Float,\n\t\tval color: Int,\n\t\tval tag: Any?,\n\t)\n\n\tinterface OnSegmentClickListener {\n\n\t\tfun onSegmentClick(view: PieChartView, segment: Segment)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/data/SuggestionDao.kt",
    "content": "package org.koitharu.kotatsu.suggestions.data\n\nimport android.database.DatabaseUtils.sqlEscapeString\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.RawQuery\nimport androidx.room.Transaction\nimport androidx.room.Update\nimport androidx.sqlite.db.SupportSQLiteQuery\nimport kotlinx.coroutines.flow.Flow\nimport org.koitharu.kotatsu.core.db.MangaQueryBuilder\nimport org.koitharu.kotatsu.core.db.entity.MangaWithTags\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\n\n@Dao\nabstract class SuggestionDao : MangaQueryBuilder.ConditionCallback {\n\n\t@Transaction\n\t@Query(\"SELECT * FROM suggestions ORDER BY relevance DESC\")\n\tabstract fun observeAll(): Flow<List<SuggestionWithManga>>\n\n\tfun observeAll(\n\t\tlimit: Int,\n\t\tfilterOptions: Collection<ListFilterOption>\n\t): Flow<List<SuggestionWithManga>> = observeAllImpl(\n\t\tMangaQueryBuilder(\"suggestions\", this)\n\t\t\t.filters(filterOptions)\n\t\t\t.orderBy(\"relevance DESC\")\n\t\t\t.limit(limit)\n\t\t\t.build(),\n\t)\n\n\t@Transaction\n\t@Query(\"SELECT manga.* FROM suggestions LEFT JOIN manga ON manga.manga_id = suggestions.manga_id ORDER BY relevance DESC LIMIT :limit\")\n\tabstract suspend fun getTopManga(limit: Int): List<MangaWithTags>\n\n\t@Transaction\n\topen suspend fun getRandom(limit: Int): List<MangaWithTags> {\n\t\tval ids = getRandomIds(limit)\n\t\treturn getByIds(ids)\n\t}\n\n\t@Query(\"SELECT COUNT(*) FROM suggestions\")\n\tabstract suspend fun count(): Int\n\n\t@Query(\"SELECT manga.title FROM suggestions LEFT JOIN manga ON suggestions.manga_id = manga.manga_id WHERE manga.title LIKE :query\")\n\tabstract suspend fun getTitles(query: String): List<String>\n\n\t@Query(\"SELECT tags.* FROM suggestions LEFT JOIN tags ON (tag_id IN (SELECT tag_id FROM manga_tags WHERE manga_tags.manga_id = suggestions.manga_id)) GROUP BY tag_id ORDER BY COUNT(tags.tag_id) DESC LIMIT :limit\")\n\tabstract suspend fun getTopTags(limit: Int): List<TagEntity>\n\n\t@Query(\"SELECT manga.source AS count FROM suggestions LEFT JOIN manga ON manga.manga_id = suggestions.manga_id GROUP BY manga.source ORDER BY COUNT(manga.source) DESC LIMIT :limit\")\n\tabstract suspend fun getTopSources(limit: Int): List<String>\n\n\t@Insert(onConflict = OnConflictStrategy.IGNORE)\n\tabstract suspend fun insert(entity: SuggestionEntity): Long\n\n\t@Update\n\tabstract suspend fun update(entity: SuggestionEntity): Int\n\n\t@Query(\"DELETE FROM suggestions\")\n\tabstract suspend fun deleteAll()\n\n\t@Transaction\n\topen suspend fun upsert(entity: SuggestionEntity) {\n\t\tif (update(entity) == 0) {\n\t\t\tinsert(entity)\n\t\t}\n\t}\n\n\t@Query(\"SELECT * FROM manga WHERE manga_id IN (:ids)\")\n\tprotected abstract suspend fun getByIds(ids: LongArray): List<MangaWithTags>\n\n\t@Query(\"SELECT manga_id FROM suggestions ORDER BY RANDOM() LIMIT :limit\")\n\tprotected abstract suspend fun getRandomIds(limit: Int): LongArray\n\n\t@Transaction\n\t@RawQuery(observedEntities = [SuggestionEntity::class])\n\tprotected abstract fun observeAllImpl(query: SupportSQLiteQuery): Flow<List<SuggestionWithManga>>\n\n\toverride fun getCondition(option: ListFilterOption): String? = when (option) {\n\t\tListFilterOption.Macro.NSFW -> \"(SELECT nsfw FROM manga WHERE manga.manga_id = suggestions.manga_id) = 1\"\n\t\tis ListFilterOption.Tag -> \"EXISTS(SELECT * FROM manga_tags WHERE manga_tags.manga_id = suggestions.manga_id AND tag_id = ${option.tagId})\"\n\t\tis ListFilterOption.Source -> \"(SELECT source FROM manga WHERE manga.manga_id = suggestions.manga_id) = ${\n\t\t\tsqlEscapeString(\n\t\t\t\toption.mangaSource.name,\n\t\t\t)\n\t\t}\"\n\n\t\telse -> null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/data/SuggestionEntity.kt",
    "content": "package org.koitharu.kotatsu.suggestions.data\n\nimport androidx.annotation.FloatRange\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\n@Entity(\n\ttableName = \"suggestions\",\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE\n\t\t)\n\t]\n)\nclass SuggestionEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"manga_id\", index = true) val mangaId: Long,\n\t@FloatRange(from = 0.0, to = 1.0)\n\t@ColumnInfo(name = \"relevance\") val relevance: Float,\n\t@ColumnInfo(name = \"created_at\") val createdAt: Long = System.currentTimeMillis(),\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/data/SuggestionWithManga.kt",
    "content": "package org.koitharu.kotatsu.suggestions.data\n\nimport androidx.room.Embedded\nimport androidx.room.Junction\nimport androidx.room.Relation\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaTagsEntity\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\ndata class SuggestionWithManga(\n\t@Embedded val suggestion: SuggestionEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"manga_id\"\n\t)\n\tval manga: MangaEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"tag_id\",\n\t\tassociateBy = Junction(MangaTagsEntity::class)\n\t)\n\tval tags: List<TagEntity>,\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/MangaSuggestion.kt",
    "content": "package org.koitharu.kotatsu.suggestions.domain\n\nimport androidx.annotation.FloatRange\nimport org.koitharu.kotatsu.parsers.model.Manga\n\ndata class MangaSuggestion(\n\tval manga: Manga,\n\t@FloatRange(from = 0.0, to = 1.0)\n\tval relevance: Float,\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt",
    "content": "package org.koitharu.kotatsu.suggestions.domain\n\nimport androidx.room.withTransaction\nimport kotlinx.coroutines.flow.Flow\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toEntities\nimport org.koitharu.kotatsu.core.db.entity.toEntity\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaTagsList\nimport org.koitharu.kotatsu.core.model.toMangaSources\nimport org.koitharu.kotatsu.core.util.ext.mapItems\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.suggestions.data.SuggestionEntity\nimport org.koitharu.kotatsu.suggestions.data.SuggestionWithManga\nimport javax.inject.Inject\n\nclass SuggestionRepository @Inject constructor(\n\tprivate val db: MangaDatabase,\n) {\n\n\tfun observeAll(): Flow<List<Manga>> {\n\t\treturn db.getSuggestionDao().observeAll().mapItems {\n\t\t\tit.toManga()\n\t\t}\n\t}\n\n\tfun observeAll(limit: Int, filterOptions: Set<ListFilterOption>): Flow<List<Manga>> {\n\t\treturn db.getSuggestionDao().observeAll(limit, filterOptions).mapItems {\n\t\t\tit.toManga()\n\t\t}\n\t}\n\n\tsuspend fun getRandomList(limit: Int): List<Manga> {\n\t\treturn db.getSuggestionDao().getRandom(limit).map {\n\t\t\tit.toManga()\n\t\t}\n\t}\n\n\tsuspend fun clear() {\n\t\tdb.getSuggestionDao().deleteAll()\n\t}\n\n\tsuspend fun isEmpty(): Boolean {\n\t\treturn db.getSuggestionDao().count() == 0\n\t}\n\n\tsuspend fun getTopTags(limit: Int): List<MangaTag> {\n\t\treturn db.getSuggestionDao().getTopTags(limit)\n\t\t\t.toMangaTagsList()\n\t}\n\n\tsuspend fun getTopSources(limit: Int): List<MangaSource> {\n\t\treturn db.getSuggestionDao().getTopSources(limit)\n\t\t\t.toMangaSources()\n\t}\n\n\tsuspend fun replace(suggestions: Iterable<MangaSuggestion>) {\n\t\tdb.withTransaction {\n\t\t\tdb.getSuggestionDao().deleteAll()\n\t\t\tsuggestions.forEach { (manga, relevance) ->\n\t\t\t\tval tags = manga.tags.toEntities()\n\t\t\t\tdb.getTagsDao().upsert(tags)\n\t\t\t\tdb.getMangaDao().upsert(manga.toEntity(), tags)\n\t\t\t\tdb.getSuggestionDao().upsert(\n\t\t\t\t\tSuggestionEntity(\n\t\t\t\t\t\tmangaId = manga.id,\n\t\t\t\t\t\trelevance = relevance,\n\t\t\t\t\t\tcreatedAt = System.currentTimeMillis(),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun SuggestionWithManga.toManga() = manga.toManga(emptySet(), null)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionsListQuickFilter.kt",
    "content": "package org.koitharu.kotatsu.suggestions.domain\n\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.MangaListQuickFilter\nimport javax.inject.Inject\n\nclass SuggestionsListQuickFilter @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val suggestionRepository: SuggestionRepository,\n) : MangaListQuickFilter(settings) {\n\n\toverride suspend fun getAvailableFilterOptions(): List<ListFilterOption> = buildList(6) {\n\t\tsuggestionRepository.getTopTags(5).mapTo(this) {\n\t\t\tListFilterOption.Tag(it)\n\t\t}\n\t\tif (!settings.isNsfwContentDisabled && !settings.isSuggestionsExcludeNsfw) {\n\t\t\tadd(ListFilterOption.Macro.NSFW)\n\t\t\tadd(ListFilterOption.SFW)\n\t\t}\n\t\tsuggestionRepository.getTopSources(3).mapTo(this) {\n\t\t\tListFilterOption.Source(it)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/TagsBlacklist.kt",
    "content": "package org.koitharu.kotatsu.suggestions.domain\n\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.util.almostEquals\n\nclass TagsBlacklist(\n\tprivate val tags: Set<String>,\n\tprivate val threshold: Float,\n) {\n\n\tfun isNotEmpty() = tags.isNotEmpty()\n\n\toperator fun contains(manga: Manga): Boolean {\n\t\tif (tags.isEmpty()) {\n\t\t\treturn false\n\t\t}\n\t\tfor (mangaTag in manga.tags) {\n\t\t\tfor (tagTitle in tags) {\n\t\t\t\tif (mangaTag.title.almostEquals(tagTitle, threshold)) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\toperator fun contains(tag: MangaTag): Boolean = tags.any {\n\t\tit.almostEquals(tag.title, threshold)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsActivity.kt",
    "content": "package org.koitharu.kotatsu.suggestions.ui\n\nimport org.koitharu.kotatsu.core.ui.FragmentContainerActivity\n\nclass SuggestionsActivity : FragmentContainerActivity(SuggestionsFragment::class.java)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt",
    "content": "package org.koitharu.kotatsu.suggestions.ui\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.snackbar.Snackbar\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.databinding.FragmentListBinding\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\n\nclass SuggestionsFragment : MangaListFragment() {\n\n\toverride val viewModel by viewModels<SuggestionsViewModel>()\n\toverride val isSwipeRefreshEnabled = false\n\n\toverride fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\taddMenuProvider(SuggestionMenuProvider())\n\t}\n\n\toverride fun onScrolledToEnd() = Unit\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu,\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_remote, menu)\n\t\treturn super.onCreateActionMode(controller, menuInflater, menu)\n\t}\n\n\tprivate inner class SuggestionMenuProvider : MenuProvider {\n\n\t\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\t\tmenuInflater.inflate(R.menu.opt_suggestions, menu)\n\t\t}\n\n\t\toverride fun onPrepareMenu(menu: Menu) {\n\t\t\tsuper.onPrepareMenu(menu)\n\t\t\tmenu.findItem(R.id.action_settings_suggestions)?.isVisible =\n\t\t\t\tmenu.findItem(R.id.action_settings) == null\n\t\t}\n\n\t\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n\t\t\tR.id.action_update -> {\n\t\t\t\tviewModel.updateSuggestions()\n\t\t\t\tSnackbar.make(\n\t\t\t\t\trequireViewBinding().recyclerView,\n\t\t\t\t\tR.string.suggestions_updating,\n\t\t\t\t\tSnackbar.LENGTH_LONG,\n\t\t\t\t).show()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\tR.id.action_settings_suggestions -> {\n\t\t\t\trouter.openSuggestionsSettings()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> false\n\t\t}\n\t}\n\n\tcompanion object {\n\n\t\t@Deprecated(\n\t\t\t\"\",\n\t\t\tReplaceWith(\n\t\t\t\t\"SuggestionsFragment()\",\n\t\t\t\t\"org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment\",\n\t\t\t),\n\t\t)\n\t\tfun newInstance() = SuggestionsFragment()\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsViewModel.kt",
    "content": "package org.koitharu.kotatsu.suggestions.ui\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.util.ext.onFirst\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.domain.QuickFilterListener\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.suggestions.domain.SuggestionRepository\nimport org.koitharu.kotatsu.suggestions.domain.SuggestionsListQuickFilter\nimport javax.inject.Inject\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport kotlinx.coroutines.flow.SharedFlow\n\n@HiltViewModel\nclass SuggestionsViewModel @Inject constructor(\n\trepository: SuggestionRepository,\n\tsettings: AppSettings,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val quickFilter: SuggestionsListQuickFilter,\n\tprivate val suggestionsScheduler: SuggestionsWorker.Scheduler,\n\tmangaDataRepository: MangaDataRepository,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {\n\n\toverride val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_SUGGESTIONS) { suggestionsListMode }\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.suggestionsListMode)\n\n\toverride val content = combine(\n\t\tquickFilter.appliedOptions.combineWithSettings().flatMapLatest { repository.observeAll(0, it) },\n\t\tquickFilter.appliedOptions,\n\t\tobserveListModeWithTriggers(),\n\t) { list, filters, mode ->\n\t\twhen {\n\t\t\tlist.isEmpty() -> if (filters.isEmpty()) {\n\t\t\t\tlistOf(\n\t\t\t\t\tEmptyState(\n\t\t\t\t\t\ticon = R.drawable.ic_empty_common,\n\t\t\t\t\t\ttextPrimary = R.string.nothing_found,\n\t\t\t\t\t\ttextSecondary = R.string.text_suggestion_holder,\n\t\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tlistOfNotNull(\n\t\t\t\t\tquickFilter.filterItem(filters),\n\t\t\t\t\tEmptyState(\n\t\t\t\t\t\ticon = R.drawable.ic_empty_common,\n\t\t\t\t\t\ttextPrimary = R.string.nothing_found,\n\t\t\t\t\t\ttextSecondary = R.string.text_empty_holder_secondary_filtered,\n\t\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\telse -> buildList(list.size + 1) {\n\t\t\t\tquickFilter.filterItem(filters)?.let(::add)\n\t\t\t\tmangaListMapper.toListModelList(this, list, mode)\n\t\t\t}\n\t\t}\n\t}.onStart {\n\t\tloadingCounter.increment()\n\t}.onFirst {\n\t\tloadingCounter.decrement()\n\t}.catch {\n\t\temit(listOf(it.toErrorState(canRetry = false)))\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\toverride fun onRefresh() = Unit\n\n\toverride fun onRetry() = Unit\n\n\tfun updateSuggestions() {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tsuggestionsScheduler.startNow()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt",
    "content": "package org.koitharu.kotatsu.suggestions.ui\n\nimport android.Manifest\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport android.os.Build\nimport android.provider.Settings\nimport androidx.annotation.FloatRange\nimport androidx.annotation.RequiresPermission\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.text.HtmlCompat\nimport androidx.core.text.bold\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.parseAsHtml\nimport androidx.hilt.work.HiltWorker\nimport androidx.work.BackoffPolicy\nimport androidx.work.Constraints\nimport androidx.work.CoroutineWorker\nimport androidx.work.ExistingPeriodicWorkPolicy\nimport androidx.work.ForegroundInfo\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.OutOfQuotaPolicy\nimport androidx.work.PeriodicWorkRequestBuilder\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport androidx.work.await\nimport androidx.work.workDataOf\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport dagger.Reusable\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedInject\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.take\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareException\nimport org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler\nimport org.koitharu.kotatsu.core.model.distinctById\nimport org.koitharu.kotatsu.core.model.getLocale\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.LocaleComparator\nimport org.koitharu.kotatsu.core.util.ext.asArrayList\nimport org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName\nimport org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.flatten\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.sanitize\nimport org.koitharu.kotatsu.core.util.ext.takeMostFrequent\nimport org.koitharu.kotatsu.core.util.ext.toBitmapOrNull\nimport org.koitharu.kotatsu.core.util.ext.trySetForeground\nimport org.koitharu.kotatsu.explore.data.MangaSourcesRepository\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaListFilter\nimport org.koitharu.kotatsu.parsers.model.MangaSource\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.parsers.model.SortOrder\nimport org.koitharu.kotatsu.parsers.util.almostEquals\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.sizeOrZero\nimport org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler\nimport org.koitharu.kotatsu.suggestions.domain.MangaSuggestion\nimport org.koitharu.kotatsu.suggestions.domain.SuggestionRepository\nimport org.koitharu.kotatsu.suggestions.domain.TagsBlacklist\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport kotlin.math.pow\nimport kotlin.random.Random\nimport androidx.appcompat.R as appcompatR\n\n@HiltWorker\nclass SuggestionsWorker @AssistedInject constructor(\n\t@Assisted appContext: Context,\n\t@Assisted params: WorkerParameters,\n\tprivate val coil: ImageLoader,\n\tprivate val suggestionRepository: SuggestionRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val favouritesRepository: FavouritesRepository,\n\tprivate val appSettings: AppSettings,\n\tprivate val captchaHandler: CaptchaHandler,\n\tprivate val workManager: WorkManager,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val sourcesRepository: MangaSourcesRepository,\n) : CoroutineWorker(appContext, params) {\n\n\tprivate val notificationManager by lazy { NotificationManagerCompat.from(appContext) }\n\n\toverride suspend fun doWork(): Result {\n\t\ttrySetForeground()\n\t\tif (!appSettings.isSuggestionsEnabled) {\n\t\t\tsuggestionRepository.clear()\n\t\t\treturn Result.success()\n\t\t}\n\t\tval count = doWorkImpl()\n\t\tval outputData = workDataOf(DATA_COUNT to count)\n\t\treturn Result.success(outputData)\n\t}\n\n\toverride suspend fun getForegroundInfo(): ForegroundInfo {\n\t\tval title = applicationContext.getString(R.string.suggestions_updating)\n\t\tval channel = NotificationChannelCompat.Builder(WORKER_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)\n\t\t\t.setName(title)\n\t\t\t.setShowBadge(true)\n\t\t\t.setVibrationEnabled(false)\n\t\t\t.setSound(null, null)\n\t\t\t.setLightsEnabled(true)\n\t\t\t.build()\n\t\tnotificationManager.createNotificationChannel(channel)\n\n\t\tval notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)\n\t\t\t.setContentTitle(title)\n\t\t\t.setContentIntent(\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\t0,\n\t\t\t\t\tAppRouter.suggestionsSettingsIntent(applicationContext),\n\t\t\t\t\t0,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t).addAction(\n\t\t\t\tappcompatR.drawable.abc_ic_clear_material,\n\t\t\t\tapplicationContext.getString(android.R.string.cancel),\n\t\t\t\tworkManager.createCancelPendingIntent(id),\n\t\t\t)\n\t\t\t.setPriority(NotificationCompat.PRIORITY_MIN)\n\t\t\t.setCategory(NotificationCompat.CATEGORY_SERVICE)\n\t\t\t.setDefaults(0)\n\t\t\t.setOngoing(false)\n\t\t\t.setSilent(true)\n\t\t\t.setProgress(0, 0, true)\n\t\t\t.setSmallIcon(android.R.drawable.stat_notify_sync)\n\t\t\t.setForegroundServiceBehavior(\n\t\t\t\tif (TAG_ONESHOT in tags) {\n\t\t\t\t\tNotificationCompat.FOREGROUND_SERVICE_IMMEDIATE\n\t\t\t\t} else {\n\t\t\t\t\tNotificationCompat.FOREGROUND_SERVICE_DEFERRED\n\t\t\t\t},\n\t\t\t)\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\tval actionIntent = PendingIntentCompat.getActivity(\n\t\t\t\tapplicationContext, SETTINGS_ACTION_CODE,\n\t\t\t\tIntent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)\n\t\t\t\t\t.putExtra(Settings.EXTRA_APP_PACKAGE, applicationContext.packageName)\n\t\t\t\t\t.putExtra(Settings.EXTRA_CHANNEL_ID, WORKER_CHANNEL_ID),\n\t\t\t\t0, false,\n\t\t\t)\n\t\t\tnotification.addAction(\n\t\t\t\tR.drawable.ic_settings,\n\t\t\t\tapplicationContext.getString(R.string.notifications_settings),\n\t\t\t\tactionIntent,\n\t\t\t)\n\t\t}\n\t\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\t\tForegroundInfo(WORKER_NOTIFICATION_ID, notification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)\n\t\t} else {\n\t\t\tForegroundInfo(WORKER_NOTIFICATION_ID, notification.build())\n\t\t}\n\t}\n\n\tprivate suspend fun doWorkImpl(): Int {\n\t\tval seed = (\n\t\t\thistoryRepository.getList(0, 20) +\n\t\t\t\tfavouritesRepository.getLastManga(20)\n\t\t\t).distinctById()\n\t\tval sources = getSources()\n\t\tif (seed.isEmpty() || sources.isEmpty()) {\n\t\t\treturn 0\n\t\t}\n\t\tval tagsBlacklist = TagsBlacklist(appSettings.suggestionsTagsBlacklist, TAG_EQ_THRESHOLD)\n\t\tval tags = seed.flatMap { it.tags.map { x -> x.title } }.takeMostFrequent(10)\n\n\t\tval semaphore = Semaphore(MAX_PARALLELISM)\n\t\tval producer = channelFlow {\n\t\t\tfor (it in sources) {\n\t\t\t\tif (it.isNsfw() && (appSettings.isSuggestionsExcludeNsfw || appSettings.isNsfwContentDisabled)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlaunch {\n\t\t\t\t\tsemaphore.withPermit {\n\t\t\t\t\t\tsend(getList(it, tags, tagsBlacklist))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tval suggestions = producer\n\t\t\t.flatten()\n\t\t\t.take(MAX_RAW_RESULTS)\n\t\t\t.map { manga ->\n\t\t\t\tMangaSuggestion(\n\t\t\t\t\tmanga = manga,\n\t\t\t\t\trelevance = computeRelevance(manga.tags, tags),\n\t\t\t\t)\n\t\t\t}.toList()\n\t\t\t.sortedBy { it.relevance }\n\t\t\t.take(MAX_RESULTS)\n\t\tsuggestionRepository.replace(suggestions)\n\t\tif (appSettings.isSuggestionsNotificationAvailable\n\t\t\t&& applicationContext.checkNotificationPermission(MANGA_CHANNEL_ID)\n\t\t) {\n\t\t\tfor (i in 0..3) {\n\t\t\t\ttry {\n\t\t\t\t\tval manga = suggestions[Random.nextInt(0, suggestions.size / 3)]\n\t\t\t\t\tval details = mangaRepositoryFactory.create(manga.manga.source)\n\t\t\t\t\t\t.getDetails(manga.manga)\n\t\t\t\t\tif (details.chapters.isNullOrEmpty()) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (details.rating > 0 && details.rating < RATING_MIN) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (details.isNsfw() && (appSettings.isSuggestionsExcludeNsfw || appSettings.isNsfwContentDisabled)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (details in tagsBlacklist) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tshowNotification(details)\n\t\t\t\t\tbreak\n\t\t\t\t} catch (e: CancellationException) {\n\t\t\t\t\tthrow e\n\t\t\t\t} catch (e: Exception) {\n\t\t\t\t\te.printStackTraceDebug()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn suggestions.size\n\t}\n\n\tprivate suspend fun getSources(): List<MangaSource> {\n\t\tif (appSettings.isSuggestionsIncludeDisabledSources) {\n\t\t\tval result = sourcesRepository.allMangaSources.toMutableList<MangaSource>()\n\t\t\tresult.addAll(sourcesRepository.getExternalSources())\n\t\t\tresult.shuffle()\n\t\t\tresult.sortWith(compareBy(nullsLast(LocaleComparator())) { it.getLocale() })\n\t\t\treturn result\n\t\t} else {\n\t\t\treturn sourcesRepository.getEnabledSources().shuffled()\n\t\t}\n\t}\n\n\tprivate suspend fun getList(\n\t\tsource: MangaSource,\n\t\ttags: List<String>,\n\t\tblacklist: TagsBlacklist,\n\t): List<Manga> = runCatchingCancellable {\n\t\tval repository = mangaRepositoryFactory.create(source)\n\t\tval availableOrders = repository.sortOrders\n\t\tval order = preferredSortOrders.first { it in availableOrders }\n\t\tval availableTags = repository.getFilterOptions().availableTags\n\t\tval tag = tags.firstNotNullOfOrNull { title ->\n\t\t\tavailableTags.find { x -> x !in blacklist && x.title.almostEquals(title, TAG_EQ_THRESHOLD) }\n\t\t}\n\t\tval list = repository.getList(\n\t\t\toffset = 0,\n\t\t\torder = order,\n\t\t\tfilter = MangaListFilter(tags = setOfNotNull(tag)),\n\t\t).asArrayList()\n\t\tif (appSettings.isSuggestionsExcludeNsfw) {\n\t\t\tlist.removeAll { it.isNsfw() }\n\t\t}\n\t\tif (blacklist.isNotEmpty()) {\n\t\t\tlist.removeAll { manga -> manga in blacklist }\n\t\t}\n\t\tlist.shuffle()\n\t\tlist.take(MAX_SOURCE_RESULTS)\n\t}.onFailure { e ->\n\t\tif (e is CloudFlareException) {\n\t\t\tcaptchaHandler.handle(e)\n\t\t}\n\t\te.printStackTraceDebug()\n\t}.getOrDefault(emptyList())\n\n\t@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)\n\tprivate suspend fun showNotification(manga: Manga) {\n\t\tval channel = NotificationChannelCompat.Builder(MANGA_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)\n\t\t\t.setName(applicationContext.getString(R.string.suggestions))\n\t\t\t.setDescription(applicationContext.getString(R.string.suggestions_summary))\n\t\t\t.setLightsEnabled(true)\n\t\t\t.setShowBadge(true)\n\t\t\t.build()\n\t\tnotificationManager.createNotificationChannel(channel)\n\n\t\tval id = manga.url.hashCode()\n\t\tval title = applicationContext.getString(R.string.suggestion_manga, manga.title)\n\t\tval builder = NotificationCompat.Builder(applicationContext, MANGA_CHANNEL_ID)\n\t\tval tagsText = manga.tags.joinToString(\", \") { it.title }\n\t\twith(builder) {\n\t\t\tsetContentText(tagsText)\n\t\t\tsetContentTitle(title)\n\t\t\tsetGroup(GROUP_SUGGESTION)\n\t\t\tsetLargeIcon(\n\t\t\t\tcoil.execute(\n\t\t\t\t\tImageRequest.Builder(applicationContext)\n\t\t\t\t\t\t.data(manga.coverUrl)\n\t\t\t\t\t\t.mangaSourceExtra(manga.source)\n\t\t\t\t\t\t.build(),\n\t\t\t\t).toBitmapOrNull(),\n\t\t\t)\n\t\t\tsetSmallIcon(R.drawable.ic_stat_suggestion)\n\t\t\tval description = manga.description?.parseAsHtml(HtmlCompat.FROM_HTML_MODE_COMPACT)?.sanitize()\n\t\t\tif (!description.isNullOrBlank()) {\n\t\t\t\tval style = NotificationCompat.BigTextStyle()\n\t\t\t\tstyle.bigText(\n\t\t\t\t\tbuildSpannedString {\n\t\t\t\t\t\tappend(tagsText)\n\t\t\t\t\t\tval chaptersCount = manga.chapters.sizeOrZero()\n\t\t\t\t\t\tappendLine()\n\t\t\t\t\t\tbold {\n\t\t\t\t\t\t\tappend(\n\t\t\t\t\t\t\t\tapplicationContext.resources.getQuantityStringSafe(\n\t\t\t\t\t\t\t\t\tR.plurals.chapters,\n\t\t\t\t\t\t\t\t\tchaptersCount,\n\t\t\t\t\t\t\t\t\tchaptersCount,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tappendLine()\n\t\t\t\t\t\tappend(description)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tstyle.setBigContentTitle(title)\n\t\t\t\tsetStyle(style)\n\t\t\t}\n\t\t\tval intent = AppRouter.detailsIntent(applicationContext, manga)\n\t\t\tsetContentIntent(\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\tid,\n\t\t\t\t\tintent,\n\t\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t)\n\t\t\tsetAutoCancel(true)\n\t\t\tsetCategory(NotificationCompat.CATEGORY_RECOMMENDATION)\n\t\t\tsetVisibility(if (manga.isNsfw()) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PRIVATE)\n\t\t\tsetShortcutId(manga.id.toString())\n\t\t\tpriority = NotificationCompat.PRIORITY_DEFAULT\n\n\t\t\taddAction(\n\t\t\t\tR.drawable.ic_read,\n\t\t\t\tapplicationContext.getString(R.string.read),\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\tid + 2,\n\t\t\t\t\tReaderIntent.Builder(applicationContext).manga(manga).build().intent,\n\t\t\t\t\t0,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t)\n\n\t\t\taddAction(\n\t\t\t\tR.drawable.ic_suggestion,\n\t\t\t\tapplicationContext.getString(R.string.more),\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\t0,\n\t\t\t\t\tAppRouter.suggestionsIntent(applicationContext),\n\t\t\t\t\t0,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t\tnotificationManager.notify(TAG, id, builder.build())\n\t}\n\n\t@FloatRange(from = 0.0, to = 1.0)\n\tprivate fun computeRelevance(mangaTags: Set<MangaTag>, allTags: List<String>): Float {\n\t\tval maxWeight = (allTags.size + allTags.size + 1 - mangaTags.size) * mangaTags.size / 2.0\n\t\tval weight = mangaTags.sumOf { tag ->\n\t\t\tval index = allTags.inexactIndexOf(tag.title, TAG_EQ_THRESHOLD)\n\t\t\tif (index < 0) 0 else allTags.size - index\n\t\t}\n\t\treturn (weight / maxWeight).pow(2.0).toFloat()\n\t}\n\n\tprivate fun Iterable<String>.inexactIndexOf(element: String, threshold: Float): Int {\n\t\tforEachIndexed { i, t ->\n\t\t\tif (t.almostEquals(element, threshold)) {\n\t\t\t\treturn i\n\t\t\t}\n\t\t}\n\t\treturn -1\n\t}\n\n\t@Reusable\n\tclass Scheduler @Inject constructor(\n\t\tprivate val workManager: WorkManager,\n\t\tprivate val settings: AppSettings,\n\t) : PeriodicWorkScheduler {\n\n\t\toverride suspend fun schedule() {\n\t\t\tval request = PeriodicWorkRequestBuilder<SuggestionsWorker>(6, TimeUnit.HOURS)\n\t\t\t\t.setConstraints(createConstraints())\n\t\t\t\t.addTag(TAG)\n\t\t\t\t.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.HOURS)\n\t\t\t\t.build()\n\t\t\tworkManager\n\t\t\t\t.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)\n\t\t\t\t.await()\n\t\t}\n\n\t\toverride suspend fun unschedule() {\n\t\t\tworkManager\n\t\t\t\t.cancelUniqueWork(TAG)\n\t\t\t\t.await()\n\t\t}\n\n\t\toverride suspend fun isScheduled(): Boolean {\n\t\t\treturn workManager\n\t\t\t\t.awaitUniqueWorkInfoByName(TAG)\n\t\t\t\t.any { !it.state.isFinished }\n\t\t}\n\n\t\tsuspend fun startNow() {\n\t\t\tif (workManager.awaitWorkInfosByTag(TAG_ONESHOT).any { !it.state.isFinished }) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tval constraints = Constraints.Builder()\n\t\t\t\t.setRequiredNetworkType(NetworkType.CONNECTED)\n\t\t\t\t.build()\n\t\t\tval request = OneTimeWorkRequestBuilder<SuggestionsWorker>()\n\t\t\t\t.setConstraints(constraints)\n\t\t\t\t.addTag(TAG_ONESHOT)\n\t\t\t\t.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)\n\t\t\t\t.build()\n\t\t\tworkManager.enqueue(request).await()\n\t\t}\n\n\t\tprivate fun createConstraints() = Constraints.Builder()\n\t\t\t.setRequiredNetworkType(if (settings.isSuggestionsWiFiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)\n\t\t\t.setRequiresBatteryNotLow(true)\n\t\t\t.build()\n\t}\n\n\tprivate companion object {\n\n\t\tconst val TAG = \"suggestions\"\n\t\tconst val TAG_ONESHOT = \"suggestions_oneshot\"\n\t\tconst val DATA_COUNT = \"count\"\n\t\tconst val WORKER_CHANNEL_ID = \"suggestion_worker\"\n\t\tconst val MANGA_CHANNEL_ID = \"suggestions\"\n\t\tconst val GROUP_SUGGESTION = \"org.koitharu.kotatsu.SUGGESTIONS\"\n\t\tconst val WORKER_NOTIFICATION_ID = 36\n\t\tconst val MAX_RESULTS = 160\n\t\tconst val MAX_PARALLELISM = 3\n\t\tconst val MAX_SOURCE_RESULTS = 20\n\t\tconst val MAX_RAW_RESULTS = 280\n\t\tconst val TAG_EQ_THRESHOLD = 0.4f\n\t\tconst val RATING_MIN = 0.5f\n\t\tconst val SETTINGS_ACTION_CODE = 4\n\n\t\tval preferredSortOrders = listOf(\n\t\t\tSortOrder.UPDATED,\n\t\t\tSortOrder.NEWEST,\n\t\t\tSortOrder.POPULARITY,\n\t\t\tSortOrder.RATING,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt",
    "content": "package org.koitharu.kotatsu.sync.data\n\nimport dagger.Reusable\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport org.json.JSONObject\nimport org.koitharu.kotatsu.core.exceptions.SyncApiException\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.util.ext.toRequestBody\nimport org.koitharu.kotatsu.parsers.util.await\nimport org.koitharu.kotatsu.parsers.util.parseJson\nimport org.koitharu.kotatsu.parsers.util.parseRaw\nimport org.koitharu.kotatsu.parsers.util.removeSurrounding\nimport javax.inject.Inject\n\n@Reusable\nclass SyncAuthApi @Inject constructor(\n\t@BaseHttpClient private val okHttpClient: OkHttpClient,\n) {\n\n\tsuspend fun authenticate(syncURL: String, email: String, password: String): String {\n\t\tval body = JSONObject(\n\t\t\tmapOf(\"email\" to email, \"password\" to password),\n\t\t).toRequestBody()\n\t\tval request = Request.Builder()\n\t\t\t.url(\"$syncURL/auth\")\n\t\t\t.post(body)\n\t\t\t.build()\n\t\tval response = okHttpClient.newCall(request).await()\n\t\tif (response.isSuccessful) {\n\t\t\treturn response.parseJson().getString(\"token\")\n\t\t} else {\n\t\t\tval code = response.code\n\t\t\tval message = response.parseRaw().removeSurrounding('\"')\n\t\t\tthrow SyncApiException(message, code)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt",
    "content": "package org.koitharu.kotatsu.sync.data\n\nimport android.accounts.Account\nimport android.accounts.AccountManager\nimport android.content.Context\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.Authenticator\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Route\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.network.CommonHeaders\n\nclass SyncAuthenticator(\n\tcontext: Context,\n\tprivate val account: Account,\n\tprivate val syncSettings: SyncSettings,\n\tprivate val authApi: SyncAuthApi,\n) : Authenticator {\n\n\tprivate val accountManager = AccountManager.get(context)\n\tprivate val tokenType = context.getString(R.string.account_type_sync)\n\n\toverride fun authenticate(route: Route?, response: Response): Request? {\n\t\tval newToken = tryRefreshToken() ?: return null\n\t\taccountManager.setAuthToken(account, tokenType, newToken)\n\t\treturn response.request.newBuilder()\n\t\t\t.header(CommonHeaders.AUTHORIZATION, \"Bearer $newToken\")\n\t\t\t.build()\n\t}\n\n\tprivate fun tryRefreshToken() = runCatching {\n\t\trunBlocking {\n\t\t\tauthApi.authenticate(\n\t\t\t\tsyncSettings.syncUrl,\n\t\t\t\taccount.name,\n\t\t\t\taccountManager.getPassword(account),\n\t\t\t)\n\t\t}\n\t}.getOrNull()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/SyncInterceptor.kt",
    "content": "package org.koitharu.kotatsu.sync.data\n\nimport android.accounts.Account\nimport android.accounts.AccountManager\nimport android.content.Context\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.DATABASE_VERSION\nimport org.koitharu.kotatsu.core.network.CommonHeaders\n\nclass SyncInterceptor(\n\tcontext: Context,\n\tprivate val account: Account,\n) : Interceptor {\n\n\tprivate val accountManager = AccountManager.get(context)\n\tprivate val tokenType = context.getString(R.string.account_type_sync)\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\tval token = accountManager.peekAuthToken(account, tokenType)\n\t\tval requestBuilder = chain.request().newBuilder()\n\t\tif (token != null) {\n\t\t\trequestBuilder.header(CommonHeaders.AUTHORIZATION, \"Bearer $token\")\n\t\t}\n\t\trequestBuilder.header(\"X-App-Version\", BuildConfig.VERSION_CODE.toString())\n\t\trequestBuilder.header(\"X-Db-Version\", DATABASE_VERSION.toString())\n\t\treturn chain.proceed(requestBuilder.build())\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/SyncSettings.kt",
    "content": "package org.koitharu.kotatsu.sync.data\n\nimport android.accounts.Account\nimport android.accounts.AccountManager\nimport android.content.Context\nimport androidx.annotation.WorkerThread\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.isHttpUrl\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport javax.inject.Inject\n\nclass SyncSettings(\n\tcontext: Context,\n\tprivate val account: Account?,\n) {\n\n\t@Inject\n\tconstructor(@ApplicationContext context: Context) : this(\n\t\tcontext,\n\t\tAccountManager.get(context)?.getAccountsByType(\n\t\t\tcontext.getString(R.string.account_type_sync),\n\t\t)?.firstOrNull(),\n\t)\n\n\tprivate val accountManager = AccountManager.get(context)\n\tprivate val defaultSyncUrl = context.resources.getStringArray(R.array.sync_url_list).first()\n\n\t@get:WorkerThread\n\t@set:WorkerThread\n\tvar syncUrl: String\n\t\tget() = account?.let {\n\t\t\taccountManager.getUserData(it, KEY_SYNC_URL)?.withHttpSchema()\n\t\t}.ifNullOrEmpty { defaultSyncUrl }\n\t\tset(value) {\n\t\t\taccount?.let {\n\t\t\t\taccountManager.setUserData(it, KEY_SYNC_URL, value)\n\t\t\t}\n\t\t}\n\n\tcompanion object {\n\n\t\tprivate fun String.withHttpSchema(): String = if (isHttpUrl()) {\n\t\t\tthis\n\t\t} else {\n\t\t\t\"http://$this\"\n\t\t}\n\n\t\tconst val KEY_SYNC_URL = \"host\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/model/FavouriteCategorySyncDto.kt",
    "content": "package org.koitharu.kotatsu.sync.data.model\n\nimport android.database.Cursor\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.util.ext.buildContentValues\nimport org.koitharu.kotatsu.core.util.ext.getBoolean\n\n@Serializable\ndata class FavouriteCategorySyncDto(\n\t@SerialName(\"category_id\") val categoryId: Int,\n\t@SerialName(\"created_at\") val createdAt: Long,\n\t@SerialName(\"sort_key\") val sortKey: Int,\n\t@SerialName(\"title\") val title: String,\n\t@SerialName(\"order\") val order: String,\n\t@SerialName(\"track\") val track: Boolean,\n\t@SerialName(\"show_in_lib\") val isVisibleInLibrary: Boolean,\n\t@SerialName(\"deleted_at\") val deletedAt: Long,\n) {\n\n\tconstructor(cursor: Cursor) : this(\n\t\tcategoryId = cursor.getInt(cursor.getColumnIndexOrThrow(\"category_id\")),\n\t\tcreatedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"created_at\")),\n\t\tsortKey = cursor.getInt(cursor.getColumnIndexOrThrow(\"sort_key\")),\n\t\ttitle = cursor.getString(cursor.getColumnIndexOrThrow(\"title\")),\n\t\torder = cursor.getString(cursor.getColumnIndexOrThrow(\"order\")),\n\t\ttrack = cursor.getBoolean(cursor.getColumnIndexOrThrow(\"track\")),\n\t\tisVisibleInLibrary = cursor.getBoolean(cursor.getColumnIndexOrThrow(\"show_in_lib\")),\n\t\tdeletedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"deleted_at\")),\n\t)\n\n\tfun toContentValues() = buildContentValues(8) {\n\t\tput(\"category_id\", categoryId)\n\t\tput(\"created_at\", createdAt)\n\t\tput(\"sort_key\", sortKey)\n\t\tput(\"title\", title)\n\t\tput(\"`order`\", order)\n\t\tput(\"track\", track)\n\t\tput(\"show_in_lib\", isVisibleInLibrary)\n\t\tput(\"deleted_at\", deletedAt)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/model/FavouriteSyncDto.kt",
    "content": "package org.koitharu.kotatsu.sync.data.model\n\nimport android.database.Cursor\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.util.ext.buildContentValues\nimport org.koitharu.kotatsu.core.util.ext.getBoolean\n\n@Serializable\ndata class FavouriteSyncDto(\n\t@SerialName(\"manga_id\") val mangaId: Long,\n\t@SerialName(\"manga\") val manga: MangaSyncDto,\n\t@SerialName(\"category_id\") val categoryId: Int,\n\t@SerialName(\"sort_key\") val sortKey: Int,\n\t@SerialName(\"pinned\") val pinned: Boolean,\n\t@SerialName(\"created_at\") val createdAt: Long,\n\t@SerialName(\"deleted_at\") var deletedAt: Long,\n) {\n\n\tconstructor(cursor: Cursor, manga: MangaSyncDto) : this(\n\t\tmangaId = cursor.getLong(cursor.getColumnIndexOrThrow(\"manga_id\")),\n\t\tmanga = manga,\n\t\tcategoryId = cursor.getInt(cursor.getColumnIndexOrThrow(\"category_id\")),\n\t\tsortKey = cursor.getInt(cursor.getColumnIndexOrThrow(\"sort_key\")),\n\t\tpinned = cursor.getBoolean(cursor.getColumnIndexOrThrow(\"pinned\")),\n\t\tcreatedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"created_at\")),\n\t\tdeletedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"deleted_at\")),\n\t)\n\n\tfun toContentValues() = buildContentValues(6) {\n\t\tput(\"manga_id\", mangaId)\n\t\tput(\"category_id\", categoryId)\n\t\tput(\"sort_key\", sortKey)\n\t\tput(\"pinned\", pinned)\n\t\tput(\"created_at\", createdAt)\n\t\tput(\"deleted_at\", deletedAt)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/model/HistorySyncDto.kt",
    "content": "package org.koitharu.kotatsu.sync.data.model\n\nimport android.database.Cursor\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.util.ext.buildContentValues\n\n@Serializable\ndata class HistorySyncDto(\n\t@SerialName(\"manga_id\") val mangaId: Long,\n\t@SerialName(\"created_at\") val createdAt: Long,\n\t@SerialName(\"updated_at\") val updatedAt: Long,\n\t@SerialName(\"chapter_id\") val chapterId: Long,\n\t@SerialName(\"page\") val page: Int,\n\t@SerialName(\"scroll\") val scroll: Float,\n\t@SerialName(\"percent\") val percent: Float,\n\t@SerialName(\"deleted_at\") val deletedAt: Long,\n\t@SerialName(\"chapters\") val chaptersCount: Int,\n\t@SerialName(\"manga\") val manga: MangaSyncDto,\n) {\n\n\tconstructor(cursor: Cursor, manga: MangaSyncDto) : this(\n\t\tmangaId = cursor.getLong(cursor.getColumnIndexOrThrow(\"manga_id\")),\n\t\tcreatedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"created_at\")),\n\t\tupdatedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"updated_at\")),\n\t\tchapterId = cursor.getLong(cursor.getColumnIndexOrThrow(\"chapter_id\")),\n\t\tpage = cursor.getInt(cursor.getColumnIndexOrThrow(\"page\")),\n\t\tscroll = cursor.getFloat(cursor.getColumnIndexOrThrow(\"scroll\")),\n\t\tpercent = cursor.getFloat(cursor.getColumnIndexOrThrow(\"percent\")),\n\t\tdeletedAt = cursor.getLong(cursor.getColumnIndexOrThrow(\"deleted_at\")),\n\t\tchaptersCount = cursor.getInt(cursor.getColumnIndexOrThrow(\"chapters\")),\n\t\tmanga = manga,\n\t)\n\n\tfun toContentValues() = buildContentValues(9) {\n\t\tput(\"manga_id\", mangaId)\n\t\tput(\"created_at\", createdAt)\n\t\tput(\"updated_at\", updatedAt)\n\t\tput(\"chapter_id\", chapterId)\n\t\tput(\"page\", page)\n\t\tput(\"scroll\", scroll)\n\t\tput(\"percent\", percent)\n\t\tput(\"deleted_at\", deletedAt)\n\t\tput(\"chapters\", chaptersCount)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/model/MangaSyncDto.kt",
    "content": "package org.koitharu.kotatsu.sync.data.model\n\nimport android.database.Cursor\nimport androidx.core.database.getStringOrNull\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.util.ext.buildContentValues\n\n@Serializable\ndata class MangaSyncDto(\n\t@SerialName(\"manga_id\") val id: Long,\n\t@SerialName(\"title\") val title: String,\n\t@SerialName(\"alt_title\") val altTitle: String?,\n\t@SerialName(\"url\") val url: String,\n\t@SerialName(\"public_url\") val publicUrl: String,\n\t@SerialName(\"rating\") val rating: Float,\n\t@SerialName(\"content_rating\") val contentRating: String?,\n\t@SerialName(\"cover_url\") val coverUrl: String,\n\t@SerialName(\"large_cover_url\") val largeCoverUrl: String?,\n\t@SerialName(\"tags\") val tags: Set<MangaTagSyncDto>,\n\t@SerialName(\"state\") val state: String?,\n\t@SerialName(\"author\") val author: String?,\n\t@SerialName(\"source\") val source: String,\n) {\n\n\tconstructor(cursor: Cursor, tags: Set<MangaTagSyncDto>) : this(\n\t\tid = cursor.getLong(cursor.getColumnIndexOrThrow(\"manga_id\")),\n\t\ttitle = cursor.getString(cursor.getColumnIndexOrThrow(\"title\")),\n\t\taltTitle = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(\"alt_title\")),\n\t\turl = cursor.getString(cursor.getColumnIndexOrThrow(\"url\")),\n\t\tpublicUrl = cursor.getString(cursor.getColumnIndexOrThrow(\"public_url\")),\n\t\trating = cursor.getFloat(cursor.getColumnIndexOrThrow(\"rating\")),\n\t\tcontentRating = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(\"content_rating\")),\n\t\tcoverUrl = cursor.getString(cursor.getColumnIndexOrThrow(\"cover_url\")),\n\t\tlargeCoverUrl = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(\"large_cover_url\")),\n\t\ttags = tags,\n\t\tstate = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(\"state\")),\n\t\tauthor = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(\"author\")),\n\t\tsource = cursor.getString(cursor.getColumnIndexOrThrow(\"source\")),\n\t)\n\n\tfun toContentValues() = buildContentValues(12) {\n\t\tput(\"manga_id\", id)\n\t\tput(\"title\", title)\n\t\tput(\"alt_title\", altTitle)\n\t\tput(\"url\", url)\n\t\tput(\"public_url\", publicUrl)\n\t\tput(\"rating\", rating)\n\t\tput(\"content_rating\", contentRating)\n\t\tput(\"cover_url\", coverUrl)\n\t\tput(\"large_cover_url\", largeCoverUrl)\n\t\tput(\"state\", state)\n\t\tput(\"author\", author)\n\t\tput(\"source\", source)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/model/MangaTagSyncDto.kt",
    "content": "package org.koitharu.kotatsu.sync.data.model\n\nimport android.database.Cursor\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport org.koitharu.kotatsu.core.util.ext.buildContentValues\n\n@Serializable\ndata class MangaTagSyncDto(\n\t@SerialName(\"tag_id\") val id: Long,\n\t@SerialName(\"title\") val title: String,\n\t@SerialName(\"key\") val key: String,\n\t@SerialName(\"source\") val source: String,\n) {\n\n\tconstructor(cursor: Cursor) : this(\n\t\tid = cursor.getLong(cursor.getColumnIndexOrThrow(\"tag_id\")),\n\t\ttitle = cursor.getString(cursor.getColumnIndexOrThrow(\"title\")),\n\t\tkey = cursor.getString(cursor.getColumnIndexOrThrow(\"key\")),\n\t\tsource = cursor.getString(cursor.getColumnIndexOrThrow(\"source\")),\n\t)\n\n\tfun toContentValues() = buildContentValues(4) {\n\t\tput(\"tag_id\", id)\n\t\tput(\"title\", title)\n\t\tput(\"key\", key)\n\t\tput(\"source\", source)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/data/model/SyncDto.kt",
    "content": "package org.koitharu.kotatsu.sync.data.model\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\n\n@Serializable\ndata class SyncDto(\n\t@SerialName(\"history\") val history: List<HistorySyncDto>? = null,\n\t@SerialName(\"categories\") val categories: List<FavouriteCategorySyncDto>? = null,\n\t@SerialName(\"favourites\") val favourites: List<FavouriteSyncDto>? = null,\n\t@SerialName(\"timestamp\") val timestamp: Long,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/domain/SyncAuthResult.kt",
    "content": "package org.koitharu.kotatsu.sync.domain\n\ndata class SyncAuthResult(\n\tval syncURL: String,\n\tval email: String,\n\tval password: String,\n\tval token: String,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/domain/SyncController.kt",
    "content": "package org.koitharu.kotatsu.sync.domain\n\nimport android.accounts.Account\nimport android.accounts.AccountManager\nimport android.content.ContentResolver\nimport android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE\nimport android.content.Context\nimport android.os.Bundle\nimport androidx.room.InvalidationTracker\nimport androidx.room.withTransaction\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.channels.trySendBlocking\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES\nimport org.koitharu.kotatsu.core.db.TABLE_HISTORY\nimport org.koitharu.kotatsu.core.util.ext.processLifecycleScope\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport javax.inject.Singleton\n\n@Singleton\nclass SyncController @Inject constructor(\n\t@ApplicationContext context: Context,\n\tprivate val dbProvider: Provider<MangaDatabase>,\n) : InvalidationTracker.Observer(arrayOf(TABLE_HISTORY, TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)) {\n\n\tprivate val authorityHistory = context.getString(R.string.sync_authority_history)\n\tprivate val authorityFavourites = context.getString(R.string.sync_authority_favourites)\n\tprivate val am = AccountManager.get(context)\n\tprivate val accountType = context.getString(R.string.account_type_sync)\n\tprivate val mutex = Mutex()\n\tprivate val defaultGcPeriod = TimeUnit.DAYS.toMillis(2) // gc period if sync disabled\n\n\toverride fun onInvalidated(tables: Set<String>) {\n\t\tval favourites = (TABLE_FAVOURITES in tables || TABLE_FAVOURITE_CATEGORIES in tables)\n\t\t\t&& !isSyncActiveOrPending(authorityFavourites)\n\t\tval history = TABLE_HISTORY in tables && !isSyncActiveOrPending(authorityHistory)\n\t\tif (favourites || history) {\n\t\t\trequestSync(favourites, history)\n\t\t}\n\t}\n\n\tfun isEnabled(account: Account): Boolean {\n\t\treturn ContentResolver.getMasterSyncAutomatically() && (ContentResolver.getSyncAutomatically(\n\t\t\taccount,\n\t\t\tauthorityFavourites,\n\t\t) || ContentResolver.getSyncAutomatically(\n\t\t\taccount,\n\t\t\tauthorityHistory,\n\t\t))\n\t}\n\n\tfun getLastSync(account: Account, authority: String): Long {\n\t\tval key = \"last_sync_\" + authority.substringAfterLast('.')\n\t\tval rawValue = am.getUserData(account, key) ?: return 0L\n\t\treturn rawValue.toLongOrNull() ?: 0L\n\t}\n\n\tfun observeSyncStatus(): Flow<Boolean> = callbackFlow {\n\t\tval handle = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_ACTIVE) { which ->\n\t\t\ttrySendBlocking(which and SYNC_OBSERVER_TYPE_ACTIVE != 0)\n\t\t}\n\t\tawaitClose { ContentResolver.removeStatusChangeListener(handle) }\n\t}\n\n\tsuspend fun requestFullSync() = withContext(Dispatchers.Default) {\n\t\trequestSyncImpl(favourites = true, history = true)\n\t}\n\n\tprivate fun requestSync(favourites: Boolean, history: Boolean) = processLifecycleScope.launch(Dispatchers.Default) {\n\t\trequestSyncImpl(favourites = favourites, history = history)\n\t}\n\n\tprivate suspend fun requestSyncImpl(favourites: Boolean, history: Boolean) = mutex.withLock {\n\t\tif (!favourites && !history) {\n\t\t\treturn\n\t\t}\n\t\tval db = dbProvider.get()\n\t\tval account = peekAccount()\n\t\tif (account == null || !ContentResolver.getMasterSyncAutomatically()) {\n\t\t\tdb.gc(favourites, history)\n\t\t\treturn\n\t\t}\n\t\tvar gcHistory = false\n\t\tvar gcFavourites = false\n\t\tif (favourites) {\n\t\t\tif (ContentResolver.getSyncAutomatically(account, authorityFavourites)) {\n\t\t\t\tContentResolver.requestSync(account, authorityFavourites, Bundle.EMPTY)\n\t\t\t} else {\n\t\t\t\tgcFavourites = true\n\t\t\t}\n\t\t}\n\t\tif (history) {\n\t\t\tif (ContentResolver.getSyncAutomatically(account, authorityHistory)) {\n\t\t\t\tContentResolver.requestSync(account, authorityHistory, Bundle.EMPTY)\n\t\t\t} else {\n\t\t\t\tgcHistory = true\n\t\t\t}\n\t\t}\n\t\tif (gcHistory || gcFavourites) {\n\t\t\tdb.gc(gcFavourites, gcHistory)\n\t\t}\n\t}\n\n\tprivate fun peekAccount(): Account? {\n\t\treturn am.getAccountsByType(accountType).firstOrNull()\n\t}\n\n\tprivate suspend fun MangaDatabase.gc(favourites: Boolean, history: Boolean) = withTransaction {\n\t\tval deletedAt = System.currentTimeMillis() - defaultGcPeriod\n\t\tif (history) {\n\t\t\tgetHistoryDao().gc(deletedAt)\n\t\t}\n\t\tif (favourites) {\n\t\t\tgetFavouritesDao().gc(deletedAt)\n\t\t\tgetFavouriteCategoriesDao().gc(deletedAt)\n\t\t}\n\t}\n\n\tprivate fun isSyncActiveOrPending(authority: String): Boolean {\n\t\tval account = peekAccount() ?: return false\n\t\treturn ContentResolver.isSyncActive(account, authority) || ContentResolver.isSyncPending(account, authority)\n\t}\n\n\tcompanion object {\n\n\t\t@JvmStatic\n\t\tfun setLastSync(context: Context, account: Account, authority: String, time: Long) {\n\t\t\tval key = \"last_sync_\" + authority.substringAfterLast('.')\n\t\t\tval am = AccountManager.get(context)\n\t\t\tam.setUserData(account, key, time.toString())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/domain/SyncHelper.kt",
    "content": "package org.koitharu.kotatsu.sync.domain\n\nimport android.accounts.Account\nimport android.content.ContentProviderClient\nimport android.content.ContentProviderOperation\nimport android.content.ContentProviderResult\nimport android.content.Context\nimport android.content.OperationApplicationException\nimport android.content.SyncResult\nimport android.content.SyncStats\nimport android.database.Cursor\nimport android.util.Log\nimport androidx.annotation.WorkerThread\nimport androidx.core.net.toUri\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.serialization.json.Json\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.Response\nimport okio.IOException\nimport org.jetbrains.annotations.Blocking\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES\nimport org.koitharu.kotatsu.core.db.TABLE_HISTORY\nimport org.koitharu.kotatsu.core.db.TABLE_MANGA\nimport org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS\nimport org.koitharu.kotatsu.core.db.TABLE_TAGS\nimport org.koitharu.kotatsu.core.network.BaseHttpClient\nimport org.koitharu.kotatsu.core.util.ext.buildContentValues\nimport org.koitharu.kotatsu.core.util.ext.map\nimport org.koitharu.kotatsu.core.util.ext.mapToSet\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.sync.data.SyncAuthApi\nimport org.koitharu.kotatsu.sync.data.SyncAuthenticator\nimport org.koitharu.kotatsu.sync.data.SyncInterceptor\nimport org.koitharu.kotatsu.sync.data.SyncSettings\nimport org.koitharu.kotatsu.sync.data.model.FavouriteCategorySyncDto\nimport org.koitharu.kotatsu.sync.data.model.FavouriteSyncDto\nimport org.koitharu.kotatsu.sync.data.model.HistorySyncDto\nimport org.koitharu.kotatsu.sync.data.model.MangaSyncDto\nimport org.koitharu.kotatsu.sync.data.model.MangaTagSyncDto\nimport org.koitharu.kotatsu.sync.data.model.SyncDto\nimport java.net.HttpURLConnection\nimport java.util.concurrent.TimeUnit\n\nclass SyncHelper @AssistedInject constructor(\n\t@ApplicationContext context: Context,\n\t@BaseHttpClient baseHttpClient: OkHttpClient,\n\t@Assisted private val account: Account,\n\t@Assisted private val provider: ContentProviderClient,\n\tprivate val settings: SyncSettings,\n) {\n\n\tprivate val authorityHistory = context.getString(R.string.sync_authority_history)\n\tprivate val authorityFavourites = context.getString(R.string.sync_authority_favourites)\n\tprivate val mediaTypeJson = \"application/json\".toMediaType()\n\tprivate val httpClient = baseHttpClient.newBuilder()\n\t\t.authenticator(SyncAuthenticator(context, account, settings, SyncAuthApi(OkHttpClient())))\n\t\t.addInterceptor(SyncInterceptor(context, account))\n\t\t.build()\n\tprivate val baseUrl: String by lazy {\n\t\tsettings.syncUrl\n\t}\n\tprivate val defaultGcPeriod: Long // gc period if sync enabled\n\t\tget() = TimeUnit.DAYS.toMillis(4)\n\n\t@WorkerThread\n\tfun syncFavourites(stats: SyncStats) {\n\t\tval payload = Json.encodeToString(\n\t\t\tSyncDto(\n\t\t\t\thistory = null,\n\t\t\t\tfavourites = getFavourites(),\n\t\t\t\tcategories = getFavouriteCategories(),\n\t\t\t\ttimestamp = System.currentTimeMillis(),\n\t\t\t),\n\t\t)\n\t\tval request = Request.Builder()\n\t\t\t.url(\"$baseUrl/resource/$TABLE_FAVOURITES\")\n\t\t\t.post(payload.toRequestBody(mediaTypeJson))\n\t\t\t.build()\n\t\tval response = httpClient.newCall(request).execute().parseDtoOrNull()\n\t\tresponse?.categories?.let { categories ->\n\t\t\tval categoriesResult = upsertFavouriteCategories(categories)\n\t\t\tstats.numDeletes += categoriesResult.firstOrNull()?.count?.toLong() ?: 0L\n\t\t\tstats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }\n\t\t}\n\t\tresponse?.favourites?.let { favourites ->\n\t\t\tval favouritesResult = upsertFavourites(favourites)\n\t\t\tstats.numDeletes += favouritesResult.firstOrNull()?.count?.toLong() ?: 0L\n\t\t\tstats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }\n\t\t\tstats.numEntries += stats.numInserts + stats.numDeletes\n\t\t}\n\t\tgcFavourites()\n\t}\n\n\t@Blocking\n\t@WorkerThread\n\tfun syncHistory(stats: SyncStats) {\n\t\tval payload = Json.encodeToString(\n\t\t\tSyncDto(\n\t\t\t\thistory = getHistory(),\n\t\t\t\tfavourites = null,\n\t\t\t\tcategories = null,\n\t\t\t\ttimestamp = System.currentTimeMillis(),\n\t\t\t),\n\t\t)\n\t\tval request = Request.Builder()\n\t\t\t.url(\"$baseUrl/resource/$TABLE_HISTORY\")\n\t\t\t.post(payload.toRequestBody(mediaTypeJson))\n\t\t\t.build()\n\t\tval response = httpClient.newCall(request).execute().parseDtoOrNull()\n\t\tresponse?.history?.let { history ->\n\t\t\tval result = upsertHistory(history)\n\t\t\tstats.numDeletes += result.firstOrNull()?.count?.toLong() ?: 0L\n\t\t\tstats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }\n\t\t\tstats.numEntries += stats.numInserts + stats.numDeletes\n\t\t}\n\t\tgcHistory()\n\t}\n\n\tfun onError(e: Throwable) {\n\t\te.printStackTraceDebug()\n\t}\n\n\tfun onSyncComplete(result: SyncResult) {\n\t\tif (BuildConfig.DEBUG) {\n\t\t\tLog.i(\"Sync\", \"Sync finished: ${result.toDebugString()}\")\n\t\t}\n\t}\n\n\tprivate fun upsertHistory(history: List<HistorySyncDto>): Array<ContentProviderResult> {\n\t\tval uri = uri(authorityHistory, TABLE_HISTORY)\n\t\tval operations = ArrayList<ContentProviderOperation>()\n\t\thistory.mapTo(operations) {\n\t\t\toperations.addAll(upsertManga(it.manga, authorityHistory))\n\t\t\tContentProviderOperation.newInsert(uri)\n\t\t\t\t.withValues(it.toContentValues())\n\t\t\t\t.build()\n\t\t}\n\t\treturn provider.applyBatch(operations)\n\t}\n\n\tprivate fun upsertFavouriteCategories(categories: List<FavouriteCategorySyncDto>): Array<ContentProviderResult> {\n\t\tval uri = uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES)\n\t\tval operations = ArrayList<ContentProviderOperation>()\n\t\tcategories.mapTo(operations) {\n\t\t\tContentProviderOperation.newInsert(uri)\n\t\t\t\t.withValues(it.toContentValues())\n\t\t\t\t.build()\n\t\t}\n\t\treturn provider.applyBatch(operations)\n\t}\n\n\tprivate fun upsertFavourites(favourites: List<FavouriteSyncDto>): Array<ContentProviderResult> {\n\t\tval uri = uri(authorityFavourites, TABLE_FAVOURITES)\n\t\tval operations = ArrayList<ContentProviderOperation>()\n\t\tfavourites.mapTo(operations) {\n\t\t\toperations.addAll(upsertManga(it.manga, authorityFavourites))\n\t\t\tContentProviderOperation.newInsert(uri)\n\t\t\t\t.withValues(it.toContentValues())\n\t\t\t\t.build()\n\t\t}\n\t\treturn provider.applyBatch(operations)\n\t}\n\n\tprivate fun upsertManga(manga: MangaSyncDto, authority: String): List<ContentProviderOperation> {\n\t\tval tags = manga.tags\n\t\tval result = ArrayList<ContentProviderOperation>(tags.size * 2 + 1)\n\t\tfor (tag in tags) {\n\t\t\tresult += ContentProviderOperation.newInsert(uri(authority, TABLE_TAGS))\n\t\t\t\t.withValues(tag.toContentValues())\n\t\t\t\t.build()\n\t\t\tresult += ContentProviderOperation.newInsert(uri(authority, TABLE_MANGA_TAGS))\n\t\t\t\t.withValues(\n\t\t\t\t\tbuildContentValues(2) {\n\t\t\t\t\t\tput(\"manga_id\", manga.id)\n\t\t\t\t\t\tput(\"tag_id\", tag.id)\n\t\t\t\t\t},\n\t\t\t\t).build()\n\t\t}\n\t\tresult.add(\n\t\t\t0,\n\t\t\tContentProviderOperation.newInsert(uri(authority, TABLE_MANGA))\n\t\t\t\t.withValues(manga.toContentValues())\n\t\t\t\t.build(),\n\t\t)\n\t\treturn result\n\t}\n\n\tprivate fun getHistory(): List<HistorySyncDto> {\n\t\treturn provider.query(authorityHistory, TABLE_HISTORY).use { cursor ->\n\t\t\tval result = ArrayList<HistorySyncDto>(cursor.count)\n\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\tdo {\n\t\t\t\t\tval mangaId = cursor.getLong(cursor.getColumnIndexOrThrow(\"manga_id\"))\n\t\t\t\t\tresult.add(HistorySyncDto(cursor, getManga(authorityHistory, mangaId)))\n\t\t\t\t} while (cursor.moveToNext())\n\t\t\t}\n\t\t\tresult\n\t\t}\n\t}\n\n\tprivate fun getFavourites(): List<FavouriteSyncDto> {\n\t\treturn provider.query(authorityFavourites, TABLE_FAVOURITES).map { cursor ->\n\t\t\tval manga = getManga(authorityFavourites, cursor.getLong(cursor.getColumnIndexOrThrow(\"manga_id\")))\n\t\t\tFavouriteSyncDto(cursor, manga)\n\t\t}\n\t}\n\n\tprivate fun getFavouriteCategories(): List<FavouriteCategorySyncDto> =\n\t\tprovider.query(authorityFavourites, TABLE_FAVOURITE_CATEGORIES).map { cursor ->\n\t\t\tFavouriteCategorySyncDto(cursor)\n\t\t}\n\n\tprivate fun getManga(authority: String, id: Long): MangaSyncDto {\n\t\tval tags = requireNotNull(\n\t\t\tprovider.query(\n\t\t\t\turi(authority, TABLE_MANGA_TAGS),\n\t\t\t\tarrayOf(\"tag_id\"),\n\t\t\t\t\"manga_id = ?\",\n\t\t\t\tarrayOf(id.toString()),\n\t\t\t\tnull,\n\t\t\t)?.mapToSet {\n\t\t\t\tval tagId = it.getLong(it.getColumnIndexOrThrow(\"tag_id\"))\n\t\t\t\tgetTag(authority, tagId)\n\t\t\t},\n\t\t)\n\t\treturn requireNotNull(\n\t\t\tprovider.query(\n\t\t\t\turi(authority, TABLE_MANGA),\n\t\t\t\tnull,\n\t\t\t\t\"manga_id = ?\",\n\t\t\t\tarrayOf(id.toString()),\n\t\t\t\tnull,\n\t\t\t)?.use { cursor ->\n\t\t\t\tcursor.moveToFirst()\n\t\t\t\tMangaSyncDto(cursor, tags)\n\t\t\t},\n\t\t)\n\t}\n\n\tprivate fun getTag(authority: String, tagId: Long): MangaTagSyncDto = requireNotNull(\n\t\tprovider.query(\n\t\t\turi(authority, TABLE_TAGS),\n\t\t\tnull,\n\t\t\t\"tag_id = ?\",\n\t\t\tarrayOf(tagId.toString()),\n\t\t\tnull,\n\t\t)?.use { cursor ->\n\t\t\tif (cursor.moveToFirst()) {\n\t\t\t\tMangaTagSyncDto(cursor)\n\t\t\t} else {\n\t\t\t\tnull\n\t\t\t}\n\t\t},\n\t)\n\n\tprivate fun gcFavourites() {\n\t\tval deletedAt = System.currentTimeMillis() - defaultGcPeriod\n\t\tval selection = \"deleted_at != 0 AND deleted_at < ?\"\n\t\tval args = arrayOf(deletedAt.toString())\n\t\tprovider.delete(uri(authorityFavourites, TABLE_FAVOURITES), selection, args)\n\t\tprovider.delete(uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES), selection, args)\n\t}\n\n\tprivate fun gcHistory() {\n\t\tval deletedAt = System.currentTimeMillis() - defaultGcPeriod\n\t\tval selection = \"deleted_at != 0 AND deleted_at < ?\"\n\t\tval args = arrayOf(deletedAt.toString())\n\t\tprovider.delete(uri(authorityHistory, TABLE_HISTORY), selection, args)\n\t}\n\n\tprivate fun ContentProviderClient.query(authority: String, table: String): Cursor {\n\t\tval uri = uri(authority, table)\n\t\treturn query(uri, null, null, null, null)\n\t\t\t?: throw OperationApplicationException(\"Query failed: $uri\")\n\t}\n\n\tprivate fun uri(authority: String, table: String) = \"content://$authority/$table\".toUri()\n\n\tprivate fun Response.parseDtoOrNull(): SyncDto? = use {\n\t\twhen {\n\t\t\t!isSuccessful -> throw IOException(body.string())\n\t\t\tcode == HttpURLConnection.HTTP_NO_CONTENT -> null\n\t\t\telse -> Json.decodeFromString<SyncDto>(body.string())\n\t\t}\n\t}\n\n\t@AssistedFactory\n\tinterface Factory {\n\n\t\tfun create(\n\t\t\taccount: Account,\n\t\t\tcontentProviderClient: ContentProviderClient,\n\t\t): SyncHelper\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAccountAuthenticator.kt",
    "content": "package org.koitharu.kotatsu.sync.ui\n\nimport android.accounts.AbstractAccountAuthenticator\nimport android.accounts.Account\nimport android.accounts.AccountAuthenticatorResponse\nimport android.accounts.AccountManager\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.text.TextUtils\n\nclass SyncAccountAuthenticator(private val context: Context) : AbstractAccountAuthenticator(context) {\n\n\toverride fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?): Bundle? = null\n\n\toverride fun addAccount(\n\t\tresponse: AccountAuthenticatorResponse?,\n\t\taccountType: String?,\n\t\tauthTokenType: String?,\n\t\trequiredFeatures: Array<out String>?,\n\t\toptions: Bundle?,\n\t): Bundle {\n\t\tval intent = Intent(context, SyncAuthActivity::class.java)\n\t\tintent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)\n\t\tval bundle = Bundle()\n\t\tif (options != null) {\n\t\t\tbundle.putAll(options)\n\t\t}\n\t\tbundle.putParcelable(AccountManager.KEY_INTENT, intent)\n\t\treturn bundle\n\t}\n\n\toverride fun confirmCredentials(\n\t\tresponse: AccountAuthenticatorResponse?,\n\t\taccount: Account?,\n\t\toptions: Bundle?,\n\t): Bundle? = null\n\n\toverride fun getAuthToken(\n\t\tresponse: AccountAuthenticatorResponse?,\n\t\taccount: Account,\n\t\tauthTokenType: String?,\n\t\toptions: Bundle?,\n\t): Bundle {\n\t\tval result = Bundle()\n\t\tval am = AccountManager.get(context.applicationContext)\n\t\tval authToken = am.peekAuthToken(account, authTokenType)\n\t\tif (!TextUtils.isEmpty(authToken)) {\n\t\t\tresult.putString(AccountManager.KEY_ACCOUNT_NAME, account.name)\n\t\t\tresult.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type)\n\t\t\tresult.putString(AccountManager.KEY_AUTHTOKEN, authToken)\n\t\t} else {\n\t\t\tval intent = Intent(context, SyncAuthActivity::class.java)\n\t\t\tintent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)\n\t\t\tval bundle = Bundle()\n\t\t\tif (options != null) {\n\t\t\t\tbundle.putAll(options)\n\t\t\t}\n\t\t\tbundle.putParcelable(AccountManager.KEY_INTENT, intent)\n\t\t}\n\t\treturn result\n\t}\n\n\toverride fun getAuthTokenLabel(authTokenType: String?): String? = null\n\n\toverride fun updateCredentials(\n\t\tresponse: AccountAuthenticatorResponse?,\n\t\taccount: Account?,\n\t\tauthTokenType: String?,\n\t\toptions: Bundle?,\n\t): Bundle? = null\n\n\toverride fun hasFeatures(\n\t\tresponse: AccountAuthenticatorResponse?,\n\t\taccount: Account?,\n\t\tfeatures: Array<out String>?,\n\t): Bundle? = null\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAdapterEntryPoint.kt",
    "content": "package org.koitharu.kotatsu.sync.ui\n\nimport dagger.hilt.EntryPoint\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport org.koitharu.kotatsu.sync.domain.SyncHelper\n\n@EntryPoint\n@InstallIn(SingletonComponent::class)\ninterface SyncAdapterEntryPoint {\n\tval syncHelperFactory: SyncHelper.Factory\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt",
    "content": "package org.koitharu.kotatsu.sync.ui\n\nimport android.accounts.Account\nimport android.accounts.AccountAuthenticatorResponse\nimport android.accounts.AccountManager\nimport android.os.Bundle\nimport android.text.Editable\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.widget.Toast\nimport androidx.activity.OnBackPressedCallback\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.FragmentResultListener\nimport androidx.transition.TransitionManager\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.transition.MaterialSharedAxis\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.getDisplayMessage\nimport org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivitySyncAuthBinding\nimport org.koitharu.kotatsu.sync.data.SyncSettings\nimport org.koitharu.kotatsu.sync.domain.SyncAuthResult\n\nprivate const val PAGE_EMAIL = 0\nprivate const val PAGE_PASSWORD = 1\nprivate const val PASSWORD_MIN_LENGTH = 4\n\n@AndroidEntryPoint\nclass SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickListener, FragmentResultListener,\n\tDefaultTextWatcher {\n\n\tprivate var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null\n\tprivate var resultBundle: Bundle? = null\n\tprivate val pageBackCallback = PageBackCallback()\n\n\tprivate val regexEmail = Regex(\"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\\\.[A-Z]{2,6}$\", RegexOption.IGNORE_CASE)\n\n\tprivate val viewModel by viewModels<SyncAuthViewModel>()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivitySyncAuthBinding.inflate(layoutInflater))\n\t\taccountAuthenticatorResponse =\n\t\t\tintent.getParcelableExtraCompat(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)\n\t\taccountAuthenticatorResponse?.onRequestContinued()\n\t\tviewBinding.buttonNext.setOnClickListener(this)\n\t\tviewBinding.buttonBack.setOnClickListener(this)\n\t\tviewBinding.buttonCancel.setOnClickListener(this)\n\t\tviewBinding.buttonDone.setOnClickListener(this)\n\t\tviewBinding.buttonSettings.setOnClickListener(this)\n\t\tviewBinding.editEmail.addTextChangedListener(this)\n\t\tviewBinding.editPassword.addTextChangedListener(this)\n\n\t\tonBackPressedDispatcher.addCallback(pageBackCallback)\n\n\t\tviewModel.onTokenObtained.observeEvent(this, ::onTokenReceived)\n\t\tviewModel.onError.observeEvent(this, ::onError)\n\t\tviewModel.isLoading.observe(this, ::onLoadingStateChanged)\n\t\tviewModel.onAccountAlreadyExists.observeEvent(this) {\n\t\t\tonAccountAlreadyExists()\n\t\t}\n\n\t\tsupportFragmentManager.setFragmentResultListener(SyncHostDialogFragment.REQUEST_KEY, this, this)\n\t\tif (savedInstanceState == null) {\n\t\t\tsetPage(PAGE_EMAIL)\n\t\t} else {\n\t\t\tpageBackCallback.update()\n\t\t}\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.root.updatePadding(top = barsInsets.top)\n\t\tviewBinding.dockedToolbarChild.updateLayoutParams<MarginLayoutParams> {\n\t\t\tleftMargin = barsInsets.left\n\t\t\trightMargin = barsInsets.right\n\t\t\tbottomMargin = barsInsets.bottom\n\t\t}\n\t\tval basePadding = viewBinding.layoutContent.paddingBottom\n\t\tviewBinding.layoutContent.updatePadding(\n\t\t\tleft = barsInsets.left + basePadding,\n\t\t\tright = barsInsets.right + basePadding,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_cancel -> {\n\t\t\t\tsetResult(RESULT_CANCELED)\n\t\t\t\tfinish()\n\t\t\t}\n\n\t\t\tR.id.button_next -> {\n\t\t\t\tsetPage(PAGE_PASSWORD)\n\t\t\t\tviewBinding.editPassword.requestFocus()\n\t\t\t}\n\n\t\t\tR.id.button_back -> {\n\t\t\t\tsetPage(PAGE_EMAIL)\n\t\t\t\tviewBinding.editEmail.requestFocus()\n\t\t\t}\n\n\t\t\tR.id.button_done -> {\n\t\t\t\tviewModel.obtainToken(\n\t\t\t\t\temail = viewBinding.editEmail.text.toString().trim(),\n\t\t\t\t\tpassword = viewBinding.editPassword.text.toString(),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tR.id.button_settings -> {\n\t\t\t\tSyncHostDialogFragment.show(supportFragmentManager, viewModel.syncURL.value)\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onFragmentResult(requestKey: String, result: Bundle) {\n\t\tval syncURL = result.getString(SyncHostDialogFragment.KEY_SYNC_URL) ?: return\n\t\tviewModel.syncURL.value = syncURL\n\t}\n\n\toverride fun finish() {\n\t\taccountAuthenticatorResponse?.let { response ->\n\t\t\tresultBundle?.also {\n\t\t\t\tresponse.onResult(it)\n\t\t\t} ?: response.onError(AccountManager.ERROR_CODE_CANCELED, getString(R.string.canceled))\n\t\t}\n\t\tsuper.finish()\n\t}\n\n\toverride fun afterTextChanged(s: Editable?) {\n\t\tval isLoading = viewModel.isLoading.value\n\t\tval email = viewBinding.editEmail.text?.trim()?.toString()\n\t\tval password = viewBinding.editPassword.text?.toString()\n\t\tviewBinding.buttonNext.isEnabled = !isLoading && !email.isNullOrEmpty() && regexEmail.matches(email)\n\t\tviewBinding.buttonDone.isEnabled = !isLoading && password != null && password.length >= PASSWORD_MIN_LENGTH\n\t}\n\n\tprivate fun onLoadingStateChanged(isLoading: Boolean) {\n\t\twith(viewBinding) {\n\t\t\tprogressBar.isInvisible = !isLoading\n\t\t\teditEmail.isEnabled = !isLoading\n\t\t\teditPassword.isEnabled = !isLoading\n\t\t}\n\t\tafterTextChanged(null)\n\t\tpageBackCallback.update()\n\t}\n\n\tprivate fun setPage(page: Int) {\n\t\twith(viewBinding) {\n\t\t\tval currentPage = if (layoutEmail.isVisible) PAGE_EMAIL else PAGE_PASSWORD\n\t\t\tif (currentPage != page) {\n\t\t\t\tval transition = MaterialSharedAxis(MaterialSharedAxis.X, page > currentPage)\n\t\t\t\tTransitionManager.beginDelayedTransition(layoutContent, transition)\n\t\t\t}\n\t\t\tbuttonNext.isVisible = page == PAGE_EMAIL\n\t\t\tbuttonBack.isVisible = page == PAGE_PASSWORD\n\t\t\tbuttonSettings.isVisible = page == PAGE_EMAIL\n\t\t\tbuttonDone.isVisible = page == PAGE_PASSWORD\n\t\t\tbuttonCancel.isVisible = page == PAGE_EMAIL\n\t\t\tlayoutEmail.isVisible = page == PAGE_EMAIL\n\t\t\tlayoutPassword.isVisible = page == PAGE_PASSWORD\n\t\t}\n\t\tpageBackCallback.update()\n\t}\n\n\tprivate fun onError(error: Throwable) {\n\t\tMaterialAlertDialogBuilder(this)\n\t\t\t.setTitle(R.string.error)\n\t\t\t.setMessage(error.getDisplayMessage(resources))\n\t\t\t.setNegativeButton(R.string.close, null)\n\t\t\t.show()\n\t}\n\n\tprivate fun onTokenReceived(authResult: SyncAuthResult) {\n\t\tval am = AccountManager.get(this)\n\t\tval account = Account(authResult.email, getString(R.string.account_type_sync))\n\t\tval userdata = Bundle(1)\n\t\tuserdata.putString(SyncSettings.KEY_SYNC_URL, authResult.syncURL)\n\t\tval result = Bundle()\n\t\tif (am.addAccountExplicitly(account, authResult.password, userdata)) {\n\t\t\tresult.putString(AccountManager.KEY_ACCOUNT_NAME, account.name)\n\t\t\tresult.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type)\n\t\t\tresult.putString(AccountManager.KEY_AUTHTOKEN, authResult.token)\n\t\t\tam.setAuthToken(account, account.type, authResult.token)\n\t\t} else {\n\t\t\tresult.putString(AccountManager.KEY_ERROR_MESSAGE, getString(R.string.account_already_exists))\n\t\t}\n\t\tresultBundle = result\n\t\tsetResult(RESULT_OK)\n\t\tfinish()\n\t}\n\n\tprivate fun onAccountAlreadyExists() {\n\t\tToast.makeText(this, R.string.account_already_exists, Toast.LENGTH_SHORT)\n\t\t\t.show()\n\t\taccountAuthenticatorResponse?.onError(\n\t\t\tAccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,\n\t\t\tgetString(R.string.account_already_exists),\n\t\t)\n\t\tsuper.finishAfterTransition()\n\t}\n\n\tprivate inner class PageBackCallback : OnBackPressedCallback(false) {\n\n\t\toverride fun handleOnBackPressed() {\n\t\t\tsetPage(PAGE_EMAIL)\n\t\t\tviewBinding.editEmail.requestFocus()\n\t\t\tupdate()\n\t\t}\n\n\t\tfun update() {\n\t\t\tisEnabled = !viewBinding.progressBar.isVisible && viewBinding.editPassword.isVisible\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt",
    "content": "package org.koitharu.kotatsu.sync.ui\n\nimport android.accounts.AccountManager\nimport android.content.Context\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.sync.data.SyncAuthApi\nimport org.koitharu.kotatsu.sync.domain.SyncAuthResult\nimport javax.inject.Inject\n\n@HiltViewModel\nclass SyncAuthViewModel @Inject constructor(\n\t@ApplicationContext context: Context,\n\tprivate val api: SyncAuthApi,\n) : BaseViewModel() {\n\n\tval onAccountAlreadyExists = MutableEventFlow<Unit>()\n\tval onTokenObtained = MutableEventFlow<SyncAuthResult>()\n\tval syncURL = MutableStateFlow(context.resources.getStringArray(R.array.sync_url_list).first())\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\tval am = AccountManager.get(context)\n\t\t\tval accounts = am.getAccountsByType(context.getString(R.string.account_type_sync))\n\t\t\tif (accounts.isNotEmpty()) {\n\t\t\t\tonAccountAlreadyExists.call(Unit)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun obtainToken(email: String, password: String) {\n\t\tval urlValue = syncURL.value\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\tval token = api.authenticate(urlValue, email, password)\n\t\t\tval result = SyncAuthResult(syncURL.value, email, password, token)\n\t\t\tonTokenObtained.call(result)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthenticatorService.kt",
    "content": "package org.koitharu.kotatsu.sync.ui\n\nimport android.app.Service\nimport android.content.Intent\nimport android.os.IBinder\n\nclass SyncAuthenticatorService : Service() {\n\n\tprivate lateinit var authenticator: SyncAccountAuthenticator\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tauthenticator = SyncAccountAuthenticator(this)\n\t}\n\n\toverride fun onBind(intent: Intent?): IBinder? {\n\t\treturn authenticator.iBinder\n\t}\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt",
    "content": "package org.koitharu.kotatsu.sync.ui\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.widget.ArrayAdapter\nimport androidx.core.os.bundleOf\nimport androidx.core.view.updateLayoutParams\nimport androidx.fragment.app.FragmentManager\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.AlertDialogFragment\nimport org.koitharu.kotatsu.core.util.ext.isHttpUrl\nimport org.koitharu.kotatsu.core.util.ext.withArgs\nimport org.koitharu.kotatsu.databinding.PreferenceDialogAutocompletetextviewBinding\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\nimport org.koitharu.kotatsu.settings.utils.validation.UrlValidator\nimport org.koitharu.kotatsu.sync.data.SyncSettings\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass SyncHostDialogFragment : AlertDialogFragment<PreferenceDialogAutocompletetextviewBinding>(),\n\tDialogInterface.OnClickListener {\n\n\t@Inject\n\tlateinit var syncSettings: SyncSettings\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?\n\t) = PreferenceDialogAutocompletetextviewBinding.inflate(inflater, container, false)\n\n\toverride fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {\n\t\treturn super.onBuildDialog(builder)\n\t\t\t.setPositiveButton(android.R.string.ok, this)\n\t\t\t.setNegativeButton(android.R.string.cancel, this)\n\t\t\t.setCancelable(false)\n\t\t\t.setTitle(R.string.server_address)\n\t}\n\n\toverride fun onViewBindingCreated(\n\t\tbinding: PreferenceDialogAutocompletetextviewBinding,\n\t\tsavedInstanceState: Bundle?\n\t) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tbinding.message.updateLayoutParams<MarginLayoutParams> {\n\t\t\ttopMargin = binding.root.resources.getDimensionPixelOffset(R.dimen.screen_padding)\n\t\t\tbottomMargin = topMargin\n\t\t}\n\t\tbinding.message.setText(R.string.sync_host_description)\n\t\tval entries = binding.root.resources.getStringArray(R.array.sync_url_list)\n\t\tval editText = binding.edit\n\t\teditText.setText(arguments?.getString(KEY_SYNC_URL).ifNullOrEmpty { syncSettings.syncUrl })\n\t\teditText.threshold = 0\n\t\teditText.setAdapter(ArrayAdapter(binding.root.context, android.R.layout.simple_spinner_dropdown_item, entries))\n\t\tbinding.dropdown.setOnClickListener {\n\t\t\teditText.showDropDown()\n\t\t}\n\t\tUrlValidator().attachToEditText(editText)\n\t}\n\n\toverride fun onClick(dialog: DialogInterface, which: Int) {\n\t\twhen (which) {\n\t\t\tDialogInterface.BUTTON_POSITIVE -> {\n\t\t\t\tval result = requireViewBinding().edit.text?.toString().orEmpty()\n\t\t\t\tvar scheme = \"\"\n\t\t\t\tif (!result.isHttpUrl()) {\n\t\t\t\t\tscheme = \"http://\"\n\t\t\t\t}\n\t\t\t\tsyncSettings.syncUrl = \"$scheme$result\"\n\t\t\t\tparentFragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(KEY_SYNC_URL to \"$scheme$result\"))\n\t\t\t}\n\t\t}\n\t\tdialog.dismiss()\n\t}\n\n\tcompanion object {\n\n\t\tprivate const val TAG = \"SyncHostDialogFragment\"\n\t\tconst val REQUEST_KEY = \"host\"\n\t\tconst val KEY_SYNC_URL = \"host\"\n\n\t\tfun show(fm: FragmentManager, syncURL: String?) = SyncHostDialogFragment().withArgs(1) {\n\t\t\tputString(KEY_SYNC_URL, syncURL)\n\t\t}.show(fm, TAG)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncProvider.kt",
    "content": "package org.koitharu.kotatsu.sync.ui\n\nimport android.content.ContentProvider\nimport android.content.ContentProviderOperation\nimport android.content.ContentProviderResult\nimport android.content.ContentValues\nimport android.database.Cursor\nimport android.database.sqlite.SQLiteDatabase\nimport android.net.Uri\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport androidx.sqlite.db.SupportSQLiteQueryBuilder\nimport dagger.hilt.EntryPoint\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.EntryPointAccessors\nimport dagger.hilt.components.SingletonComponent\nimport org.koitharu.kotatsu.core.db.*\nimport java.util.concurrent.Callable\n\nabstract class SyncProvider : ContentProvider() {\n\n\tprivate val entryPoint by lazy {\n\t\tEntryPointAccessors.fromApplication(checkNotNull(context), SyncProviderEntryPoint::class.java)\n\t}\n\tprivate val database by lazy { entryPoint.database }\n\n\tprivate val supportedTables = setOf(\n\t\tTABLE_FAVOURITES,\n\t\tTABLE_MANGA,\n\t\tTABLE_TAGS,\n\t\tTABLE_FAVOURITE_CATEGORIES,\n\t\tTABLE_HISTORY,\n\t\tTABLE_MANGA_TAGS,\n\t)\n\n\toverride fun onCreate(): Boolean {\n\t\treturn true\n\t}\n\n\toverride fun query(\n\t\turi: Uri,\n\t\tprojection: Array<out String>?,\n\t\tselection: String?,\n\t\tselectionArgs: Array<out String>?,\n\t\tsortOrder: String?,\n\t): Cursor? {\n\t\tval tableName = getTableName(uri) ?: return null\n\t\tval sqlQuery = SupportSQLiteQueryBuilder.builder(tableName)\n\t\t\t.columns(projection)\n\t\t\t.selection(selection, selectionArgs)\n\t\t\t.orderBy(sortOrder)\n\t\t\t.create()\n\t\treturn database.openHelper.readableDatabase.query(sqlQuery)\n\t}\n\n\toverride fun getType(uri: Uri): String? {\n\t\treturn getTableName(uri)?.let { \"vnd.android.cursor.dir/\" }\n\t}\n\n\toverride fun insert(uri: Uri, values: ContentValues?): Uri? {\n\t\tval table = getTableName(uri)\n\t\tif (values == null || table == null) {\n\t\t\treturn null\n\t\t}\n\t\tval db = database.openHelper.writableDatabase\n\t\tif (db.insert(table, SQLiteDatabase.CONFLICT_IGNORE, values) < 0) {\n\t\t\tdb.update(table, values)\n\t\t}\n\t\treturn uri\n\t}\n\n\toverride fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {\n\t\tval table = getTableName(uri) ?: return 0\n\t\treturn database.openHelper.writableDatabase.delete(table, selection, selectionArgs)\n\t}\n\n\toverride fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {\n\t\tval table = getTableName(uri)\n\t\tif (values == null || table == null) {\n\t\t\treturn 0\n\t\t}\n\t\treturn database.openHelper.writableDatabase\n\t\t\t.update(table, SQLiteDatabase.CONFLICT_IGNORE, values, selection, selectionArgs)\n\t}\n\n\toverride fun applyBatch(operations: ArrayList<ContentProviderOperation>): Array<ContentProviderResult> {\n\t\treturn runAtomicTransaction { super.applyBatch(operations) }\n\t}\n\n\toverride fun bulkInsert(uri: Uri, values: Array<out ContentValues>): Int {\n\t\treturn runAtomicTransaction { super.bulkInsert(uri, values) }\n\t}\n\n\tprivate fun getTableName(uri: Uri): String? {\n\t\treturn uri.pathSegments.singleOrNull()?.takeIf { it in supportedTables }\n\t}\n\n\tprivate fun <R> runAtomicTransaction(callable: Callable<R>): R {\n\t\treturn synchronized(database) {\n\t\t\tdatabase.runInTransaction(callable)\n\t\t}\n\t}\n\n\tprivate fun SupportSQLiteDatabase.update(table: String, values: ContentValues) {\n\t\tval keys = when (table) {\n\t\t\tTABLE_TAGS -> listOf(\"tag_id\")\n\t\t\tTABLE_MANGA_TAGS -> listOf(\"tag_id\", \"manga_id\")\n\t\t\tTABLE_MANGA -> listOf(\"manga_id\")\n\t\t\tTABLE_FAVOURITES -> listOf(\"manga_id\", \"category_id\")\n\t\t\tTABLE_FAVOURITE_CATEGORIES -> listOf(\"category_id\")\n\t\t\tTABLE_HISTORY -> listOf(\"manga_id\")\n\t\t\telse -> throw IllegalArgumentException(\"Update for $table is not supported\")\n\t\t}\n\t\tval whereClause = keys.joinToString(\" AND \") { \"`$it` = ?\" }\n\t\tval whereArgs = Array<Any>(keys.size) { i -> values.get(\"`${keys[i]}`\") ?: values.get(keys[i]) }\n\t\tthis.update(table, SQLiteDatabase.CONFLICT_IGNORE, values, whereClause, whereArgs)\n\t}\n\n\t@EntryPoint\n\t@InstallIn(SingletonComponent::class)\n\tinterface SyncProviderEntryPoint {\n\n\t\tval database: MangaDatabase\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt",
    "content": "package org.koitharu.kotatsu.sync.ui.favourites\n\nimport android.accounts.Account\nimport android.content.AbstractThreadedSyncAdapter\nimport android.content.ContentProviderClient\nimport android.content.Context\nimport android.content.SyncResult\nimport android.os.Bundle\nimport dagger.hilt.android.EntryPointAccessors\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.onError\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.sync.domain.SyncController\nimport org.koitharu.kotatsu.sync.ui.SyncAdapterEntryPoint\n\nclass FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {\n\n\toverride fun onPerformSync(\n\t\taccount: Account,\n\t\textras: Bundle,\n\t\tauthority: String,\n\t\tprovider: ContentProviderClient,\n\t\tsyncResult: SyncResult,\n\t) {\n\t\tif (!context.resources.getBoolean(R.bool.is_sync_enabled)) {\n\t\t\treturn\n\t\t}\n\t\tval entryPoint = EntryPointAccessors.fromApplication(context, SyncAdapterEntryPoint::class.java)\n\t\tval syncHelper = entryPoint.syncHelperFactory.create(account, provider)\n\t\trunCatchingCancellable {\n\t\t\tsyncHelper.syncFavourites(syncResult.stats)\n\t\t\tSyncController.setLastSync(context, account, authority, System.currentTimeMillis())\n\t\t}.onFailure { e ->\n\t\t\tsyncResult.onError(e)\n\t\t\tsyncHelper.onError(e)\n\t\t}\n\t\tsyncHelper.onSyncComplete(syncResult)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncProvider.kt",
    "content": "package org.koitharu.kotatsu.sync.ui.favourites\n\nimport org.koitharu.kotatsu.sync.ui.SyncProvider\n\nclass FavouritesSyncProvider : SyncProvider()"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncService.kt",
    "content": "package org.koitharu.kotatsu.sync.ui.favourites\n\nimport android.app.Service\nimport android.content.Intent\nimport android.os.IBinder\n\nclass FavouritesSyncService : Service() {\n\n\tprivate lateinit var syncAdapter: FavouritesSyncAdapter\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tsyncAdapter = FavouritesSyncAdapter(applicationContext)\n\t}\n\n\toverride fun onBind(intent: Intent?): IBinder {\n\t\treturn syncAdapter.syncAdapterBinder\n\t}\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt",
    "content": "package org.koitharu.kotatsu.sync.ui.history\n\nimport android.accounts.Account\nimport android.content.AbstractThreadedSyncAdapter\nimport android.content.ContentProviderClient\nimport android.content.Context\nimport android.content.SyncResult\nimport android.os.Bundle\nimport dagger.hilt.android.EntryPointAccessors\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.util.ext.onError\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.sync.domain.SyncController\nimport org.koitharu.kotatsu.sync.ui.SyncAdapterEntryPoint\n\nclass HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {\n\n\toverride fun onPerformSync(\n\t\taccount: Account,\n\t\textras: Bundle,\n\t\tauthority: String,\n\t\tprovider: ContentProviderClient,\n\t\tsyncResult: SyncResult,\n\t) {\n\t\tif (!context.resources.getBoolean(R.bool.is_sync_enabled)) {\n\t\t\treturn\n\t\t}\n\t\tval entryPoint = EntryPointAccessors.fromApplication(context, SyncAdapterEntryPoint::class.java)\n\t\tval syncHelper = entryPoint.syncHelperFactory.create(account, provider)\n\t\trunCatchingCancellable {\n\t\t\tsyncHelper.syncHistory(syncResult.stats)\n\t\t\tSyncController.setLastSync(context, account, authority, System.currentTimeMillis())\n\t\t}.onFailure { e ->\n\t\t\tsyncResult.onError(e)\n\t\t\tsyncHelper.onError(e)\n\t\t}\n\t\tsyncHelper.onSyncComplete(syncResult)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/history/HistorySyncProvider.kt",
    "content": "package org.koitharu.kotatsu.sync.ui.history\n\nimport org.koitharu.kotatsu.sync.ui.SyncProvider\n\nclass HistorySyncProvider : SyncProvider()"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/history/HistorySyncService.kt",
    "content": "package org.koitharu.kotatsu.sync.ui.history\n\nimport android.app.Service\nimport android.content.Intent\nimport android.os.IBinder\n\nclass HistorySyncService : Service() {\n\n\tprivate lateinit var syncAdapter: HistorySyncAdapter\n\n\toverride fun onCreate() {\n\t\tsuper.onCreate()\n\t\tsyncAdapter = HistorySyncAdapter(applicationContext)\n\t}\n\n\toverride fun onBind(intent: Intent?): IBinder {\n\t\treturn syncAdapter.syncAdapterBinder\n\t}\n}"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/EntityMapping.kt",
    "content": "package org.koitharu.kotatsu.tracker.data\n\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem\nimport java.time.Instant\n\nfun TrackLogWithManga.toTrackingLogItem(): TrackingLogItem {\n\tval chaptersList = trackLog.chapters.split('\\n').filterNot { x -> x.isEmpty() }\n\treturn TrackingLogItem(\n\t\tid = trackLog.id,\n\t\tchapters = chaptersList,\n\t\tmanga = manga.toManga(tags.toMangaTags(), null),\n\t\tcreatedAt = Instant.ofEpochMilli(trackLog.createdAt),\n\t\tisNew = trackLog.isUnread,\n\t)\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/MangaWithTrack.kt",
    "content": "package org.koitharu.kotatsu.tracker.data\n\nimport androidx.room.Embedded\nimport androidx.room.Junction\nimport androidx.room.Relation\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaTagsEntity\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\nclass MangaWithTrack(\n\t@Embedded val track: TrackEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"manga_id\",\n\t)\n\tval manga: MangaEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"tag_id\",\n\t\tassociateBy = Junction(MangaTagsEntity::class),\n\t)\n\tval tags: List<TagEntity>,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackEntity.kt",
    "content": "package org.koitharu.kotatsu.tracker.data\n\nimport androidx.annotation.IntDef\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\n@Entity(\n\ttableName = \"tracks\",\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t],\n)\nclass TrackEntity(\n\t@PrimaryKey(autoGenerate = false)\n\t@ColumnInfo(name = \"manga_id\") val mangaId: Long,\n\t@ColumnInfo(name = \"last_chapter_id\") val lastChapterId: Long,\n\t@ColumnInfo(name = \"chapters_new\") val newChapters: Int,\n\t@ColumnInfo(name = \"last_check_time\") val lastCheckTime: Long,\n\t@ColumnInfo(name = \"last_chapter_date\") val lastChapterDate: Long,\n\t@TrackerResult\n\t@ColumnInfo(name = \"last_result\") val lastResult: Int,\n\t@ColumnInfo(name = \"last_error\") val lastError: String?,\n) {\n\n\t@IntDef(RESULT_NONE, RESULT_HAS_UPDATE, RESULT_NO_UPDATE, RESULT_FAILED, RESULT_EXTERNAL_MODIFICATION)\n\t@Retention(AnnotationRetention.SOURCE)\n\tannotation class TrackerResult\n\n\tcompanion object {\n\n\t\tconst val RESULT_NONE = 0\n\t\tconst val RESULT_HAS_UPDATE = 1\n\t\tconst val RESULT_NO_UPDATE = 2\n\t\tconst val RESULT_FAILED = 3\n\t\tconst val RESULT_EXTERNAL_MODIFICATION = 4\n\n\t\tfun create(mangaId: Long) = TrackEntity(\n\t\t\tmangaId = mangaId,\n\t\t\tlastChapterId = 0L,\n\t\t\tnewChapters = 0,\n\t\t\tlastCheckTime = 0L,\n\t\t\tlastChapterDate = 0,\n\t\t\tlastResult = RESULT_NONE,\n\t\t\tlastError = null,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackLogEntity.kt",
    "content": "package org.koitharu.kotatsu.tracker.data\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.PrimaryKey\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\n@Entity(\n\ttableName = \"track_logs\",\n\tforeignKeys = [\n\t\tForeignKey(\n\t\t\tentity = MangaEntity::class,\n\t\t\tparentColumns = [\"manga_id\"],\n\t\t\tchildColumns = [\"manga_id\"],\n\t\t\tonDelete = ForeignKey.CASCADE,\n\t\t),\n\t],\n)\nclass TrackLogEntity(\n\t@PrimaryKey(autoGenerate = true)\n\t@ColumnInfo(name = \"id\") val id: Long = 0L,\n\t@ColumnInfo(name = \"manga_id\", index = true) val mangaId: Long,\n\t@ColumnInfo(name = \"chapters\") val chapters: String,\n\t@ColumnInfo(name = \"created_at\") val createdAt: Long,\n\t@ColumnInfo(name = \"unread\") val isUnread: Boolean,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackLogWithManga.kt",
    "content": "package org.koitharu.kotatsu.tracker.data\n\nimport androidx.room.Embedded\nimport androidx.room.Junction\nimport androidx.room.Relation\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\nimport org.koitharu.kotatsu.core.db.entity.MangaTagsEntity\nimport org.koitharu.kotatsu.core.db.entity.TagEntity\n\nclass TrackLogWithManga(\n\t@Embedded val trackLog: TrackLogEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"manga_id\"\n\t)\n\tval manga: MangaEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"tag_id\",\n\t\tassociateBy = Junction(MangaTagsEntity::class)\n\t)\n\tval tags: List<TagEntity>,\n)"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackWithManga.kt",
    "content": "package org.koitharu.kotatsu.tracker.data\n\nimport androidx.room.Embedded\nimport androidx.room.Relation\nimport org.koitharu.kotatsu.core.db.entity.MangaEntity\n\nclass TrackWithManga(\n\t@Embedded val track: TrackEntity,\n\t@Relation(\n\t\tparentColumn = \"manga_id\",\n\t\tentityColumn = \"manga_id\",\n\t)\n\tval manga: MangaEntity,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt",
    "content": "package org.koitharu.kotatsu.tracker.data\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.RawQuery\nimport androidx.room.Transaction\nimport androidx.room.Upsert\nimport androidx.sqlite.db.SupportSQLiteQuery\nimport kotlinx.coroutines.flow.Flow\nimport org.koitharu.kotatsu.core.db.MangaQueryBuilder\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\n\n@Dao\nabstract class TracksDao : MangaQueryBuilder.ConditionCallback {\n\n\t@Transaction\n\t@Query(\"SELECT * FROM tracks ORDER BY last_check_time ASC LIMIT :limit OFFSET :offset\")\n\tabstract suspend fun findAll(offset: Int, limit: Int): List<TrackWithManga>\n\n\t@Transaction\n\t@Query(\"SELECT * FROM tracks ORDER BY last_check_time DESC\")\n\tabstract fun observeAll(): Flow<List<TrackWithManga>>\n\n\t@Query(\"SELECT manga_id FROM tracks\")\n\tabstract suspend fun findAllIds(): LongArray\n\n\t@Query(\"SELECT * FROM tracks WHERE manga_id = :mangaId\")\n\tabstract suspend fun find(mangaId: Long): TrackEntity?\n\n\t@Query(\"SELECT IFNULL(chapters_new,0) FROM tracks WHERE manga_id = :mangaId\")\n\tabstract suspend fun findNewChapters(mangaId: Long): Int\n\n\t@Query(\"SELECT COUNT(*) FROM tracks\")\n\tabstract suspend fun getTracksCount(): Int\n\n\t@Query(\"SELECT COUNT(*) FROM tracks WHERE chapters_new > 0\")\n\tabstract fun observeUpdateMangaCount(): Flow<Int>\n\n\t@Query(\"SELECT IFNULL(chapters_new, 0) FROM tracks WHERE manga_id = :mangaId\")\n\tabstract fun observeNewChapters(mangaId: Long): Flow<Int>\n\n\t@Transaction\n\t@Query(\"SELECT * FROM tracks WHERE chapters_new > 0 ORDER BY last_chapter_date DESC\")\n\tabstract fun observeUpdatedManga(): Flow<List<MangaWithTrack>>\n\n\tfun observeUpdatedManga(\n\t\tlimit: Int,\n\t\tfilterOptions: Set<ListFilterOption>,\n\t): Flow<List<MangaWithTrack>> = observeMangaImpl(\n\t\tMangaQueryBuilder(\"tracks\", this)\n\t\t\t.where(\"chapters_new > 0\")\n\t\t\t.filters(filterOptions)\n\t\t\t.limit(limit)\n\t\t\t.orderBy(\"last_chapter_date DESC\")\n\t\t\t.build(),\n\t)\n\n\t@Query(\"DELETE FROM tracks\")\n\tabstract suspend fun clear()\n\n\t@Query(\"UPDATE tracks SET chapters_new = 0\")\n\tabstract suspend fun clearCounters()\n\n\t@Query(\"UPDATE tracks SET chapters_new = 0 WHERE manga_id = :mangaId\")\n\tabstract suspend fun clearCounter(mangaId: Long)\n\n\t@Query(\"DELETE FROM tracks WHERE manga_id = :mangaId\")\n\tabstract suspend fun delete(mangaId: Long)\n\n\t@Query(\"DELETE FROM tracks WHERE manga_id NOT IN (SELECT manga_id FROM history WHERE history.deleted_at = 0 UNION SELECT manga_id FROM favourites WHERE favourites.deleted_at = 0 AND category_id IN (SELECT category_id FROM favourite_categories WHERE favourite_categories.deleted_at = 0 AND track = 1))\")\n\tabstract suspend fun gc()\n\n\t@Upsert\n\tabstract suspend fun upsert(entity: TrackEntity)\n\n\t@Transaction\n\t@RawQuery(observedEntities = [TrackEntity::class])\n\tprotected abstract fun observeMangaImpl(query: SupportSQLiteQuery): Flow<List<MangaWithTrack>>\n\n\toverride fun getCondition(option: ListFilterOption): String? = when (option) {\n\t\tListFilterOption.Macro.FAVORITE -> \"EXISTS(SELECT * FROM favourites WHERE favourites.manga_id = tracks.manga_id)\"\n\t\tis ListFilterOption.Favorite -> \"EXISTS(SELECT * FROM favourites WHERE favourites.manga_id = tracks.manga_id AND favourites.category_id = ${option.category.id})\"\n\t\tis ListFilterOption.Tag -> \"EXISTS(SELECT * FROM manga_tags WHERE manga_tags.manga_id = tracks.manga_id AND tag_id = ${option.tagId})\"\n\t\tListFilterOption.Macro.NSFW -> \"(SELECT nsfw FROM manga WHERE manga.manga_id = tracks.manga_id) = 1\"\n\t\telse -> null\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/CheckNewChaptersUseCase.kt",
    "content": "package org.koitharu.kotatsu.tracker.domain\n\nimport android.util.Log\nimport coil3.request.CachePolicy\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.core.model.getPreferredBranch\nimport org.koitharu.kotatsu.core.model.isLocal\nimport org.koitharu.kotatsu.core.parser.CachingMangaRepository\nimport org.koitharu.kotatsu.core.parser.MangaRepository\nimport org.koitharu.kotatsu.core.util.MultiMutex\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.toInstantOrNull\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.findById\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.tracker.domain.model.MangaTracking\nimport org.koitharu.kotatsu.tracker.domain.model.MangaUpdates\nimport java.time.Instant\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass CheckNewChaptersUseCase @Inject constructor(\n\tprivate val repository: TrackingRepository,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val mangaRepositoryFactory: MangaRepository.Factory,\n\tprivate val localMangaRepository: LocalMangaRepository,\n) {\n\n\tprivate val mutex = MultiMutex<Long>()\n\n\tsuspend operator fun invoke(manga: Manga): MangaUpdates = mutex.withLock(manga.id) {\n\t\trepository.updateTracks()\n\t\tval tracking = repository.getTrackOrNull(manga) ?: return@withLock MangaUpdates.Failure(\n\t\t\tmanga = manga,\n\t\t\terror = null,\n\t\t)\n\t\tinvokeImpl(tracking)\n\t}\n\n\tsuspend operator fun invoke(track: MangaTracking): MangaUpdates = mutex.withLock(track.manga.id) {\n\t\tinvokeImpl(track)\n\t}\n\n\tsuspend operator fun invoke(manga: Manga, currentChapterId: Long) = mutex.withLock(manga.id) {\n\t\trunCatchingCancellable {\n\t\t\trepository.updateTracks()\n\t\t\tval details = getFullManga(manga)\n\t\t\tval track = repository.getTrackOrNull(manga) ?: return@withLock\n\t\t\tval branch = checkNotNull(details.chapters?.findById(currentChapterId)).branch\n\t\t\tval chapters = details.getChapters(branch)\n\t\t\tval chapterIndex = chapters.indexOfFirst { x -> x.id == currentChapterId }\n\t\t\tval lastNewChapterIndex = chapters.size - track.newChapters\n\t\t\tval lastChapter = chapters.lastOrNull()\n\t\t\tval tracking = MangaTracking(\n\t\t\t\tmanga = details,\n\t\t\t\tlastChapterId = lastChapter?.id ?: 0L,\n\t\t\t\tlastCheck = Instant.now(),\n\t\t\t\tlastChapterDate = lastChapter?.uploadDate?.toInstantOrNull() ?: track.lastChapterDate,\n\t\t\t\tnewChapters = when {\n\t\t\t\t\ttrack.newChapters == 0 -> 0\n\t\t\t\t\tchapterIndex < 0 -> track.newChapters\n\t\t\t\t\tchapterIndex >= lastNewChapterIndex -> chapters.lastIndex - chapterIndex\n\t\t\t\t\telse -> track.newChapters\n\t\t\t\t},\n\t\t\t)\n\t\t\trepository.mergeWith(tracking)\n\t\t}.onFailure { e ->\n\t\t\te.printStackTraceDebug()\n\t\t}.isSuccess\n\t}\n\n\tprivate suspend fun invokeImpl(track: MangaTracking): MangaUpdates = runCatchingCancellable {\n\t\tval details = getFullManga(track.manga)\n\t\tcompare(track, details, getBranch(details, track.lastChapterId))\n\t}.getOrElse { error ->\n\t\tMangaUpdates.Failure(\n\t\t\tmanga = track.manga,\n\t\t\terror = error,\n\t\t)\n\t}.also { updates ->\n\t\trepository.saveUpdates(updates)\n\t}\n\n\tprivate suspend fun getBranch(manga: Manga, trackChapterId: Long): String? {\n\t\thistoryRepository.getOne(manga)?.let {\n\t\t\tmanga.chapters?.findById(it.chapterId)\n\t\t}?.let {\n\t\t\treturn it.branch\n\t\t}\n\t\tmanga.chapters?.findById(trackChapterId)?.let {\n\t\t\treturn it.branch\n\t\t}\n\t\t// fallback\n\t\treturn manga.getPreferredBranch(null)\n\t}\n\n\tprivate suspend fun getFullManga(manga: Manga): Manga = when {\n\t\tmanga.isLocal -> fetchDetails(\n\t\t\trequireNotNull(localMangaRepository.getRemoteManga(manga)) {\n\t\t\t\t\"Local manga is not supported\"\n\t\t\t},\n\t\t)\n\n\t\tmanga.chapters.isNullOrEmpty() -> fetchDetails(manga)\n\t\telse -> manga\n\t}\n\n\tprivate suspend fun fetchDetails(manga: Manga): Manga {\n\t\tval repo = mangaRepositoryFactory.create(manga.source)\n\t\treturn if (repo is CachingMangaRepository) {\n\t\t\trepo.getDetails(manga, CachePolicy.WRITE_ONLY)\n\t\t} else {\n\t\t\trepo.getDetails(manga)\n\t\t}\n\t}\n\n\t/**\n\t * The main functionality of tracker: check new chapters in [manga] comparing to the [track]\n\t */\n\tprivate fun compare(track: MangaTracking, manga: Manga, branch: String?): MangaUpdates.Success {\n\t\tif (track.isEmpty()) {\n\t\t\t// first check or manga was empty on last check\n\t\t\treturn MangaUpdates.Success(manga, branch, emptyList(), isValid = false)\n\t\t}\n\t\tval chapters = requireNotNull(manga.getChapters(branch))\n\t\tif (BuildConfig.DEBUG && chapters.findById(track.lastChapterId) == null) {\n\t\t\tLog.e(\"Tracker\", \"Chapter ${track.lastChapterId} not found\")\n\t\t}\n\t\tval newChapters = chapters.takeLastWhile { x -> x.id != track.lastChapterId }\n\t\treturn when {\n\t\t\tnewChapters.isEmpty() -> {\n\t\t\t\tMangaUpdates.Success(\n\t\t\t\t\tmanga = manga,\n\t\t\t\t\tbranch = branch,\n\t\t\t\t\tnewChapters = emptyList(),\n\t\t\t\t\tisValid = chapters.lastOrNull()?.id == track.lastChapterId,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tnewChapters.size == chapters.size -> {\n\t\t\t\tMangaUpdates.Success(manga, branch, emptyList(), isValid = false)\n\t\t\t}\n\n\t\t\telse -> {\n\t\t\t\tMangaUpdates.Success(manga, branch, newChapters, isValid = true)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/GetTracksUseCase.kt",
    "content": "package org.koitharu.kotatsu.tracker.domain\n\nimport org.koitharu.kotatsu.tracker.domain.model.MangaTracking\nimport javax.inject.Inject\n\nclass GetTracksUseCase @Inject constructor(\n\tprivate val repository: TrackingRepository,\n) {\n\n\tsuspend operator fun invoke(limit: Int): List<MangaTracking> {\n\t\trepository.updateTracks()\n\t\treturn repository.getTracks(offset = 0, limit = limit)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt",
    "content": "package org.koitharu.kotatsu.tracker.domain\n\nimport androidx.annotation.VisibleForTesting\nimport androidx.room.withTransaction\nimport dagger.Reusable\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.onStart\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.db.entity.toMangaTags\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.mapItems\nimport org.koitharu.kotatsu.core.util.ext.toInstantOrNull\nimport org.koitharu.kotatsu.details.domain.ProgressUpdateUseCase\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.ifZero\nimport org.koitharu.kotatsu.tracker.data.TrackEntity\nimport org.koitharu.kotatsu.tracker.data.TrackLogEntity\nimport org.koitharu.kotatsu.tracker.data.toTrackingLogItem\nimport org.koitharu.kotatsu.tracker.domain.model.MangaTracking\nimport org.koitharu.kotatsu.tracker.domain.model.MangaUpdates\nimport org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem\nimport java.util.concurrent.atomic.AtomicBoolean\nimport javax.inject.Inject\n\nprivate const val NO_ID = 0L\nprivate const val MAX_LOG_SIZE = 120\n\n@Reusable\nclass TrackingRepository @Inject constructor(\n\tprivate val db: MangaDatabase,\n\tprivate val settings: AppSettings,\n\tprivate val progressUpdateUseCase: ProgressUpdateUseCase,\n) {\n\n\tprivate var isGcCalled = AtomicBoolean(false)\n\n\tsuspend fun getNewChaptersCount(mangaId: Long): Int {\n\t\treturn db.getTracksDao().findNewChapters(mangaId)\n\t}\n\n\tfun observeNewChaptersCount(mangaId: Long): Flow<Int> {\n\t\treturn db.getTracksDao().observeNewChapters(mangaId)\n\t}\n\n\t@Deprecated(\"\")\n\tfun observeUpdatedMangaCount(): Flow<Int> {\n\t\treturn db.getTracksDao().observeUpdateMangaCount()\n\t\t\t.onStart { gcIfNotCalled() }\n\t}\n\n\tfun observeUnreadUpdatesCount(): Flow<Int> {\n\t\treturn db.getTrackLogsDao().observeUnreadCount()\n\t}\n\n\tfun observeUpdatedManga(limit: Int, filterOptions: Set<ListFilterOption>): Flow<List<MangaTracking>> {\n\t\treturn db.getTracksDao().observeUpdatedManga(limit, filterOptions)\n\t\t\t.mapItems {\n\t\t\t\tMangaTracking(\n\t\t\t\t\tmanga = it.manga.toManga(it.tags.toMangaTags(), null),\n\t\t\t\t\tlastChapterId = it.track.lastChapterId,\n\t\t\t\t\tlastCheck = it.track.lastCheckTime.toInstantOrNull(),\n\t\t\t\t\tlastChapterDate = it.track.lastChapterDate.toInstantOrNull(),\n\t\t\t\t\tnewChapters = it.track.newChapters,\n\t\t\t\t)\n\t\t\t}.distinctUntilChanged()\n\t\t\t.onStart { gcIfNotCalled() }\n\t}\n\n\tsuspend fun getTracks(offset: Int, limit: Int): List<MangaTracking> {\n\t\treturn db.getTracksDao().findAll(offset = offset, limit = limit).map {\n\t\t\tMangaTracking(\n\t\t\t\tmanga = it.manga.toManga(emptySet(), null),\n\t\t\t\tlastChapterId = it.track.lastChapterId,\n\t\t\t\tlastCheck = it.track.lastCheckTime.toInstantOrNull(),\n\t\t\t\tlastChapterDate = it.track.lastChapterDate.toInstantOrNull(),\n\t\t\t\tnewChapters = it.track.newChapters,\n\t\t\t)\n\t\t}\n\t}\n\n\t@Deprecated(\"\")\n\tsuspend fun getTrack(manga: Manga): MangaTracking {\n\t\treturn getTrackOrNull(manga) ?: MangaTracking(\n\t\t\tmanga = manga,\n\t\t\tlastChapterId = NO_ID,\n\t\t\tlastCheck = null,\n\t\t\tlastChapterDate = null,\n\t\t\tnewChapters = 0,\n\t\t)\n\t}\n\n\tsuspend fun getTrackOrNull(manga: Manga): MangaTracking? {\n\t\tval track = db.getTracksDao().find(manga.id) ?: return null\n\t\treturn MangaTracking(\n\t\t\tmanga = manga,\n\t\t\tlastChapterId = track.lastChapterId,\n\t\t\tlastCheck = track.lastCheckTime.toInstantOrNull(),\n\t\t\tlastChapterDate = track.lastChapterDate.toInstantOrNull(),\n\t\t\tnewChapters = track.newChapters,\n\t\t)\n\t}\n\n\t@VisibleForTesting\n\tsuspend fun deleteTrack(mangaId: Long) {\n\t\tdb.getTracksDao().delete(mangaId)\n\t}\n\n\tfun observeTrackingLog(limit: Int, filterOptions: Set<ListFilterOption>): Flow<List<TrackingLogItem>> {\n\t\treturn db.getTrackLogsDao().observeAll(limit, filterOptions)\n\t\t\t.mapItems { it.toTrackingLogItem() }\n\t\t\t.onStart { gcIfNotCalled() }\n\t}\n\n\tsuspend fun getLogsCount() = db.getTrackLogsDao().count()\n\n\tsuspend fun clearLogs() = db.getTrackLogsDao().clear()\n\n\tsuspend fun clearCounters() = db.getTracksDao().clearCounters()\n\n\tsuspend fun markAsRead(trackLogId: Long) = db.getTrackLogsDao().markAsRead(trackLogId)\n\n\tsuspend fun gc() = db.withTransaction {\n\t\tdb.getTracksDao().gc()\n\t\tdb.getTrackLogsDao().run {\n\t\t\tgc()\n\t\t\ttrim(MAX_LOG_SIZE)\n\t\t}\n\t}\n\n\tsuspend fun saveUpdates(updates: MangaUpdates) {\n\t\tdb.withTransaction {\n\t\t\tval track = getOrCreateTrack(updates.manga.id).mergeWith(updates)\n\t\t\tdb.getTracksDao().upsert(track)\n\t\t\tif (updates is MangaUpdates.Success && updates.isValid && updates.newChapters.isNotEmpty()) {\n\t\t\t\tprogressUpdateUseCase(updates.manga)\n\t\t\t\tval logEntity = TrackLogEntity(\n\t\t\t\t\tmangaId = updates.manga.id,\n\t\t\t\t\tchapters = updates.newChapters.joinToString(\"\\n\") { x -> x.name },\n\t\t\t\t\tcreatedAt = System.currentTimeMillis(),\n\t\t\t\t\tisUnread = true,\n\t\t\t\t)\n\t\t\t\tdb.getTrackLogsDao().insert(logEntity)\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun clearUpdates(ids: Collection<Long>) {\n\t\twhen {\n\t\t\tids.isEmpty() -> return\n\t\t\tids.size == 1 -> db.getTracksDao().clearCounter(ids.single())\n\t\t\telse -> db.withTransaction {\n\t\t\t\tfor (id in ids) {\n\t\t\t\t\tdb.getTracksDao().clearCounter(id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tsuspend fun mergeWith(tracking: MangaTracking) {\n\t\tval entity = TrackEntity(\n\t\t\tmangaId = tracking.manga.id,\n\t\t\tlastChapterId = tracking.lastChapterId,\n\t\t\tnewChapters = tracking.newChapters,\n\t\t\tlastCheckTime = tracking.lastCheck?.toEpochMilli() ?: 0L,\n\t\t\tlastChapterDate = tracking.lastChapterDate?.toEpochMilli() ?: 0L,\n\t\t\tlastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,\n\t\t\tlastError = null,\n\t\t)\n\t\tdb.getTracksDao().upsert(entity)\n\t}\n\n\tsuspend fun getCategoriesCount(): IntArray {\n\t\tval categories = db.getFavouriteCategoriesDao().findAll()\n\t\treturn intArrayOf(\n\t\t\tcategories.count { it.track },\n\t\t\tcategories.size,\n\t\t)\n\t}\n\n\tsuspend fun updateTracks() = db.withTransaction {\n\t\tval dao = db.getTracksDao()\n\t\tdao.gc()\n\t\tval ids = dao.findAllIds().toMutableSet()\n\t\tval size = ids.size\n\t\t// history\n\t\tif (AppSettings.TRACK_HISTORY in settings.trackSources) {\n\t\t\tval historyIds = db.getHistoryDao().findAllIds()\n\t\t\tfor (mangaId in historyIds) {\n\t\t\t\tif (!ids.remove(mangaId)) {\n\t\t\t\t\tdao.upsert(TrackEntity.create(mangaId))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// favorites\n\t\tif (AppSettings.TRACK_FAVOURITES in settings.trackSources) {\n\t\t\tval favoritesIds = db.getFavouritesDao().findIdsWithTrack()\n\t\t\tfor (mangaId in favoritesIds) {\n\t\t\t\tif (!ids.remove(mangaId)) {\n\t\t\t\t\tdao.upsert(TrackEntity.create(mangaId))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// remove unused\n\t\tfor (mangaId in ids) {\n\t\t\tdao.delete(mangaId)\n\t\t}\n\t\tsize - ids.size\n\t}\n\n\tprivate suspend fun getOrCreateTrack(mangaId: Long): TrackEntity {\n\t\treturn db.getTracksDao().find(mangaId) ?: TrackEntity.create(mangaId)\n\t}\n\n\tprivate fun TrackEntity.mergeWith(updates: MangaUpdates): TrackEntity {\n\t\treturn when (updates) {\n\t\t\tis MangaUpdates.Failure -> TrackEntity(\n\t\t\t\tmangaId = mangaId,\n\t\t\t\tlastChapterId = lastChapterId,\n\t\t\t\tnewChapters = newChapters,\n\t\t\t\tlastCheckTime = System.currentTimeMillis(),\n\t\t\t\tlastChapterDate = lastChapterDate,\n\t\t\t\tlastResult = TrackEntity.RESULT_FAILED,\n\t\t\t\tlastError = updates.error?.toString(),\n\t\t\t)\n\n\t\t\tis MangaUpdates.Success -> TrackEntity(\n\t\t\t\tmangaId = mangaId,\n\t\t\t\tlastChapterId = updates.manga.getChapters(updates.branch).lastOrNull()?.id ?: NO_ID,\n\t\t\t\tnewChapters = if (updates.isValid) newChapters + updates.newChapters.size else 0,\n\t\t\t\tlastCheckTime = System.currentTimeMillis(),\n\t\t\t\tlastChapterDate = updates.lastChapterDate().ifZero { lastChapterDate },\n\t\t\t\tlastResult = if (updates.isNotEmpty()) TrackEntity.RESULT_HAS_UPDATE else TrackEntity.RESULT_NO_UPDATE,\n\t\t\t\tlastError = null,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate suspend fun gcIfNotCalled() {\n\t\tif (isGcCalled.compareAndSet(false, true)) {\n\t\t\tgc()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/UpdatesListQuickFilter.kt",
    "content": "package org.koitharu.kotatsu.tracker.domain\n\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.MangaListQuickFilter\nimport javax.inject.Inject\n\nclass UpdatesListQuickFilter @Inject constructor(\n\tprivate val favouritesRepository: FavouritesRepository,\n\tsettings: AppSettings,\n) : MangaListQuickFilter(settings) {\n\n\toverride suspend fun getAvailableFilterOptions(): List<ListFilterOption> =\n\t\tfavouritesRepository.getMostUpdatedCategories(\n\t\t\tlimit = 4,\n\t\t).map {\n\t\t\tListFilterOption.Favorite(it)\n\t\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaTracking.kt",
    "content": "package org.koitharu.kotatsu.tracker.domain.model\n\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.time.Instant\n\ndata class MangaTracking(\n\tval manga: Manga,\n\tval lastChapterId: Long,\n\tval lastCheck: Instant?,\n\tval lastChapterDate: Instant?,\n\tval newChapters: Int,\n) {\n\n\tfun isEmpty(): Boolean {\n\t\treturn lastChapterId == 0L\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaUpdates.kt",
    "content": "package org.koitharu.kotatsu.tracker.domain.model\n\nimport org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport org.koitharu.kotatsu.parsers.util.ifZero\n\nsealed interface MangaUpdates {\n\n\tval manga: Manga\n\n\tdata class Success(\n\t\toverride val manga: Manga,\n\t\tval branch: String?,\n\t\tval newChapters: List<MangaChapter>,\n\t\tval isValid: Boolean,\n\t) : MangaUpdates {\n\n\t\tfun isNotEmpty() = newChapters.isNotEmpty()\n\n\t\tfun lastChapterDate(): Long {\n\t\t\tval lastChapter = newChapters.lastOrNull()\n\t\t\treturn lastChapter?.uploadDate?.ifZero { System.currentTimeMillis() }\n\t\t\t\t?: (manga.chapters?.lastOrNull()?.uploadDate ?: 0L)\n\t\t}\n\t}\n\n\tdata class Failure(\n\t\toverride val manga: Manga,\n\t\tval error: Throwable?,\n\t) : MangaUpdates {\n\n\t\tfun shouldRetry() = error is TooManyRequestExceptions\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/TrackingLogItem.kt",
    "content": "package org.koitharu.kotatsu.tracker.domain.model\n\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.time.Instant\n\ndata class TrackingLogItem(\n\tval id: Long,\n\tval manga: Manga,\n\tval chapters: List<String>,\n\tval createdAt: Instant,\n\tval isNew: Boolean,\n)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.debug\n\nimport android.graphics.Color\nimport android.text.format.DateUtils\nimport androidx.core.content.ContextCompat\nimport androidx.core.text.bold\nimport androidx.core.text.buildSpannedString\nimport androidx.core.text.color\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.getThemeColor\nimport org.koitharu.kotatsu.databinding.ItemTrackDebugBinding\nimport org.koitharu.kotatsu.tracker.data.TrackEntity\nimport androidx.appcompat.R as appcompatR\n\nfun trackDebugAD(\n\tclickListener: OnListItemClickListener<TrackDebugItem>,\n) = adapterDelegateViewBinding<TrackDebugItem, TrackDebugItem, ItemTrackDebugBinding>(\n\t{ layoutInflater, parent -> ItemTrackDebugBinding.inflate(layoutInflater, parent, false) },\n) {\n\tval indicatorNew = ContextCompat.getDrawable(context, R.drawable.ic_new)\n\n\titemView.setOnClickListener { v ->\n\t\tclickListener.onItemClick(item, v)\n\t}\n\n\tbind {\n\t\tbinding.imageViewCover.setImageAsync(item.manga.coverUrl, item.manga)\n\t\tbinding.textViewTitle.text = item.manga.title\n\t\tbinding.textViewSummary.text = buildSpannedString {\n\t\t\tappend(\n\t\t\t\titem.lastCheckTime?.let {\n\t\t\t\t\tDateUtils.getRelativeDateTimeString(\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t\tit.toEpochMilli(),\n\t\t\t\t\t\tDateUtils.MINUTE_IN_MILLIS,\n\t\t\t\t\t\tDateUtils.WEEK_IN_MILLIS,\n\t\t\t\t\t\t0,\n\t\t\t\t\t)\n\t\t\t\t} ?: getString(R.string.never),\n\t\t\t)\n\t\t\tif (item.lastResult == TrackEntity.RESULT_FAILED) {\n\t\t\t\tappend(\" - \")\n\t\t\t\tbold {\n\t\t\t\t\tcolor(context.getThemeColor(appcompatR.attr.colorError, Color.RED)) {\n\t\t\t\t\t\tappend(item.lastError ?: getString(R.string.error))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbinding.textViewTitle.drawableStart = if (item.newChapters > 0) {\n\t\t\tindicatorNew\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugItem.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.debug\n\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport java.time.Instant\n\ndata class TrackDebugItem(\n\tval manga: Manga,\n\tval lastChapterId: Long,\n\tval newChapters: Int,\n\tval lastCheckTime: Instant?,\n\tval lastChapterDate: Instant?,\n\tval lastResult: Int,\n\tval lastError: String?,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is TrackDebugItem && other.manga.id == manga.id\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.debug\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityTrackerDebugBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\n\n@AndroidEntryPoint\nclass TrackerDebugActivity : BaseActivity<ActivityTrackerDebugBinding>(), OnListItemClickListener<TrackDebugItem> {\n\n\tprivate val viewModel by viewModels<TrackerDebugViewModel>()\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityTrackerDebugBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)\n\t\tval tracksAdapter = BaseListAdapter<TrackDebugItem>()\n\t\t\t.addDelegate(ListItemType.FEED, trackDebugAD(this))\n\t\twith(viewBinding.recyclerView) {\n\t\t\tsetHasFixedSize(true)\n\t\t\tadapter = tracksAdapter\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, false))\n\t\t}\n\t\tviewModel.content.observe(this, tracksAdapter)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.recyclerView.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\ttop = barsInsets.top,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onItemClick(item: TrackDebugItem, view: View) {\n\t\trouter.openDetails(item.manga)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugViewModel.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.debug\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.db.entity.toManga\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.util.ext.toInstantOrNull\nimport org.koitharu.kotatsu.tracker.data.TrackWithManga\nimport javax.inject.Inject\n\n@HiltViewModel\nclass TrackerDebugViewModel @Inject constructor(\n\tdb: MangaDatabase\n) : BaseViewModel() {\n\n\tval content = db.getTracksDao().observeAll()\n\t\t.map { it.toUiList() }\n\t\t.withErrorHandling()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tprivate fun List<TrackWithManga>.toUiList(): List<TrackDebugItem> = map {\n\t\tTrackDebugItem(\n\t\t\tmanga = it.manga.toManga(emptySet(), null),\n\t\t\tlastChapterId = it.track.lastChapterId,\n\t\t\tnewChapters = it.track.newChapters,\n\t\t\tlastCheckTime = it.track.lastCheckTime.toInstantOrNull(),\n\t\t\tlastChapterDate = it.track.lastChapterDate.toInstantOrNull(),\n\t\t\tlastResult = it.track.lastResult,\n\t\t\tlastError = it.track.lastError,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.swiperefreshlayout.widget.SwipeRefreshLayout\nimport coil3.ImageLoader\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.flow.drop\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.nav.router\nimport org.koitharu.kotatsu.core.ui.BaseFragment\nimport org.koitharu.kotatsu.core.ui.list.PaginationScrollListener\nimport org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper\nimport org.koitharu.kotatsu.core.ui.util.MenuInvalidator\nimport org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner\nimport org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver\nimport org.koitharu.kotatsu.core.ui.widgets.TipView\nimport org.koitharu.kotatsu.core.util.ext.addMenuProvider\nimport org.koitharu.kotatsu.core.util.ext.consumeAll\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.databinding.FragmentListBinding\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListListener\nimport org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaTag\nimport org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass FeedFragment :\n\tBaseFragment<FragmentListBinding>(),\n\tPaginationScrollListener.Callback,\n\tRecyclerViewOwner,\n\tMangaListListener,\n\tSwipeRefreshLayout.OnRefreshListener {\n\n\t@Inject\n\tlateinit var coil: ImageLoader\n\n\tprivate val viewModel by viewModels<FeedViewModel>()\n\n\toverride val recyclerView: RecyclerView?\n\t\tget() = viewBinding?.recyclerView\n\n\toverride fun onCreateViewBinding(\n\t\tinflater: LayoutInflater,\n\t\tcontainer: ViewGroup?,\n\t) = FragmentListBinding.inflate(inflater, container, false)\n\n\toverride fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {\n\t\tsuper.onViewBindingCreated(binding, savedInstanceState)\n\t\tval sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width))\n\t\tval feedAdapter = FeedAdapter(this, sizeResolver) { item, v ->\n\t\t\tviewModel.onItemClick(item)\n\t\t\trouter.openDetails(item.toMangaWithOverride())\n\t\t}\n\t\twith(binding.recyclerView) {\n\t\t\tval paddingVertical = resources.getDimensionPixelSize(R.dimen.list_spacing_normal)\n\t\t\tsetPadding(0, paddingVertical, 0, paddingVertical)\n\t\t\tlayoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)\n\t\t\tadapter = feedAdapter\n\t\t\tsetHasFixedSize(true)\n\t\t\taddOnScrollListener(PaginationScrollListener(4, this@FeedFragment))\n\t\t\taddItemDecoration(TypedListSpacingDecoration(context, true))\n\t\t\tRecyclerScrollKeeper(this).attach()\n\t\t}\n\t\tbinding.swipeRefreshLayout.setOnRefreshListener(this)\n\t\taddMenuProvider(FeedMenuProvider(binding.recyclerView, viewModel))\n\n\t\tviewModel.isHeaderEnabled.drop(1).observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))\n\t\tviewModel.content.observe(viewLifecycleOwner, feedAdapter)\n\t\tviewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))\n\t\tviewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))\n\t\tviewModel.isRunning.observe(viewLifecycleOwner, this::onIsTrackerRunningChanged)\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval typeMask = WindowInsetsCompat.Type.systemBars()\n\t\tval barsInsets = insets.getInsets(typeMask)\n\t\tval paddingVertical = resources.getDimensionPixelSize(R.dimen.list_spacing_normal)\n\t\tviewBinding?.recyclerView?.setPadding(\n\t\t\tleft = barsInsets.left,\n\t\t\ttop = paddingVertical,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom + paddingVertical,\n\t\t)\n\t\treturn insets.consumeAll(typeMask)\n\t}\n\n\toverride fun onRefresh() {\n\t\tviewModel.update()\n\t}\n\n\toverride fun onFilterOptionClick(option: ListFilterOption) = viewModel.toggleFilterOption(option)\n\n\toverride fun onRetryClick(error: Throwable) = Unit\n\n\toverride fun onFilterClick(view: View?) = Unit\n\n\toverride fun onEmptyActionClick() = Unit\n\n\toverride fun onPrimaryButtonClick(tipView: TipView) = Unit\n\n\toverride fun onSecondaryButtonClick(tipView: TipView) = Unit\n\n\toverride fun onListHeaderClick(item: ListHeader, view: View) {\n\t\trouter.openMangaUpdates()\n\t}\n\n\tprivate fun onIsTrackerRunningChanged(isRunning: Boolean) {\n\t\trequireViewBinding().swipeRefreshLayout.isRefreshing = isRunning\n\t}\n\n\toverride fun onScrolledToEnd() {\n\t\tviewModel.requestMoreItems()\n\t}\n\n\toverride fun onItemClick(item: MangaListModel, view: View) {\n\t\trouter.openDetails(item.toMangaWithOverride())\n\t}\n\n\toverride fun onReadClick(manga: Manga, view: View) = Unit\n\n\toverride fun onTagClick(manga: Manga, tag: MangaTag, view: View) = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed\n\nimport android.content.Context\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.core.view.MenuProvider\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.dialog.RememberCheckListener\nimport org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog\nimport org.koitharu.kotatsu.core.ui.dialog.setCheckbox\n\nclass FeedMenuProvider(\n\tprivate val snackbarHost: View,\n\tprivate val viewModel: FeedViewModel,\n) : MenuProvider {\n\n\tprivate val context: Context\n\t\tget() = snackbarHost.context\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.opt_feed, menu)\n\t}\n\n\toverride fun onPrepareMenu(menu: Menu) {\n\t\tsuper.onPrepareMenu(menu)\n\t\tmenu.findItem(R.id.action_show_updated)?.isChecked = viewModel.isHeaderEnabled.value\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {\n\t\tR.id.action_update -> {\n\t\t\tviewModel.update()\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_show_updated -> {\n\t\t\tviewModel.setHeaderEnabled(!menuItem.isChecked)\n\t\t\ttrue\n\t\t}\n\n\t\tR.id.action_clear_feed -> {\n\t\t\tval checkListener = RememberCheckListener(true)\n\t\t\tbuildAlertDialog(context, isCentered = true) {\n\t\t\t\tsetIcon(R.drawable.ic_clear_all)\n\t\t\t\tsetTitle(R.string.clear_updates_feed)\n\t\t\t\tsetMessage(R.string.text_clear_updates_feed_prompt)\n\t\t\t\tsetNegativeButton(android.R.string.cancel, null)\n\t\t\t\tsetCheckbox(R.string.clear_new_chapters_counters, true, checkListener)\n\t\t\t\tsetPositiveButton(R.string.clear) { _, _ ->\n\t\t\t\t\tviewModel.clearFeed(checkListener.isChecked)\n\t\t\t\t}\n\t\t\t}.show()\n\t\t\ttrue\n\t\t}\n\n\t\telse -> false\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.prefs.observeAsStateFlow\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.core.ui.model.DateTimeAgo\nimport org.koitharu.kotatsu.core.ui.util.ReversibleAction\nimport org.koitharu.kotatsu.core.util.ext.MutableEventFlow\nimport org.koitharu.kotatsu.core.util.ext.calculateTimeAgo\nimport org.koitharu.kotatsu.core.util.ext.call\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.domain.QuickFilterListener\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.tracker.domain.TrackingRepository\nimport org.koitharu.kotatsu.tracker.domain.UpdatesListQuickFilter\nimport org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem\nimport org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem\nimport org.koitharu.kotatsu.tracker.ui.feed.model.UpdatedMangaHeader\nimport org.koitharu.kotatsu.tracker.work.TrackWorker\nimport java.util.concurrent.atomic.AtomicBoolean\nimport javax.inject.Inject\n\nprivate const val PAGE_SIZE = 20\n\n@HiltViewModel\nclass FeedViewModel @Inject constructor(\n\tprivate val settings: AppSettings,\n\tprivate val repository: TrackingRepository,\n\tprivate val scheduler: TrackWorker.Scheduler,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val quickFilter: UpdatesListQuickFilter,\n) : BaseViewModel(), QuickFilterListener by quickFilter {\n\n\tprivate val limit = MutableStateFlow(PAGE_SIZE)\n\tprivate val isReady = AtomicBoolean(false)\n\n\tval isRunning = scheduler.observeIsRunning()\n\t\t.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)\n\n\tval isHeaderEnabled = settings.observeAsStateFlow(\n\t\tscope = viewModelScope + Dispatchers.Default,\n\t\tkey = AppSettings.KEY_FEED_HEADER,\n\t\tvalueProducer = { isFeedHeaderVisible },\n\t)\n\n\tval onActionDone = MutableEventFlow<ReversibleAction>()\n\n\t@Suppress(\"USELESS_CAST\")\n\tval content = combine(\n\t\tobserveHeader(),\n\t\tquickFilter.appliedOptions,\n\t\tcombine(limit, quickFilter.appliedOptions.combineWithSettings(), ::Pair)\n\t\t\t.flatMapLatest { repository.observeTrackingLog(it.first, it.second) },\n\t) { header, filters, list ->\n\t\tval result = ArrayList<ListModel>((list.size * 1.4).toInt().coerceAtLeast(3))\n\t\tquickFilter.filterItem(filters)?.let(result::add)\n\t\tif (header != null) {\n\t\t\tresult += header\n\t\t}\n\t\tif (list.isEmpty()) {\n\t\t\tresult += EmptyState(\n\t\t\t\ticon = R.drawable.ic_empty_feed,\n\t\t\t\ttextPrimary = R.string.text_empty_holder_primary,\n\t\t\t\ttextSecondary = R.string.text_feed_holder,\n\t\t\t\tactionStringRes = 0,\n\t\t\t)\n\t\t} else {\n\t\t\tisReady.set(true)\n\t\t\tlist.mapListTo(result)\n\t\t}\n\t\tresult as List<ListModel>\n\t}.catch { e ->\n\t\temit(listOf(e.toErrorState(canRetry = false)))\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.gc()\n\t\t}\n\t}\n\n\tfun clearFeed(clearCounters: Boolean) {\n\t\tlaunchLoadingJob(Dispatchers.Default) {\n\t\t\trepository.clearLogs()\n\t\t\tif (clearCounters) {\n\t\t\t\trepository.clearCounters()\n\t\t\t}\n\t\t\tonActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null))\n\t\t}\n\t}\n\n\tfun requestMoreItems() {\n\t\tif (isReady.compareAndSet(true, false)) {\n\t\t\tlimit.value += PAGE_SIZE\n\t\t}\n\t}\n\n\tfun update() {\n\t\tscheduler.startNow()\n\t}\n\n\tfun setHeaderEnabled(value: Boolean) {\n\t\tsettings.isFeedHeaderVisible = value\n\t}\n\n\tfun onItemClick(item: FeedItem) {\n\t\tlaunchJob(Dispatchers.Default, CoroutineStart.ATOMIC) {\n\t\t\trepository.markAsRead(item.id)\n\t\t}\n\t}\n\n\tprivate suspend fun List<TrackingLogItem>.mapListTo(destination: MutableList<ListModel>) {\n\t\tvar prevDate: DateTimeAgo? = null\n\t\tfor (item in this) {\n\t\t\tval date = calculateTimeAgo(item.createdAt)\n\t\t\tif (prevDate != date) {\n\t\t\t\tdestination += if (date != null) {\n\t\t\t\t\tListHeader(date)\n\t\t\t\t} else {\n\t\t\t\t\tListHeader(R.string.unknown)\n\t\t\t\t}\n\t\t\t}\n\t\t\tprevDate = date\n\t\t\tdestination += mangaListMapper.toFeedItem(item)\n\t\t}\n\t}\n\n\tprivate fun observeHeader() = isHeaderEnabled.flatMapLatest { hasHeader ->\n\t\tif (hasHeader) {\n\t\t\tquickFilter.appliedOptions.combineWithSettings().flatMapLatest {\n\t\t\t\trepository.observeUpdatedManga(10, it)\n\t\t\t}.map { mangaList ->\n\t\t\t\tif (mangaList.isEmpty()) {\n\t\t\t\t\tnull\n\t\t\t\t} else {\n\t\t\t\t\tUpdatedMangaHeader(\n\t\t\t\t\t\tmangaList.map { mangaListMapper.toListModel(it.manga, ListMode.GRID) },\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tflowOf(null)\n\t\t}\n\t}\n\n\tprivate fun Flow<Set<ListFilterOption>>.combineWithSettings(): Flow<Set<ListFilterOption>> = combine(\n\t\tsettings.observeAsFlow(AppSettings.KEY_DISABLE_NSFW) { isNsfwContentDisabled },\n\t) { filters, skipNsfw ->\n\t\tif (skipNsfw) {\n\t\t\tfilters + ListFilterOption.SFW\n\t\t} else {\n\t\t\tfilters\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed.adapter\n\nimport android.content.Context\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.MangaListListener\nimport org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.errorFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.errorStateListAD\nimport org.koitharu.kotatsu.list.ui.adapter.listHeaderAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD\nimport org.koitharu.kotatsu.list.ui.adapter.loadingStateAD\nimport org.koitharu.kotatsu.list.ui.adapter.quickFilterAD\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.size.ItemSizeResolver\nimport org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem\n\nclass FeedAdapter(\n\tlistener: MangaListListener,\n\tsizeResolver: ItemSizeResolver,\n\tfeedClickListener: OnListItemClickListener<FeedItem>,\n) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {\n\n\tinit {\n\t\taddDelegate(ListItemType.FEED, feedItemAD(feedClickListener))\n\t\taddDelegate(\n\t\t\tListItemType.MANGA_NESTED_GROUP,\n\t\t\tupdatedMangaAD(\n\t\t\t\tsizeResolver = sizeResolver,\n\t\t\t\tlistener = listener,\n\t\t\t\theaderClickListener = listener,\n\t\t\t),\n\t\t)\n\t\taddDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())\n\t\taddDelegate(ListItemType.STATE_LOADING, loadingStateAD())\n\t\taddDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener))\n\t\taddDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener))\n\t\taddDelegate(ListItemType.HEADER, listHeaderAD(listener))\n\t\taddDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listener))\n\t\taddDelegate(ListItemType.QUICK_FILTER, quickFilterAD(listener))\n\t}\n\n\toverride fun getSectionText(context: Context, position: Int): CharSequence? {\n\t\treturn findHeader(position)?.getText(context)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedItemAD.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed.adapter\n\nimport androidx.core.content.ContextCompat\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.drawableStart\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.databinding.ItemFeedBinding\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem\n\nfun feedItemAD(\n\tclickListener: OnListItemClickListener<FeedItem>,\n) = adapterDelegateViewBinding<FeedItem, ListModel, ItemFeedBinding>(\n\t{ inflater, parent -> ItemFeedBinding.inflate(inflater, parent, false) },\n) {\n\tval indicatorNew = ContextCompat.getDrawable(context, R.drawable.ic_new)\n\n\titemView.setOnClickListener {\n\t\tclickListener.onItemClick(item, it)\n\t}\n\n\tbind {\n\t\tbinding.imageViewCover.setImageAsync(item.imageUrl, item.manga.source)\n\t\tbinding.textViewTitle.text = item.title\n\t\tbinding.textViewSummary.text = context.resources.getQuantityStringSafe(\n\t\t\tR.plurals.new_chapters,\n\t\t\titem.count,\n\t\t\titem.count,\n\t\t)\n\t\tbinding.textViewSummary.drawableStart = if (item.isNew) {\n\t\t\tindicatorNew\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/UpdatedMangaAD.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.databinding.ItemListGroupBinding\nimport org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener\nimport org.koitharu.kotatsu.list.ui.adapter.ListItemType\nimport org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\nimport org.koitharu.kotatsu.list.ui.size.ItemSizeResolver\nimport org.koitharu.kotatsu.tracker.ui.feed.model.UpdatedMangaHeader\n\nfun updatedMangaAD(\n\tsizeResolver: ItemSizeResolver,\n\tlistener: OnListItemClickListener<MangaListModel>,\n\theaderClickListener: ListHeaderClickListener,\n) = adapterDelegateViewBinding<UpdatedMangaHeader, ListModel, ItemListGroupBinding>(\n\t{ layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) },\n) {\n\n\tval adapter = BaseListAdapter<ListModel>()\n\t\t.addDelegate(ListItemType.MANGA_GRID, mangaGridItemAD(sizeResolver, listener))\n\tbinding.recyclerView.adapter = adapter\n\tbinding.buttonMore.setOnClickListener { v ->\n\t\theaderClickListener.onListHeaderClick(ListHeader(0, payload = item), v)\n\t}\n\tbinding.textViewTitle.setText(R.string.updates)\n\tbinding.buttonMore.setText(R.string.more)\n\n\tbind {\n\t\tadapter.items = item.list\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/model/FeedItem.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed.model\n\nimport org.koitharu.kotatsu.core.model.withOverride\nimport org.koitharu.kotatsu.core.ui.model.MangaOverride\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.ifNullOrEmpty\n\ndata class FeedItem(\n\tval id: Long,\n\tprivate val override: MangaOverride?,\n\tval manga: Manga,\n\tval count: Int,\n\tval isNew: Boolean,\n) : ListModel {\n\n\tval imageUrl: String?\n\t\tget() = override?.coverUrl.ifNullOrEmpty { manga.coverUrl }\n\n\tval title: String\n\t\tget() = override?.title.ifNullOrEmpty { manga.title }\n\n\tfun toMangaWithOverride() = manga.withOverride(override)\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is FeedItem && other.id == id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? = when {\n\t\tpreviousState !is FeedItem -> null\n\t\tisNew != previousState.isNew -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED\n\t\telse -> super.getChangePayload(previousState)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/model/UpdatedMangaHeader.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.feed.model\n\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.MangaListModel\n\ndata class UpdatedMangaHeader(\n\tval list: List<MangaListModel>,\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is UpdatedMangaHeader\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any {\n\t\treturn ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesActivity.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.updates\n\nimport org.koitharu.kotatsu.core.ui.FragmentContainerActivity\n\nclass UpdatesActivity : FragmentContainerActivity(UpdatesFragment::class.java)\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesFragment.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.updates\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.appcompat.view.ActionMode\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.ListSelectionController\nimport org.koitharu.kotatsu.list.ui.MangaListFragment\n\n@AndroidEntryPoint\nclass UpdatesFragment : MangaListFragment() {\n\n\toverride val viewModel by viewModels<UpdatesViewModel>()\n\toverride val isSwipeRefreshEnabled = false\n\n\toverride fun onScrolledToEnd() = Unit\n\n\toverride fun onCreateActionMode(\n\t\tcontroller: ListSelectionController,\n\t\tmenuInflater: MenuInflater,\n\t\tmenu: Menu\n\t): Boolean {\n\t\tmenuInflater.inflate(R.menu.mode_updates, menu)\n\t\treturn super.onCreateActionMode(controller, menuInflater, menu)\n\t}\n\n\toverride fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode?, item: MenuItem): Boolean {\n\t\treturn when (item.itemId) {\n\t\t\tR.id.action_remove -> {\n\t\t\t\tviewModel.remove(controller.snapshot())\n\t\t\t\tmode?.finish()\n\t\t\t\ttrue\n\t\t\t}\n\n\t\t\telse -> super.onActionItemClicked(controller, mode, item)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt",
    "content": "package org.koitharu.kotatsu.tracker.ui.updates\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.parser.MangaDataRepository\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.ListMode\nimport org.koitharu.kotatsu.core.prefs.observeAsFlow\nimport org.koitharu.kotatsu.core.ui.model.DateTimeAgo\nimport org.koitharu.kotatsu.core.util.ext.calculateTimeAgo\nimport org.koitharu.kotatsu.core.util.ext.onFirst\nimport org.koitharu.kotatsu.list.domain.ListFilterOption\nimport org.koitharu.kotatsu.list.domain.MangaListMapper\nimport org.koitharu.kotatsu.list.domain.QuickFilterListener\nimport org.koitharu.kotatsu.list.ui.MangaListViewModel\nimport org.koitharu.kotatsu.list.ui.model.EmptyState\nimport org.koitharu.kotatsu.list.ui.model.ListHeader\nimport org.koitharu.kotatsu.list.ui.model.ListModel\nimport org.koitharu.kotatsu.list.ui.model.LoadingState\nimport org.koitharu.kotatsu.list.ui.model.toErrorState\nimport org.koitharu.kotatsu.tracker.domain.TrackingRepository\nimport org.koitharu.kotatsu.tracker.domain.UpdatesListQuickFilter\nimport org.koitharu.kotatsu.tracker.domain.model.MangaTracking\nimport javax.inject.Inject\nimport org.koitharu.kotatsu.local.data.LocalStorageChanges\nimport org.koitharu.kotatsu.local.domain.model.LocalManga\nimport kotlinx.coroutines.flow.SharedFlow\n\n@HiltViewModel\nclass UpdatesViewModel @Inject constructor(\n\tprivate val repository: TrackingRepository,\n\tsettings: AppSettings,\n\tprivate val mangaListMapper: MangaListMapper,\n\tprivate val quickFilter: UpdatesListQuickFilter,\n\tmangaDataRepository: MangaDataRepository,\n\t@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,\n) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {\n\n\toverride val content = combine(\n\t\tquickFilter.appliedOptions.flatMapLatest { filterOptions ->\n\t\t\trepository.observeUpdatedManga(\n\t\t\t\tlimit = 0,\n\t\t\t\tfilterOptions = filterOptions,\n\t\t\t)\n\t\t},\n\t\tquickFilter.appliedOptions,\n\t\tsettings.observeAsFlow(AppSettings.KEY_UPDATED_GROUPING) { isUpdatedGroupingEnabled },\n\t\tobserveListModeWithTriggers(),\n\t) { mangaList, filters, grouping, mode ->\n\t\twhen {\n\t\t\tmangaList.isEmpty() -> listOfNotNull(\n\t\t\t\tquickFilter.filterItem(filters),\n\t\t\t\tEmptyState(\n\t\t\t\t\ticon = R.drawable.ic_empty_history,\n\t\t\t\t\ttextPrimary = R.string.text_history_holder_primary,\n\t\t\t\t\ttextSecondary = R.string.text_history_holder_secondary,\n\t\t\t\t\tactionStringRes = 0,\n\t\t\t\t),\n\t\t\t)\n\n\t\t\telse -> mangaList.toUi(mode, filters, grouping)\n\t\t}\n\t}.onStart {\n\t\tloadingCounter.increment()\n\t}.onFirst {\n\t\tloadingCounter.decrement()\n\t}.catch {\n\t\temit(listOf(it.toErrorState(canRetry = false)))\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))\n\n\tinit {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.gc()\n\t\t}\n\t}\n\n\toverride fun onRefresh() = Unit\n\n\toverride fun onRetry() = Unit\n\n\tfun remove(ids: Set<Long>) {\n\t\tlaunchJob(Dispatchers.Default) {\n\t\t\trepository.clearUpdates(ids)\n\t\t}\n\t}\n\n\tprivate suspend fun List<MangaTracking>.toUi(\n\t\tmode: ListMode,\n\t\tfilters: Set<ListFilterOption>,\n\t\tgrouped: Boolean,\n\t): List<ListModel> {\n\t\tval result = ArrayList<ListModel>(if (grouped) (size * 1.4).toInt() else size + 1)\n\t\tquickFilter.filterItem(filters)?.let(result::add)\n\t\tvar prevHeader: DateTimeAgo? = null\n\t\tfor (item in this) {\n\t\t\tif (grouped) {\n\t\t\t\tval header = item.lastChapterDate?.let { calculateTimeAgo(it) }\n\t\t\t\tif (header != prevHeader) {\n\t\t\t\t\tif (header != null) {\n\t\t\t\t\t\tresult += ListHeader(header)\n\t\t\t\t\t}\n\t\t\t\t\tprevHeader = header\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult += mangaListMapper.toListModel(item.manga, mode)\n\t\t}\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/work/TrackWorker.kt",
    "content": "package org.koitharu.kotatsu.tracker.work\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ServiceInfo\nimport android.os.Build\nimport android.provider.Settings\nimport androidx.annotation.CheckResult\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.hilt.work.HiltWorker\nimport androidx.work.BackoffPolicy\nimport androidx.work.Constraints\nimport androidx.work.CoroutineWorker\nimport androidx.work.ExistingPeriodicWorkPolicy\nimport androidx.work.ForegroundInfo\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.OutOfQuotaPolicy\nimport androidx.work.PeriodicWorkRequestBuilder\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport androidx.work.WorkQuery\nimport androidx.work.WorkerParameters\nimport androidx.work.await\nimport dagger.Lazy\nimport dagger.Reusable\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedInject\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.NonCancellable\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.mapNotNull\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport kotlinx.coroutines.withContext\nimport org.koitharu.kotatsu.BuildConfig\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.db.MangaDatabase\nimport org.koitharu.kotatsu.core.exceptions.CloudFlareException\nimport org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler\nimport org.koitharu.kotatsu.core.model.ids\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.TrackerDownloadStrategy\nimport org.koitharu.kotatsu.core.prefs.TriStateOption\nimport org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.onEachIndexed\nimport org.koitharu.kotatsu.core.util.ext.printStackTraceDebug\nimport org.koitharu.kotatsu.core.util.ext.trySetForeground\nimport org.koitharu.kotatsu.download.ui.worker.DownloadTask\nimport org.koitharu.kotatsu.download.ui.worker.DownloadWorker\nimport org.koitharu.kotatsu.local.data.LocalMangaRepository\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\nimport org.koitharu.kotatsu.parsers.util.toIntUp\nimport org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler\nimport org.koitharu.kotatsu.tracker.domain.CheckNewChaptersUseCase\nimport org.koitharu.kotatsu.tracker.domain.GetTracksUseCase\nimport org.koitharu.kotatsu.tracker.domain.model.MangaTracking\nimport org.koitharu.kotatsu.tracker.domain.model.MangaUpdates\nimport org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper.NotificationInfo\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport kotlin.math.roundToInt\nimport androidx.appcompat.R as appcompatR\n\n@HiltWorker\nclass TrackWorker @AssistedInject constructor(\n\t@Assisted context: Context,\n\t@Assisted workerParams: WorkerParameters,\n\tprivate val captchaHandler: CaptchaHandler,\n\tprivate val notificationHelper: TrackerNotificationHelper,\n\tprivate val settings: AppSettings,\n\tprivate val getTracksUseCase: GetTracksUseCase,\n\tprivate val checkNewChaptersUseCase: CheckNewChaptersUseCase,\n\tprivate val workManager: WorkManager,\n\tprivate val localRepositoryLazy: Lazy<LocalMangaRepository>,\n\tprivate val downloadSchedulerLazy: Lazy<DownloadWorker.Scheduler>,\n) : CoroutineWorker(context, workerParams) {\n\n\tprivate val notificationManager by lazy { NotificationManagerCompat.from(applicationContext) }\n\n\toverride suspend fun doWork(): Result {\n\t\tnotificationHelper.updateChannels()\n\t\tval isForeground = trySetForeground()\n\t\treturn try {\n\t\t\tdoWorkImpl(isFullRun = isForeground && TAG_ONESHOT in tags)\n\t\t} catch (e: CancellationException) {\n\t\t\tthrow e\n\t\t} catch (e: Throwable) {\n\t\t\te.printStackTraceDebug()\n\t\t\tResult.failure()\n\t\t} finally {\n\t\t\twithContext(NonCancellable) {\n\t\t\t\tnotificationManager.cancel(WORKER_NOTIFICATION_ID)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate suspend fun doWorkImpl(isFullRun: Boolean): Result {\n\t\tif (!settings.isTrackerEnabled) {\n\t\t\treturn Result.success()\n\t\t}\n\t\tval tracks = getTracksUseCase(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE)\n\t\tif (tracks.isEmpty()) {\n\t\t\treturn Result.success()\n\t\t}\n\n\t\tval notifications = checkUpdatesAsync(tracks)\n\t\tif (notifications.isNotEmpty() && applicationContext.checkNotificationPermission(null)) {\n\t\t\tval groupNotification = notificationHelper.createGroupNotification(notifications)\n\t\t\tnotifications.forEach { notificationManager.notify(it.tag, it.id, it.notification) }\n\t\t\tif (groupNotification != null) {\n\t\t\t\tnotificationManager.notify(TAG, TrackerNotificationHelper.GROUP_NOTIFICATION_ID, groupNotification)\n\t\t\t}\n\t\t}\n\t\treturn Result.success()\n\t}\n\n\t@CheckResult\n\tprivate suspend fun checkUpdatesAsync(tracks: List<MangaTracking>): List<NotificationInfo> {\n\t\tval semaphore = Semaphore(MAX_PARALLELISM)\n\t\treturn channelFlow {\n\t\t\tfor (track in tracks) {\n\t\t\t\tlaunch {\n\t\t\t\t\tsemaphore.withPermit {\n\t\t\t\t\t\tsend(\n\t\t\t\t\t\t\trunCatchingCancellable {\n\t\t\t\t\t\t\t\tcheckNewChaptersUseCase.invoke(track)\n\t\t\t\t\t\t\t}.getOrElse { error ->\n\t\t\t\t\t\t\t\tMangaUpdates.Failure(\n\t\t\t\t\t\t\t\t\tmanga = track.manga,\n\t\t\t\t\t\t\t\t\terror = error,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}.onEachIndexed { index, it ->\n\t\t\tif (applicationContext.checkNotificationPermission(WORKER_CHANNEL_ID)) {\n\t\t\t\tnotificationManager.notify(WORKER_NOTIFICATION_ID, createWorkerNotification(tracks.size, index + 1))\n\t\t\t}\n\t\t\twhen (it) {\n\t\t\t\tis MangaUpdates.Failure -> {\n\t\t\t\t\tval e = it.error\n\t\t\t\t\tif (e is CloudFlareException) {\n\t\t\t\t\t\tcaptchaHandler.handle(e)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tis MangaUpdates.Success -> processDownload(it)\n\t\t\t}\n\t\t}.mapNotNull {\n\t\t\twhen (it) {\n\t\t\t\tis MangaUpdates.Failure -> null\n\t\t\t\tis MangaUpdates.Success -> if (it.isValid && it.isNotEmpty()) {\n\t\t\t\t\tnotificationHelper.createNotification(\n\t\t\t\t\t\tmanga = it.manga,\n\t\t\t\t\t\tnewChapters = it.newChapters,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tnull\n\t\t\t\t}\n\t\t\t}\n\t\t}.toList()\n\t}\n\n\toverride suspend fun getForegroundInfo(): ForegroundInfo {\n\t\tval channel = NotificationChannelCompat.Builder(\n\t\t\tWORKER_CHANNEL_ID,\n\t\t\tNotificationManagerCompat.IMPORTANCE_LOW,\n\t\t)\n\t\t\t.setName(applicationContext.getString(R.string.check_for_new_chapters))\n\t\t\t.setShowBadge(false)\n\t\t\t.setVibrationEnabled(false)\n\t\t\t.setSound(null, null)\n\t\t\t.setLightsEnabled(false)\n\t\t\t.build()\n\t\tnotificationManager.createNotificationChannel(channel)\n\n\t\tval notification = createWorkerNotification(0, 0)\n\t\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n\t\t\tForegroundInfo(WORKER_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)\n\t\t} else {\n\t\t\tForegroundInfo(WORKER_NOTIFICATION_ID, notification)\n\t\t}\n\t}\n\n\tprivate fun createWorkerNotification(max: Int, progress: Int) = NotificationCompat.Builder(\n\t\tapplicationContext,\n\t\tWORKER_CHANNEL_ID,\n\t).apply {\n\t\tsetContentTitle(applicationContext.getString(R.string.check_for_new_chapters))\n\t\tsetPriority(NotificationCompat.PRIORITY_MIN)\n\t\tsetCategory(NotificationCompat.CATEGORY_SERVICE)\n\t\tsetDefaults(0)\n\t\tsetOngoing(false)\n\t\tsetOnlyAlertOnce(true)\n\t\tsetSilent(true)\n\t\tsetContentIntent(\n\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\tapplicationContext,\n\t\t\t\t0,\n\t\t\t\tAppRouter.trackerSettingsIntent(applicationContext),\n\t\t\t\t0,\n\t\t\t\tfalse,\n\t\t\t),\n\t\t)\n\t\taddAction(\n\t\t\tappcompatR.drawable.abc_ic_clear_material,\n\t\t\tapplicationContext.getString(android.R.string.cancel),\n\t\t\tworkManager.createCancelPendingIntent(id),\n\t\t)\n\t\tsetProgress(max, progress, max == 0)\n\t\tsetSmallIcon(android.R.drawable.stat_notify_sync)\n\t\tsetForegroundServiceBehavior(\n\t\t\tif (TAG_ONESHOT in tags) {\n\t\t\t\tNotificationCompat.FOREGROUND_SERVICE_IMMEDIATE\n\t\t\t} else {\n\t\t\t\tNotificationCompat.FOREGROUND_SERVICE_DEFERRED\n\t\t\t},\n\t\t)\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\tval actionIntent = PendingIntentCompat.getActivity(\n\t\t\t\tapplicationContext, SETTINGS_ACTION_CODE,\n\t\t\t\tIntent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)\n\t\t\t\t\t.putExtra(Settings.EXTRA_APP_PACKAGE, applicationContext.packageName)\n\t\t\t\t\t.putExtra(Settings.EXTRA_CHANNEL_ID, WORKER_CHANNEL_ID),\n\t\t\t\t0, false,\n\t\t\t)\n\t\t\taddAction(\n\t\t\t\tR.drawable.ic_settings,\n\t\t\t\tapplicationContext.getString(R.string.notifications_settings),\n\t\t\t\tactionIntent,\n\t\t\t)\n\t\t}\n\t}.build()\n\n\tprivate suspend fun processDownload(mangaUpdates: MangaUpdates.Success) {\n\t\tif (!mangaUpdates.isValid || mangaUpdates.newChapters.isEmpty()) {\n\t\t\treturn\n\t\t}\n\t\twhen (settings.trackerDownloadStrategy) {\n\t\t\tTrackerDownloadStrategy.DISABLED -> Unit\n\t\t\tTrackerDownloadStrategy.DOWNLOADED -> {\n\t\t\t\tval localManga = localRepositoryLazy.get().findSavedManga(mangaUpdates.manga)\n\t\t\t\tif (localManga != null) {\n\t\t\t\t\tval task = DownloadTask(\n\t\t\t\t\t\tmangaId = mangaUpdates.manga.id,\n\t\t\t\t\t\tisPaused = false,\n\t\t\t\t\t\tisSilent = false,\n\t\t\t\t\t\tchaptersIds = mangaUpdates.newChapters.ids().toLongArray(),\n\t\t\t\t\t\tdestination = null,\n\t\t\t\t\t\tformat = null,\n\t\t\t\t\t\tallowMeteredNetwork = settings.allowDownloadOnMeteredNetwork != TriStateOption.DISABLED,\n\t\t\t\t\t)\n\t\t\t\t\tdownloadSchedulerLazy.get().schedule(setOf(mangaUpdates.manga to task))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Reusable\n\tclass Scheduler @Inject constructor(\n\t\tprivate val workManager: WorkManager,\n\t\tprivate val settings: AppSettings,\n\t\tprivate val dbProvider: Provider<MangaDatabase>,\n\t) : PeriodicWorkScheduler {\n\n\t\toverride suspend fun schedule() {\n\t\t\tval frequency = settings.trackerFrequencyFactor\n\t\t\tif (frequency <= 0f) {\n\t\t\t\treturn unschedule()\n\t\t\t}\n\t\t\tval constraints = createConstraints()\n\t\t\tval runCount = dbProvider.get().getTracksDao().getTracksCount()\n\t\t\tval runsPerFullCheck = (runCount / BATCH_SIZE.toFloat()).toIntUp().coerceAtLeast(1)\n\t\t\tval interval = (18 / runsPerFullCheck / frequency).roundToInt().coerceAtLeast(2)\n\t\t\tval request = PeriodicWorkRequestBuilder<TrackWorker>(interval.toLong(), TimeUnit.HOURS)\n\t\t\t\t.setConstraints(constraints)\n\t\t\t\t.addTag(TAG)\n\t\t\t\t.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)\n\t\t\t\t.build()\n\t\t\tworkManager\n\t\t\t\t.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)\n\t\t\t\t.await()\n\t\t}\n\n\t\toverride suspend fun unschedule() {\n\t\t\tworkManager\n\t\t\t\t.cancelUniqueWork(TAG)\n\t\t\t\t.await()\n\t\t}\n\n\t\toverride suspend fun isScheduled(): Boolean {\n\t\t\treturn workManager\n\t\t\t\t.awaitUniqueWorkInfoByName(TAG)\n\t\t\t\t.any { !it.state.isFinished }\n\t\t}\n\n\t\tfun startNow() {\n\t\t\tval constraints = Constraints.Builder()\n\t\t\t\t.setRequiredNetworkType(NetworkType.CONNECTED)\n\t\t\t\t.build()\n\t\t\tval request = OneTimeWorkRequestBuilder<TrackWorker>()\n\t\t\t\t.setConstraints(constraints)\n\t\t\t\t.addTag(TAG_ONESHOT)\n\t\t\t\t.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)\n\t\t\t\t.build()\n\t\t\tworkManager.enqueue(request)\n\t\t}\n\n\t\tfun observeIsRunning(): Flow<Boolean> {\n\t\t\tval query = WorkQuery.Builder.fromTags(listOf(TAG, TAG_ONESHOT)).build()\n\t\t\treturn workManager.getWorkInfosFlow(query)\n\t\t\t\t.map { works ->\n\t\t\t\t\tworks.any { x -> x.state == WorkInfo.State.RUNNING }\n\t\t\t\t}\n\t\t}\n\n\t\tprivate fun createConstraints() = Constraints.Builder()\n\t\t\t.setRequiredNetworkType(if (settings.isTrackerWifiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)\n\t\t\t.build()\n\t}\n\n\tprivate companion object {\n\n\t\tconst val WORKER_CHANNEL_ID = \"track_worker\"\n\t\tconst val WORKER_NOTIFICATION_ID = 35\n\t\tconst val TAG = \"tracking\"\n\t\tconst val TAG_ONESHOT = \"tracking_oneshot\"\n\t\tconst val MAX_PARALLELISM = 6\n\t\tval BATCH_SIZE = if (BuildConfig.DEBUG) 20 else 46\n\t\tconst val SETTINGS_ACTION_CODE = 5\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/tracker/work/TrackerNotificationHelper.kt",
    "content": "package org.koitharu.kotatsu.tracker.work\n\nimport android.app.Notification\nimport android.app.NotificationManager\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.os.Build\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationCompat.VISIBILITY_PRIVATE\nimport androidx.core.app.NotificationCompat.VISIBILITY_SECRET\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.content.ContextCompat\nimport coil3.ImageLoader\nimport coil3.request.ImageRequest\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.LocalizedAppContext\nimport org.koitharu.kotatsu.core.model.getLocalizedTitle\nimport org.koitharu.kotatsu.core.model.isNsfw\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.checkNotificationPermission\nimport org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe\nimport org.koitharu.kotatsu.core.util.ext.mangaSourceExtra\nimport org.koitharu.kotatsu.core.util.ext.toBitmapOrNull\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.model.MangaChapter\nimport javax.inject.Inject\n\nclass TrackerNotificationHelper @Inject constructor(\n\t@LocalizedAppContext private val applicationContext: Context,\n\tprivate val settings: AppSettings,\n\tprivate val coil: ImageLoader,\n) {\n\n\tfun getAreNotificationsEnabled(): Boolean {\n\t\tval manager = NotificationManagerCompat.from(applicationContext)\n\t\tif (!manager.areNotificationsEnabled()) {\n\t\t\treturn false\n\t\t}\n\t\treturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n\t\t\tval channel = manager.getNotificationChannel(CHANNEL_ID)\n\t\t\tchannel != null && channel.importance != NotificationManager.IMPORTANCE_NONE\n\t\t} else {\n\t\t\t// fallback\n\t\t\tsettings.isTrackerNotificationsEnabled\n\t\t}\n\t}\n\n\tsuspend fun createNotification(manga: Manga, newChapters: List<MangaChapter>): NotificationInfo? {\n\t\tif (newChapters.isEmpty() || !applicationContext.checkNotificationPermission(CHANNEL_ID)) {\n\t\t\treturn null\n\t\t}\n\t\tif (manga.isNsfw() && (settings.isTrackerNsfwDisabled || settings.isNsfwContentDisabled)) {\n\t\t\treturn null\n\t\t}\n\t\tval id = manga.url.hashCode()\n\t\tval builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\tval summary = applicationContext.resources.getQuantityStringSafe(\n\t\t\tR.plurals.new_chapters,\n\t\t\tnewChapters.size,\n\t\t\tnewChapters.size,\n\t\t)\n\t\twith(builder) {\n\t\t\tsetContentText(summary)\n\t\t\tsetContentTitle(manga.title)\n\t\t\tsetNumber(newChapters.size)\n\t\t\tsetLargeIcon(\n\t\t\t\tcoil.execute(\n\t\t\t\t\tImageRequest.Builder(applicationContext)\n\t\t\t\t\t\t.data(manga.coverUrl)\n\t\t\t\t\t\t.mangaSourceExtra(manga.source)\n\t\t\t\t\t\t.build(),\n\t\t\t\t).toBitmapOrNull(),\n\t\t\t)\n\t\t\tsetSmallIcon(R.drawable.ic_stat_book_plus)\n\t\t\tsetGroup(GROUP_NEW_CHAPTERS)\n\t\t\tval style = NotificationCompat.InboxStyle(this)\n\t\t\tfor (chapter in newChapters) {\n\t\t\t\tstyle.addLine(chapter.getLocalizedTitle(applicationContext.resources))\n\t\t\t}\n\t\t\tstyle.setSummaryText(manga.title)\n\t\t\tstyle.setBigContentTitle(summary)\n\t\t\tsetStyle(style)\n\t\t\tval intent = AppRouter.detailsIntent(applicationContext, manga)\n\t\t\tsetContentIntent(\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\tid,\n\t\t\t\t\tintent,\n\t\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t)\n\t\t\tsetVisibility(if (manga.isNsfw()) VISIBILITY_SECRET else VISIBILITY_PRIVATE)\n\t\t\tsetShortcutId(manga.id.toString())\n\t\t\tapplyCommonSettings(this)\n\t\t}\n\t\treturn NotificationInfo(id, TAG, builder.build(), manga, newChapters.size)\n\t}\n\n\tfun createGroupNotification(\n\t\tnotifications: List<NotificationInfo>\n\t): Notification? {\n\t\tif (notifications.size <= 1) {\n\t\t\treturn null\n\t\t}\n\t\tval newChaptersCount = notifications.sumOf { it.newChapters }\n\t\tval builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)\n\t\twith(builder) {\n\t\t\tval title = applicationContext.resources.getQuantityStringSafe(\n\t\t\t\tR.plurals.new_chapters,\n\t\t\t\tnewChaptersCount,\n\t\t\t\tnewChaptersCount,\n\t\t\t)\n\t\t\tsetContentTitle(title)\n\t\t\tsetContentText(notifications.joinToString { it.manga.title })\n\t\t\tsetSmallIcon(R.drawable.ic_stat_book_plus)\n\t\t\tval style = NotificationCompat.InboxStyle(this)\n\t\t\tfor (item in notifications) {\n\t\t\t\tstyle.addLine(\n\t\t\t\t\tapplicationContext.getString(R.string.new_chapters_pattern, item.manga.title, item.newChapters),\n\t\t\t\t)\n\t\t\t}\n\t\t\tstyle.setBigContentTitle(title)\n\t\t\tsetStyle(style)\n\t\t\tsetNumber(newChaptersCount)\n\t\t\tsetGroup(GROUP_NEW_CHAPTERS)\n\t\t\tsetGroupSummary(true)\n\t\t\tsetVisibility(\n\t\t\t\tif (notifications.any { it.manga.isNsfw() }) {\n\t\t\t\t\tVISIBILITY_SECRET\n\t\t\t\t} else {\n\t\t\t\t\tVISIBILITY_PRIVATE\n\t\t\t\t},\n\t\t\t)\n\t\t\tval intent = AppRouter.mangaUpdatesIntent(applicationContext)\n\t\t\tsetContentIntent(\n\t\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\t\tapplicationContext,\n\t\t\t\t\tGROUP_NOTIFICATION_ID,\n\t\t\t\t\tintent,\n\t\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\t\tfalse,\n\t\t\t\t),\n\t\t\t)\n\t\t\tapplyCommonSettings(this)\n\t\t}\n\t\treturn builder.build()\n\t}\n\n\tfun updateChannels() {\n\t\tval manager = NotificationManagerCompat.from(applicationContext)\n\t\tmanager.deleteNotificationChannel(LEGACY_CHANNEL_ID)\n\t\tmanager.deleteNotificationChannel(LEGACY_CHANNEL_ID_HISTORY)\n\t\tmanager.deleteNotificationChannelGroup(LEGACY_CHANNELS_GROUP_ID)\n\n\t\tval channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)\n\t\t\t.setName(applicationContext.getString(R.string.new_chapters))\n\t\t\t.setDescription(applicationContext.getString(R.string.show_notification_new_chapters_on))\n\t\t\t.setShowBadge(true)\n\t\t\t.setLightColor(ContextCompat.getColor(applicationContext, R.color.blue_primary))\n\t\t\t.build()\n\t\tmanager.createNotificationChannel(channel)\n\t}\n\n\tprivate fun applyCommonSettings(builder: NotificationCompat.Builder) {\n\t\tbuilder.setAutoCancel(true)\n\t\tbuilder.setCategory(NotificationCompat.CATEGORY_SOCIAL)\n\t\tbuilder.priority = NotificationCompat.PRIORITY_DEFAULT\n\t\tif (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n\t\t\tbuilder.setSound(settings.notificationSound)\n\t\t\tvar defaults = if (settings.notificationLight) {\n\t\t\t\tbuilder.setLights(ContextCompat.getColor(applicationContext, R.color.blue_primary), 1000, 5000)\n\t\t\t\tNotificationCompat.DEFAULT_LIGHTS\n\t\t\t} else 0\n\t\t\tif (settings.notificationVibrate) {\n\t\t\t\tbuilder.setVibrate(longArrayOf(500, 500, 500, 500))\n\t\t\t\tdefaults = defaults or NotificationCompat.DEFAULT_VIBRATE\n\t\t\t}\n\t\t\tbuilder.setDefaults(defaults)\n\t\t}\n\t}\n\n\tclass NotificationInfo(\n\t\tval id: Int,\n\t\tval tag: String,\n\t\tval notification: Notification,\n\t\tval manga: Manga,\n\t\tval newChapters: Int,\n\t)\n\n\tcompanion object {\n\n\t\tconst val CHANNEL_ID = \"tracker_chapters\"\n\t\tconst val GROUP_NOTIFICATION_ID = 0\n\t\tconst val GROUP_NEW_CHAPTERS = \"org.koitharu.kotatsu.NEW_CHAPTERS\"\n\t\tconst val TAG = \"tracker\"\n\n\t\tprivate const val LEGACY_CHANNELS_GROUP_ID = \"trackers\"\n\t\tprivate const val LEGACY_CHANNEL_ID_HISTORY = \"track_history\"\n\t\tprivate const val LEGACY_CHANNEL_ID = \"tracking\"\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/WidgetUpdater.kt",
    "content": "package org.koitharu.kotatsu.widget\n\nimport android.appwidget.AppWidgetManager\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport androidx.room.InvalidationTracker\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport org.koitharu.kotatsu.core.db.TABLE_FAVOURITES\nimport org.koitharu.kotatsu.core.db.TABLE_HISTORY\nimport org.koitharu.kotatsu.widget.recent.RecentWidgetProvider\nimport org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass WidgetUpdater @Inject constructor(\n\t@ApplicationContext private val context: Context,\n) : InvalidationTracker.Observer(TABLE_HISTORY, TABLE_FAVOURITES) {\n\n\toverride fun onInvalidated(tables: Set<String>) {\n\t\tif (TABLE_HISTORY in tables) {\n\t\t\tupdateWidgets(RecentWidgetProvider::class.java)\n\t\t}\n\t\tif (TABLE_FAVOURITES in tables) {\n\t\t\tupdateWidgets(ShelfWidgetProvider::class.java)\n\t\t}\n\t}\n\n\tprivate fun updateWidgets(cls: Class<*>) {\n\t\tval intent = Intent(context, cls)\n\t\tintent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE\n\t\tval ids = (AppWidgetManager.getInstance(context) ?: return)\n\t\t\t.getAppWidgetIds(ComponentName(context, cls))\n\t\tintent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)\n\t\tcontext.sendBroadcast(intent)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt",
    "content": "package org.koitharu.kotatsu.widget.recent\n\nimport android.content.Context\nimport android.content.Intent\nimport android.widget.RemoteViews\nimport android.widget.RemoteViewsService\nimport androidx.core.graphics.drawable.toBitmap\nimport coil3.ImageLoader\nimport coil3.executeBlocking\nimport coil3.request.ImageRequest\nimport coil3.request.transformations\nimport coil3.size.Size\nimport coil3.transform.RoundedCornersTransformation\nimport dagger.Lazy\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow\nimport org.koitharu.kotatsu.core.util.ext.mangaExtra\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.replaceWith\nimport org.koitharu.kotatsu.parsers.util.runCatchingCancellable\n\nclass RecentListFactory(\n\tprivate val context: Context,\n\tprivate val historyRepository: HistoryRepository,\n\tprivate val coilLazy: Lazy<ImageLoader>,\n\tprivate val settings: AppSettings,\n) : RemoteViewsService.RemoteViewsFactory {\n\n\tprivate val dataSet = ArrayList<Manga>()\n\tprivate val transformation = RoundedCornersTransformation(\n\t\tcontext.resources.getDimension(R.dimen.appwidget_corner_radius_inner),\n\t)\n\tprivate val coverSize = Size(\n\t\tcontext.resources.getDimensionPixelSize(R.dimen.widget_cover_width),\n\t\tcontext.resources.getDimensionPixelSize(R.dimen.widget_cover_height),\n\t)\n\n\toverride fun onCreate() = Unit\n\n\toverride fun getLoadingView() = null\n\n\toverride fun getItemId(position: Int) = dataSet.getOrNull(position)?.id ?: 0L\n\n\toverride fun onDataSetChanged() {\n\t\tval data = if (settings.appPassword.isNullOrEmpty()) {\n\t\t\trunBlocking { historyRepository.getList(0, 10) }\n\t\t} else {\n\t\t\temptyList()\n\t\t}\n\t\tdataSet.replaceWith(data)\n\t}\n\n\toverride fun hasStableIds() = true\n\n\toverride fun getViewAt(position: Int): RemoteViews {\n\t\tval views = RemoteViews(context.packageName, R.layout.item_recent)\n\t\tval item = dataSet.getOrNull(position) ?: return views\n\t\trunCatchingCancellable {\n\t\t\tcoilLazy.get().executeBlocking(\n\t\t\t\tImageRequest.Builder(context)\n\t\t\t\t\t.data(item.coverUrl)\n\t\t\t\t\t.size(coverSize)\n\t\t\t\t\t.mangaExtra(item)\n\t\t\t\t\t.transformations(transformation)\n\t\t\t\t\t.build(),\n\t\t\t).getDrawableOrThrow().toBitmap()\n\t\t}.onSuccess { cover ->\n\t\t\tviews.setImageViewBitmap(R.id.imageView_cover, cover)\n\t\t}.onFailure {\n\t\t\tviews.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)\n\t\t}\n\t\tval intent = Intent()\n\t\tintent.putExtra(AppRouter.KEY_ID, item.id)\n\t\tviews.setOnClickFillInIntent(R.id.imageView_cover, intent)\n\t\treturn views\n\t}\n\n\toverride fun getCount() = dataSet.size\n\n\toverride fun getViewTypeCount() = 1\n\n\toverride fun onDestroy() = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentWidgetConfigActivity.kt",
    "content": "package org.koitharu.kotatsu.widget.recent\n\nimport android.appwidget.AppWidgetManager\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.WindowInsetsCompat\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.prefs.AppWidgetConfig\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityAppwidgetRecentBinding\n\n@AndroidEntryPoint\nclass RecentWidgetConfigActivity :\n\tBaseActivity<ActivityAppwidgetRecentBinding>(),\n\tView.OnClickListener {\n\n\tprivate lateinit var config: AppWidgetConfig\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityAppwidgetRecentBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tviewBinding.buttonDone.setOnClickListener(this)\n\t\tval appWidgetId = intent?.getIntExtra(\n\t\t\tAppWidgetManager.EXTRA_APPWIDGET_ID,\n\t\t\tAppWidgetManager.INVALID_APPWIDGET_ID,\n\t\t) ?: AppWidgetManager.INVALID_APPWIDGET_ID\n\t\tif (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {\n\t\t\tfinishAfterTransition()\n\t\t\treturn\n\t\t}\n\t\tconfig = AppWidgetConfig(this, RecentWidgetProvider::class.java, appWidgetId)\n\t\tviewBinding.switchBackground.isChecked = config.hasBackground\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.root.setPadding(\n\t\t\tbarsInsets.left,\n\t\t\tbarsInsets.top,\n\t\t\tbarsInsets.right,\n\t\t\tbarsInsets.bottom,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_done -> {\n\t\t\t\tconfig.hasBackground = viewBinding.switchBackground.isChecked\n\t\t\t\tupdateWidget()\n\t\t\t\tsetResult(\n\t\t\t\t\tRESULT_OK,\n\t\t\t\t\tIntent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId),\n\t\t\t\t)\n\t\t\t\tfinish()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun updateWidget() {\n\t\tval intent = Intent(this, RecentWidgetProvider::class.java)\n\t\tintent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE\n\t\tval ids = intArrayOf(config.widgetId)\n\t\tintent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)\n\t\tsendBroadcast(intent)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt",
    "content": "package org.koitharu.kotatsu.widget.recent\n\nimport android.app.PendingIntent\nimport android.appwidget.AppWidgetManager\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.Color\nimport android.widget.RemoteViews\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.prefs.AppWidgetConfig\nimport org.koitharu.kotatsu.core.ui.BaseAppWidgetProvider\nimport org.koitharu.kotatsu.reader.ui.ReaderActivity\n\nclass RecentWidgetProvider : BaseAppWidgetProvider() {\n\n\toverride fun onUpdate(\n\t\tcontext: Context,\n\t\tappWidgetManager: AppWidgetManager,\n\t\tappWidgetIds: IntArray\n\t) {\n\t\tsuper.onUpdate(context, appWidgetManager, appWidgetIds)\n\t\tappWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stackView)\n\t}\n\n\toverride fun onUpdateWidget(context: Context, config: AppWidgetConfig): RemoteViews {\n\t\tval views = RemoteViews(context.packageName, R.layout.widget_recent)\n\t\tif (!config.hasBackground) {\n\t\t\tviews.setInt(R.id.widget_root, \"setBackgroundColor\", Color.TRANSPARENT)\n\t\t} else {\n\t\t\tviews.setInt(R.id.widget_root, \"setBackgroundResource\", R.drawable.bg_appwidget_root)\n\t\t}\n\t\tval adapter = Intent(context, RecentWidgetService::class.java)\n\t\tadapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)\n\t\tadapter.data = adapter.toUri(Intent.URI_INTENT_SCHEME).toUri()\n\t\tviews.setRemoteAdapter(R.id.stackView, adapter)\n\t\tval intent = Intent(context, ReaderActivity::class.java)\n\t\tintent.action = ReaderIntent.ACTION_MANGA_READ\n\t\tviews.setPendingIntentTemplate(\n\t\t\tR.id.stackView,\n\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\tcontext,\n\t\t\t\t0,\n\t\t\t\tintent,\n\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\ttrue,\n\t\t\t),\n\t\t)\n\t\tviews.setEmptyView(R.id.stackView, R.id.textView_holder)\n\t\treturn views\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentWidgetService.kt",
    "content": "package org.koitharu.kotatsu.widget.recent\n\nimport android.content.Intent\nimport android.widget.RemoteViewsService\nimport coil3.ImageLoader\nimport dagger.Lazy\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.history.data.HistoryRepository\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass RecentWidgetService : RemoteViewsService() {\n\n\t@Inject\n\tlateinit var historyRepository: HistoryRepository\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\t@Inject\n\tlateinit var coilLazy: Lazy<ImageLoader>\n\n\toverride fun onGetViewFactory(intent: Intent): RemoteViewsFactory {\n\t\treturn RecentListFactory(applicationContext, historyRepository, coilLazy, settings)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfConfigViewModel.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf\n\nimport androidx.lifecycle.viewModelScope\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.plus\nimport org.koitharu.kotatsu.core.ui.BaseViewModel\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.widget.shelf.model.CategoryItem\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ShelfConfigViewModel @Inject constructor(\n\tfavouritesRepository: FavouritesRepository,\n) : BaseViewModel() {\n\n\tprivate val selectedCategoryId = MutableStateFlow(0L)\n\n\tval content: StateFlow<List<CategoryItem>> = combine(\n\t\tfavouritesRepository.observeCategories(),\n\t\tselectedCategoryId,\n\t) { categories, selectedId ->\n\t\tval list = ArrayList<CategoryItem>(categories.size + 1)\n\t\tlist += CategoryItem(0L, null, selectedId == 0L)\n\t\tcategories.mapTo(list) {\n\t\t\tCategoryItem(it.id, it.title, selectedId == it.id)\n\t\t}\n\t\tlist\n\t}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())\n\n\tvar checkedId: Long\n\t\tget() = selectedCategoryId.value\n\t\tset(value) {\n\t\t\tselectedCategoryId.value = value\n\t\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf\n\nimport android.content.Context\nimport android.content.Intent\nimport android.widget.RemoteViews\nimport android.widget.RemoteViewsService\nimport androidx.core.graphics.drawable.toBitmap\nimport coil3.ImageLoader\nimport coil3.executeBlocking\nimport coil3.request.ImageRequest\nimport coil3.request.transformations\nimport coil3.size.Size\nimport coil3.transform.RoundedCornersTransformation\nimport dagger.Lazy\nimport kotlinx.coroutines.runBlocking\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.AppRouter\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.core.prefs.AppWidgetConfig\nimport org.koitharu.kotatsu.core.ui.image.TrimTransformation\nimport org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow\nimport org.koitharu.kotatsu.core.util.ext.mangaExtra\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport org.koitharu.kotatsu.parsers.model.Manga\nimport org.koitharu.kotatsu.parsers.util.replaceWith\n\nclass ShelfListFactory(\n\tprivate val context: Context,\n\tprivate val favouritesRepository: FavouritesRepository,\n\tprivate val coilLazy: Lazy<ImageLoader>,\n\tprivate val settings: AppSettings,\n\twidgetId: Int,\n) : RemoteViewsService.RemoteViewsFactory {\n\n\tprivate val dataSet = ArrayList<Manga>()\n\tprivate val config = AppWidgetConfig(context, ShelfWidgetProvider::class.java, widgetId)\n\tprivate val transformation = RoundedCornersTransformation(\n\t\tcontext.resources.getDimension(R.dimen.appwidget_corner_radius_inner),\n\t)\n\tprivate val coverSize = Size(\n\t\tcontext.resources.getDimensionPixelSize(R.dimen.widget_cover_width),\n\t\tcontext.resources.getDimensionPixelSize(R.dimen.widget_cover_height),\n\t)\n\n\toverride fun onCreate() = Unit\n\n\toverride fun getLoadingView() = null\n\n\toverride fun getItemId(position: Int) = dataSet.getOrNull(position)?.id ?: 0L\n\n\toverride fun onDataSetChanged() {\n\t\tval data = if (settings.appPassword.isNullOrEmpty()) {\n\t\t\trunBlocking {\n\t\t\t\tval category = config.categoryId\n\t\t\t\tif (category == 0L) {\n\t\t\t\t\tfavouritesRepository.getAllManga()\n\t\t\t\t} else {\n\t\t\t\t\tfavouritesRepository.getManga(category)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\temptyList()\n\t\t}\n\t\tdataSet.replaceWith(data)\n\t}\n\n\toverride fun hasStableIds() = true\n\n\toverride fun getViewAt(position: Int): RemoteViews {\n\t\tval views = RemoteViews(context.packageName, R.layout.item_shelf)\n\t\tval item = dataSet.getOrNull(position) ?: return views\n\t\tviews.setTextViewText(R.id.textView_title, item.title)\n\t\trunCatching {\n\t\t\tcoilLazy.get().executeBlocking(\n\t\t\t\tImageRequest.Builder(context)\n\t\t\t\t\t.data(item.coverUrl)\n\t\t\t\t\t.size(coverSize)\n\t\t\t\t\t.mangaExtra(item)\n\t\t\t\t\t.transformations(transformation, TrimTransformation())\n\t\t\t\t\t.build(),\n\t\t\t).getDrawableOrThrow().toBitmap()\n\t\t}.onSuccess { cover ->\n\t\t\tviews.setImageViewBitmap(R.id.imageView_cover, cover)\n\t\t}.onFailure {\n\t\t\tviews.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)\n\t\t}\n\t\tval intent = Intent()\n\t\tintent.putExtra(AppRouter.KEY_ID, item.id)\n\t\tviews.setOnClickFillInIntent(R.id.rootLayout, intent)\n\t\treturn views\n\t}\n\n\toverride fun getCount() = dataSet.size\n\n\toverride fun getViewTypeCount() = 1\n\n\toverride fun onDestroy() = Unit\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfWidgetConfigActivity.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf\n\nimport android.appwidget.AppWidgetManager\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver\nimport org.koitharu.kotatsu.core.prefs.AppWidgetConfig\nimport org.koitharu.kotatsu.core.ui.BaseActivity\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets\nimport org.koitharu.kotatsu.core.util.ext.observe\nimport org.koitharu.kotatsu.core.util.ext.observeEvent\nimport org.koitharu.kotatsu.core.util.ext.systemBarsInsets\nimport org.koitharu.kotatsu.databinding.ActivityAppwidgetShelfBinding\nimport org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter\nimport org.koitharu.kotatsu.widget.shelf.model.CategoryItem\n\n@AndroidEntryPoint\nclass ShelfWidgetConfigActivity :\n\tBaseActivity<ActivityAppwidgetShelfBinding>(),\n\tOnListItemClickListener<CategoryItem>,\n\tView.OnClickListener {\n\n\tprivate val viewModel by viewModels<ShelfConfigViewModel>()\n\n\tprivate lateinit var adapter: CategorySelectAdapter\n\tprivate lateinit var config: AppWidgetConfig\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\t\tsetContentView(ActivityAppwidgetShelfBinding.inflate(layoutInflater))\n\t\tsetDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)\n\t\tadapter = CategorySelectAdapter(this)\n\t\tviewBinding.recyclerView.adapter = adapter\n\t\tviewBinding.buttonDone.setOnClickListener(this)\n\t\tval appWidgetId = intent?.getIntExtra(\n\t\t\tAppWidgetManager.EXTRA_APPWIDGET_ID,\n\t\t\tAppWidgetManager.INVALID_APPWIDGET_ID,\n\t\t) ?: AppWidgetManager.INVALID_APPWIDGET_ID\n\t\tif (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {\n\t\t\tfinishAfterTransition()\n\t\t\treturn\n\t\t}\n\t\tconfig = AppWidgetConfig(this, ShelfWidgetProvider::class.java, appWidgetId)\n\t\tviewModel.checkedId = config.categoryId\n\t\tviewBinding.switchBackground.isChecked = config.hasBackground\n\n\t\tviewModel.content.observe(this, adapter)\n\t\tviewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))\n\t}\n\n\toverride fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {\n\t\tval barsInsets = insets.systemBarsInsets\n\t\tviewBinding.recyclerView.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\tbottom = barsInsets.bottom,\n\t\t)\n\t\tviewBinding.appbar.updatePadding(\n\t\t\tleft = barsInsets.left,\n\t\t\tright = barsInsets.right,\n\t\t\ttop = barsInsets.top,\n\t\t)\n\t\treturn insets.consumeAllSystemBarsInsets()\n\t}\n\n\toverride fun onClick(v: View) {\n\t\twhen (v.id) {\n\t\t\tR.id.button_done -> {\n\t\t\t\tconfig.categoryId = viewModel.checkedId\n\t\t\t\tconfig.hasBackground = viewBinding.switchBackground.isChecked\n\t\t\t\tupdateWidget()\n\t\t\t\tsetResult(\n\t\t\t\t\tRESULT_OK,\n\t\t\t\t\tIntent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId),\n\t\t\t\t)\n\t\t\t\tfinish()\n\t\t\t}\n\t\t}\n\t}\n\n\toverride fun onItemClick(item: CategoryItem, view: View) {\n\t\tviewModel.checkedId = item.id\n\t}\n\n\tprivate fun updateWidget() {\n\t\tval intent = Intent(this, ShelfWidgetProvider::class.java)\n\t\tintent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE\n\t\tval ids = intArrayOf(config.widgetId)\n\t\tintent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)\n\t\tsendBroadcast(intent)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf\n\nimport android.app.PendingIntent\nimport android.appwidget.AppWidgetManager\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.Color\nimport android.widget.RemoteViews\nimport androidx.core.app.PendingIntentCompat\nimport androidx.core.net.toUri\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.nav.ReaderIntent\nimport org.koitharu.kotatsu.core.prefs.AppWidgetConfig\nimport org.koitharu.kotatsu.core.ui.BaseAppWidgetProvider\nimport org.koitharu.kotatsu.reader.ui.ReaderActivity\n\nclass ShelfWidgetProvider : BaseAppWidgetProvider() {\n\n\toverride fun onUpdate(\n\t\tcontext: Context,\n\t\tappWidgetManager: AppWidgetManager,\n\t\tappWidgetIds: IntArray\n\t) {\n\t\tsuper.onUpdate(context, appWidgetManager, appWidgetIds)\n\t\tappWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.gridView)\n\t}\n\n\toverride fun onUpdateWidget(context: Context, config: AppWidgetConfig): RemoteViews {\n\t\tval views = RemoteViews(context.packageName, R.layout.widget_shelf)\n\t\tif (!config.hasBackground) {\n\t\t\tviews.setInt(R.id.widget_root, \"setBackgroundColor\", Color.TRANSPARENT)\n\t\t} else {\n\t\t\tviews.setInt(R.id.widget_root, \"setBackgroundResource\", R.drawable.bg_appwidget_root)\n\t\t}\n\t\tval adapter = Intent(context, ShelfWidgetService::class.java)\n\t\tadapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)\n\t\tadapter.data = adapter.toUri(Intent.URI_INTENT_SCHEME).toUri()\n\t\tviews.setRemoteAdapter(R.id.gridView, adapter)\n\t\tval intent = Intent(context, ReaderActivity::class.java)\n\t\tintent.action = ReaderIntent.ACTION_MANGA_READ\n\t\tviews.setPendingIntentTemplate(\n\t\t\tR.id.gridView,\n\t\t\tPendingIntentCompat.getActivity(\n\t\t\t\tcontext,\n\t\t\t\t0,\n\t\t\t\tintent,\n\t\t\t\tPendingIntent.FLAG_UPDATE_CURRENT,\n\t\t\t\ttrue,\n\t\t\t),\n\t\t)\n\t\tviews.setEmptyView(R.id.gridView, R.id.textView_holder)\n\t\treturn views\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfWidgetService.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf\n\nimport android.appwidget.AppWidgetManager\nimport android.content.Intent\nimport android.widget.RemoteViewsService\nimport coil3.ImageLoader\nimport dagger.Lazy\nimport dagger.hilt.android.AndroidEntryPoint\nimport org.koitharu.kotatsu.core.prefs.AppSettings\nimport org.koitharu.kotatsu.favourites.domain.FavouritesRepository\nimport javax.inject.Inject\n\n@AndroidEntryPoint\nclass ShelfWidgetService : RemoteViewsService() {\n\n\t@Inject\n\tlateinit var favouritesRepository: FavouritesRepository\n\n\t@Inject\n\tlateinit var settings: AppSettings\n\n\t@Inject\n\tlateinit var coilLazy: Lazy<ImageLoader>\n\n\toverride fun onGetViewFactory(intent: Intent): RemoteViewsFactory {\n\t\tval widgetId = intent.getIntExtra(\n\t\t\tAppWidgetManager.EXTRA_APPWIDGET_ID,\n\t\t\tAppWidgetManager.INVALID_APPWIDGET_ID,\n\t\t)\n\t\treturn ShelfListFactory(applicationContext, favouritesRepository, coilLazy, settings, widgetId)\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectAdapter.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf.adapter\n\nimport org.koitharu.kotatsu.core.ui.BaseListAdapter\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.widget.shelf.model.CategoryItem\n\nclass CategorySelectAdapter(\n\tclickListener: OnListItemClickListener<CategoryItem>\n) : BaseListAdapter<CategoryItem>() {\n\n\tinit {\n\t\tdelegatesManager.addDelegate(categorySelectItemAD(clickListener))\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/adapter/CategorySelectItemAD.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf.adapter\n\nimport com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.list.OnListItemClickListener\nimport org.koitharu.kotatsu.databinding.ItemCategoryCheckableSingleBinding\nimport org.koitharu.kotatsu.widget.shelf.model.CategoryItem\n\nfun categorySelectItemAD(\n\tclickListener: OnListItemClickListener<CategoryItem>\n) = adapterDelegateViewBinding<CategoryItem, CategoryItem, ItemCategoryCheckableSingleBinding>(\n\t{ inflater, parent -> ItemCategoryCheckableSingleBinding.inflate(inflater, parent, false) },\n) {\n\n\titemView.setOnClickListener {\n\t\tclickListener.onItemClick(item, it)\n\t}\n\n\tbind {\n\t\twith(binding.checkedTextView) {\n\t\t\ttext = item.name ?: getString(R.string.all_favourites)\n\t\t\tisChecked = item.isSelected\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/model/CategoryItem.kt",
    "content": "package org.koitharu.kotatsu.widget.shelf.model\n\nimport org.koitharu.kotatsu.list.ui.ListModelDiffCallback\nimport org.koitharu.kotatsu.list.ui.model.ListModel\n\ndata class CategoryItem(\n\tval id: Long,\n\tval name: String?,\n\tval isSelected: Boolean\n) : ListModel {\n\n\toverride fun areItemsTheSame(other: ListModel): Boolean {\n\t\treturn other is CategoryItem && other.id == id\n\t}\n\n\toverride fun getChangePayload(previousState: ListModel): Any? {\n\t\treturn if (previousState is CategoryItem && previousState.isSelected != isSelected) {\n\t\t\tListModelDiffCallback.PAYLOAD_CHECKED_CHANGED\n\t\t} else {\n\t\t\tnull\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/res/anim/bottom_sheet_slide_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:duration=\"@integer/config_defaultAnimTime\"\n\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\">\n\n\t<translate\n\t\tandroid:fromYDelta=\"100%p\"\n\t\tandroid:toYDelta=\"0\" />\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/bottom_sheet_slide_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:duration=\"@integer/config_defaultAnimTime\"\n\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\">\n\n\t<translate\n\t\tandroid:fromYDelta=\"0\"\n\t\tandroid:toYDelta=\"100%p\" />\n\n</set>"
  },
  {
    "path": "app/src/main/res/color/bg_background_transparency.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:alpha=\"0.4\" android:color=\"?android:colorBackground\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/bg_floating_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:alpha=\"0.6\" android:color=\"?colorSurfaceBright\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/bottom_menu_active_indicator.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:alpha=\"0.2\" android:color=\"?attr/colorPrimary\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/bottom_menu_active_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:color=\"?attr/colorOnSecondaryContainer\" android:state_checked=\"true\" />\n\t<item android:color=\"?attr/colorOnSurfaceVariant\" android:state_checked=\"false\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/colored_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:color=\"?attr/colorSurfaceContainerHigh\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/list_item_background_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:alpha=\"0.3\" android:color=\"?colorPrimary\" android:state_checked=\"true\"/>\n\t<item android:color=\"@android:color/transparent\"/>\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/list_item_text_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:alpha=\"@dimen/material_emphasis_disabled\" android:color=\"?attr/colorOnSurfaceVariant\" android:state_enabled=\"false\" />\n\t<item android:color=\"?attr/colorPrimaryDark\" android:state_checked=\"true\" />\n\t<item android:color=\"?attr/colorOnSurfaceVariant\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/selector_overlay.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:alpha=\"0.27\" android:color=\"?attr/colorOnSurface\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/drawable/avd_explore_enter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:aapt=\"http://schemas.android.com/aapt\">\n\t<aapt:attr name=\"android:drawable\">\n\t\t<vector\n\t\t\tandroid:name=\"browse_enter\"\n\t\t\tandroid:width=\"24dp\"\n\t\t\tandroid:height=\"24dp\"\n\t\t\tandroid:viewportWidth=\"24\"\n\t\t\tandroid:viewportHeight=\"24\">\n\t\t\t<group\n\t\t\t\tandroid:name=\"main_parent\"\n\t\t\t\tandroid:pivotX=\"12\"\n\t\t\t\tandroid:pivotY=\"12\">\n\t\t\t\t<group\n\t\t\t\t\tandroid:name=\"group\"\n\t\t\t\t\tandroid:pivotX=\"12\"\n\t\t\t\t\tandroid:pivotY=\"12\">\n\t\t\t\t\t<path\n\t\t\t\t\t\tandroid:name=\"dot\"\n\t\t\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\t\t\tandroid:pathData=\"M 12 10.9 C 12.61 10.9 13.1 11.39 13.1 12 C 13.1 12.61 12.61 13.1 12 13.1 C 11.39 13.1 10.9 12.61 10.9 12 C 10.9 11.39 11.39 10.9 12 10.9 Z\" />\n\t\t\t\t</group>\n\t\t\t\t<clip-path\n\t\t\t\t\tandroid:name=\"dot_mask\"\n\t\t\t\t\tandroid:pathData=\"M 0.188 0.188 L 0.188 24 L 23.938 24 L 23.938 0.188 L 0.188 0.188 Z M 12 10.9 C 12.61 10.9 13.1 11.39 13.1 12 C 13.1 12.61 12.61 13.1 12 13.1 C 11.39 13.1 10.9 12.61 10.9 12 C 10.9 11.39 11.39 10.9 12 10.9 Z\" />\n\t\t\t\t<path\n\t\t\t\t\tandroid:name=\"compass\"\n\t\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\t\tandroid:pathData=\"M 6.5 17.5 L 14.01 14.01 L 17.5 6.5 L 9.99 9.99 L 6.5 17.5 Z M 12 10.9 C 12.61 10.9 13.1 11.39 13.1 12 C 13.1 12.61 12.61 13.1 12 13.1 C 11.39 13.1 10.9 12.61 10.9 12 C 10.9 11.39 11.39 10.9 12 10.9 Z\" />\n\t\t\t\t<group\n\t\t\t\t\tandroid:name=\"ring_parent\"\n\t\t\t\t\tandroid:pivotX=\"12\"\n\t\t\t\t\tandroid:pivotY=\"12\"\n\t\t\t\t\tandroid:scaleX=\"0.833\"\n\t\t\t\t\tandroid:scaleY=\"0.833\">\n\t\t\t\t\t<path\n\t\t\t\t\t\tandroid:name=\"ring\"\n\t\t\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\t\t\tandroid:pathData=\"M 12 0 C 8.819 0 5.765 1.265 3.515 3.515 C 1.265 5.765 0 8.819 0 12 C 0 15.181 1.265 18.235 3.515 20.485 C 5.765 22.735 8.819 24 12 24 C 15.181 24 18.235 22.735 20.485 20.485 C 22.735 18.235 24 15.181 24 12 C 24 8.819 22.735 5.765 20.485 3.515 C 18.235 1.265 15.181 0 12 0 Z M 12 21.6 C 9.455 21.6 7.012 20.588 5.212 18.788 C 3.412 16.988 2.4 14.545 2.4 12 C 2.4 9.455 3.412 7.012 5.212 5.212 C 7.012 3.412 9.455 2.4 12 2.4 C 14.545 2.4 16.988 3.412 18.788 5.212 C 20.588 7.012 21.6 9.455 21.6 12 C 21.6 14.545 20.588 16.988 18.788 18.788 C 16.988 20.588 14.545 21.6 12 21.6 Z\"\n\t\t\t\t\t\tandroid:strokeAlpha=\"0\" />\n\t\t\t\t</group>\n\t\t\t\t<group android:name=\"group_1\" />\n\t\t\t</group>\n\t\t</vector>\n\t</aapt:attr>\n\t<target android:name=\"compass\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<objectAnimator\n\t\t\t\tandroid:duration=\"@integer/config_defaultAnimTime\"\n\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\tandroid:propertyName=\"pathData\"\n\t\t\t\tandroid:valueFrom=\"M 9.99 9.99 C 9.408 11.242 8.827 12.493 8.245 13.745 C 7.663 14.997 7.082 16.248 6.5 17.5 C 6.5 17.5 6.5 17.5 6.5 17.5 C 9.003 16.337 11.507 15.173 14.01 14.01 C 15.173 11.507 16.337 9.003 17.5 6.5 C 14.997 7.663 12.493 8.827 9.99 9.99 M 12 10.9 C 11.39 10.9 10.9 11.39 10.9 12 C 10.9 12.305 11.023 12.58 11.221 12.779 C 11.42 12.977 11.695 13.1 12 13.1 C 12.61 13.1 13.1 12.61 13.1 12 C 13.1 11.39 12.61 10.9 12 10.9 L 12 10.9 M 12 12 L 12 12 L 12 12 L 12 12 L 12 12 L 12 12 L 12 12 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12\"\n\t\t\t\tandroid:valueTo=\"M 12 2 C 6.48 2 2 6.48 2 12 C 2 17.141 5.886 21.38 10.878 21.938 C 11.247 21.979 11.621 22 12 22 C 17.52 22 22 17.52 22 12 C 22 6.48 17.52 2 12 2 C 12 2 12 2 12 2 M 12 10.9 C 11.695 10.9 11.42 11.023 11.221 11.221 C 11.023 11.42 10.9 11.695 10.9 12 C 10.9 12.61 11.39 13.1 12 13.1 C 12.61 13.1 13.1 12.61 13.1 12 C 13.1 11.39 12.61 10.9 12 10.9 L 12 10.9 M 14.19 14.19 L 6 18 L 6 18 L 9.81 9.81 L 18 6 L 14.19 14.19 L 14.19 14.19 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12\"\n\t\t\t\tandroid:valueType=\"pathType\" />\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"main_parent\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<objectAnimator\n\t\t\t\tandroid:duration=\"@integer/config_defaultAnimTime\"\n\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\tandroid:propertyName=\"rotation\"\n\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\tandroid:valueTo=\"180\"\n\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"group\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"66\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\t\tandroid:propertyName=\"scaleX\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/decelerate_interpolator\"\n\t\t\t\t\tandroid:propertyName=\"scaleX\"\n\t\t\t\t\tandroid:startOffset=\"66\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"66\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/decelerate_interpolator\"\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:startOffset=\"66\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n</animated-vector>"
  },
  {
    "path": "app/src/main/res/drawable/avd_explore_leave.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:aapt=\"http://schemas.android.com/aapt\">\n\t<aapt:attr name=\"android:drawable\">\n\t\t<vector\n\t\t\tandroid:name=\"browse_leave\"\n\t\t\tandroid:width=\"24dp\"\n\t\t\tandroid:height=\"24dp\"\n\t\t\tandroid:viewportWidth=\"24\"\n\t\t\tandroid:viewportHeight=\"24\">\n\t\t\t<group\n\t\t\t\tandroid:name=\"main_parent\"\n\t\t\t\tandroid:pivotX=\"12\"\n\t\t\t\tandroid:pivotY=\"12\">\n\t\t\t\t<group\n\t\t\t\t\tandroid:name=\"group\"\n\t\t\t\t\tandroid:pivotX=\"12\"\n\t\t\t\t\tandroid:pivotY=\"12\">\n\t\t\t\t\t<path\n\t\t\t\t\t\tandroid:name=\"dot\"\n\t\t\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\t\t\tandroid:pathData=\"M 12 10.9 C 12.61 10.9 13.1 11.39 13.1 12 C 13.1 12.61 12.61 13.1 12 13.1 C 11.39 13.1 10.9 12.61 10.9 12 C 10.9 11.39 11.39 10.9 12 10.9 Z\" />\n\t\t\t\t</group>\n\t\t\t\t<clip-path\n\t\t\t\t\tandroid:name=\"dot_mask\"\n\t\t\t\t\tandroid:pathData=\"M 0.188 0.188 L 0.188 24 L 23.938 24 L 23.938 0.188 L 0.188 0.188 Z M 12 10.9 C 12.61 10.9 13.1 11.39 13.1 12 C 13.1 12.61 12.61 13.1 12 13.1 C 11.39 13.1 10.9 12.61 10.9 12 C 10.9 11.39 11.39 10.9 12 10.9 Z\" />\n\t\t\t\t<path\n\t\t\t\t\tandroid:name=\"compass\"\n\t\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\t\tandroid:pathData=\"M 6.5 17.5 L 14.01 14.01 L 17.5 6.5 L 9.99 9.99 L 6.5 17.5 Z M 12 10.9 C 12.61 10.9 13.1 11.39 13.1 12 C 13.1 12.61 12.61 13.1 12 13.1 C 11.39 13.1 10.9 12.61 10.9 12 C 10.9 11.39 11.39 10.9 12 10.9 Z\" />\n\t\t\t\t<group\n\t\t\t\t\tandroid:name=\"ring_parent\"\n\t\t\t\t\tandroid:pivotX=\"12\"\n\t\t\t\t\tandroid:pivotY=\"12\"\n\t\t\t\t\tandroid:scaleX=\"0.833\"\n\t\t\t\t\tandroid:scaleY=\"0.833\">\n\t\t\t\t\t<path\n\t\t\t\t\t\tandroid:name=\"ring\"\n\t\t\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\t\t\tandroid:pathData=\"M 12 0 C 8.819 0 5.765 1.265 3.515 3.515 C 1.265 5.765 0 8.819 0 12 C 0 15.181 1.265 18.235 3.515 20.485 C 5.765 22.735 8.819 24 12 24 C 15.181 24 18.235 22.735 20.485 20.485 C 22.735 18.235 24 15.181 24 12 C 24 8.819 22.735 5.765 20.485 3.515 C 18.235 1.265 15.181 0 12 0 Z M 12 21.6 C 9.455 21.6 7.012 20.588 5.212 18.788 C 3.412 16.988 2.4 14.545 2.4 12 C 2.4 9.455 3.412 7.012 5.212 5.212 C 7.012 3.412 9.455 2.4 12 2.4 C 14.545 2.4 16.988 3.412 18.788 5.212 C 20.588 7.012 21.6 9.455 21.6 12 C 21.6 14.545 20.588 16.988 18.788 18.788 C 16.988 20.588 14.545 21.6 12 21.6 Z\"\n\t\t\t\t\t\tandroid:strokeAlpha=\"0\" />\n\t\t\t\t</group>\n\t\t\t</group>\n\t\t</vector>\n\t</aapt:attr>\n\t<target android:name=\"compass\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<objectAnimator\n\t\t\t\tandroid:duration=\"@integer/config_defaultAnimTime\"\n\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\tandroid:propertyName=\"pathData\"\n\t\t\t\tandroid:valueFrom=\"M 12 2 C 6.48 2 2 6.48 2 12 C 2 17.141 5.886 21.38 10.878 21.938 C 11.247 21.979 11.621 22 12 22 C 17.52 22 22 17.52 22 12 C 22 6.48 17.52 2 12 2 L 12 2 M 12 10.9 C 11.695 10.9 11.42 11.023 11.221 11.221 C 11.023 11.42 10.9 11.695 10.9 12 C 10.9 12.61 11.39 13.1 12 13.1 C 12.61 13.1 13.1 12.61 13.1 12 C 13.1 11.39 12.61 10.9 12 10.9 L 12 10.9 M 14.19 14.19 L 6 18 L 6 18 L 9.81 9.81 L 18 6 L 14.19 14.19 L 14.19 14.19 M 12 12 L 12 12 L 12 12 L 12 12 L 12 12 L 12 12\"\n\t\t\t\tandroid:valueTo=\"M 9.99 9.99 C 9.408 11.242 8.827 12.493 8.245 13.745 C 7.663 14.997 7.082 16.248 6.5 17.5 C 6.5 17.5 6.5 17.5 6.5 17.5 C 9.003 16.337 11.507 15.173 14.01 14.01 C 15.173 11.507 16.337 9.003 17.5 6.5 L 9.99 9.99 M 12 10.9 C 11.39 10.9 10.9 11.39 10.9 12 C 10.9 12.305 11.023 12.58 11.221 12.779 C 11.42 12.977 11.695 13.1 12 13.1 C 12.61 13.1 13.1 12.61 13.1 12 C 13.1 11.39 12.61 10.9 12 10.9 L 12 10.9 M 12 12 L 12 12 L 12 12 L 12 12 L 12 12 L 12 12 L 12 12 M 12 12 L 12 12 L 12 12 L 12 12 L 12 12 L 12 12\"\n\t\t\t\tandroid:valueType=\"pathType\" />\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"main_parent\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<objectAnimator\n\t\t\t\tandroid:duration=\"@integer/config_defaultAnimTime\"\n\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\tandroid:propertyName=\"rotation\"\n\t\t\t\tandroid:valueFrom=\"180\"\n\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"group\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"66\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\t\tandroid:propertyName=\"scaleX\"\n\t\t\t\t\tandroid:valueFrom=\"1\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/decelerate_interpolator\"\n\t\t\t\t\tandroid:propertyName=\"scaleX\"\n\t\t\t\t\tandroid:startOffset=\"66\"\n\t\t\t\t\tandroid:valueFrom=\"1\"\n\t\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"66\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:valueFrom=\"1\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/decelerate_interpolator\"\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:startOffset=\"66\"\n\t\t\t\t\tandroid:valueFrom=\"1\"\n\t\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n</animated-vector>"
  },
  {
    "path": "app/src/main/res/drawable/avd_favourites_enter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:aapt=\"http://schemas.android.com/aapt\">\n\t<aapt:attr name=\"android:drawable\">\n\t\t<vector\n\t\t\tandroid:name=\"vector\"\n\t\t\tandroid:width=\"24dp\"\n\t\t\tandroid:height=\"24dp\"\n\t\t\tandroid:viewportWidth=\"24\"\n\t\t\tandroid:viewportHeight=\"24\">\n\t\t\t<path\n\t\t\t\tandroid:name=\"path\"\n\t\t\t\tandroid:pathData=\"M 16.5 3 C 14.76 3 13.09 3.81 12 5.09 C 10.91 3.81 9.24 3 7.5 3 C 4.42 3 2 5.42 2 8.5 C 2 12.28 5.4 15.36 10.55 20.04 L 12 21.35 L 13.45 20.03 C 18.6 15.36 22 12.28 22 8.5 C 22 5.42 19.58 3 16.5 3 Z M 12.1 18.55 L 12 18.65 L 11.9 18.55 C 7.14 14.24 4 11.39 4 8.5 C 4 6.5 5.5 5 7.5 5 C 9.04 5 10.54 5.99 11.07 7.36 L 12.94 7.36 C 13.46 5.99 14.96 5 16.5 5 C 18.5 5 20 6.5 20 8.5 C 20 11.39 16.86 14.24 12.1 18.55 Z\"\n\t\t\t\tandroid:fillColor=\"#000000\"/>\n\t\t</vector>\n\t</aapt:attr>\n\t<target android:name=\"path\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<objectAnimator\n\t\t\t\tandroid:propertyName=\"pathData\"\n\t\t\t\tandroid:duration=\"300\"\n\t\t\t\tandroid:valueFrom=\"M 12 21.35 L 10.55 20.04 C 5.4 15.36 2 12.28 2 8.5 C 2 5.42 4.42 3 7.5 3 C 9.24 3 10.91 3.81 12 5.09 C 13.09 3.81 14.76 3 16.5 3 L 16.5 3 C 19.58 3 22 5.42 22 8.5 C 22 12.28 18.6 15.36 13.45 20.03 L 12.725 20.69 L 12 21.35 M 11.07 7.36 C 10.54 5.99 9.04 5 7.5 5 C 5.5 5 4 6.5 4 8.5 C 4 11.39 7.14 14.24 11.9 18.55 L 12 18.65 L 12.1 18.55 L 12.1 18.55 C 16.86 14.24 20 11.39 20 8.5 C 20 6.5 18.5 5 16.5 5 C 14.96 5 13.46 5.99 12.94 7.36 L 11.07 7.36\"\n\t\t\t\tandroid:valueTo=\"M 12 21.35 L 10.55 20.03 C 5.4 15.36 2 12.28 2 8.5 C 2 5.42 4.42 3 7.5 3 C 9.24 3 10.91 3.81 12 5.09 C 13.09 3.81 14.76 3 16.5 3 L 16.5 3 C 19.58 3 22 5.42 22 8.5 C 22 12.28 18.6 15.36 13.45 20.04 L 12 21.35 L 12 21.35 M 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 L 12 11.825 L 12 11.825 L 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 L 12 11.825\"\n\t\t\t\tandroid:valueType=\"pathType\"\n\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n\t\t</aapt:attr>\n\t</target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/avd_favourites_leave.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:aapt=\"http://schemas.android.com/aapt\">\n\t<aapt:attr name=\"android:drawable\">\n\t\t<vector\n\t\t\tandroid:name=\"vector\"\n\t\t\tandroid:width=\"24dp\"\n\t\t\tandroid:height=\"24dp\"\n\t\t\tandroid:viewportWidth=\"24\"\n\t\t\tandroid:viewportHeight=\"24\">\n\t\t\t<path\n\t\t\t\tandroid:name=\"path\"\n\t\t\t\tandroid:pathData=\"M 16.5 3 C 14.76 3 13.09 3.81 12 5.09 C 10.91 3.81 9.24 3 7.5 3 C 4.42 3 2 5.42 2 8.5 C 2 12.28 5.4 15.36 10.55 20.04 L 12 21.35 L 13.45 20.03 C 18.6 15.36 22 12.28 22 8.5 C 22 5.42 19.58 3 16.5 3 Z M 12.1 18.55 L 12 18.65 L 11.9 18.55 C 7.14 14.24 4 11.39 4 8.5 C 4 6.5 5.5 5 7.5 5 C 9.04 5 10.54 5.99 11.07 7.36 L 12.94 7.36 C 13.46 5.99 14.96 5 16.5 5 C 18.5 5 20 6.5 20 8.5 C 20 11.39 16.86 14.24 12.1 18.55 Z\"\n\t\t\t\tandroid:fillColor=\"#000000\"/>\n\t\t</vector>\n\t</aapt:attr>\n\t<target android:name=\"path\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<objectAnimator\n\t\t\t\tandroid:propertyName=\"pathData\"\n\t\t\t\tandroid:duration=\"300\"\n\t\t\t\tandroid:valueFrom=\"M 16.5 3 C 14.76 3 13.09 3.81 12 5.09 C 10.91 3.81 9.24 3 7.5 3 C 4.42 3 2 5.42 2 8.5 C 2 12.28 5.4 15.36 10.55 20.03 L 12 21.35 L 12 21.35 L 13.45 20.04 C 18.6 15.36 22 12.28 22 8.5 C 22 5.42 19.58 3 16.5 3 L 16.5 3 M 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 L 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 C 12 11.825 12 11.825 12 11.825 L 12 11.825\"\n\t\t\t\tandroid:valueTo=\"M 16.5 3 C 14.76 3 13.09 3.81 12 5.09 C 10.91 3.81 9.24 3 7.5 3 C 4.42 3 2 5.42 2 8.5 C 2 12.28 5.4 15.36 10.55 20.04 L 11.275 20.695 L 12 21.35 L 13.45 20.03 C 18.6 15.36 22 12.28 22 8.5 C 22 5.42 19.58 3 16.5 3 L 16.5 3 M 12.1 18.55 C 12.067 18.583 12.033 18.617 12 18.65 C 11.967 18.617 11.933 18.583 11.9 18.55 C 7.14 14.24 4 11.39 4 8.5 C 4 6.5 5.5 5 7.5 5 C 9.04 5 10.54 5.99 11.07 7.36 L 12.94 7.36 C 13.46 5.99 14.96 5 16.5 5 C 18.5 5 20 6.5 20 8.5 C 20 11.39 16.86 14.24 12.1 18.55 L 12.1 18.55\"\n\t\t\t\tandroid:valueType=\"pathType\"\n\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n\t\t</aapt:attr>\n\t</target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/avd_feed_enter.xml",
    "content": "<animated-vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:pathData=\"M 4 17.8 C 4 17.3 4.1 16.9 4.4 16.5 C 4.7 16.1 5.1 15.9 5.5 15.7 C 5.9 15.6 6.4 15.6 6.8 15.7 C 7.2 15.8 7.6 16.1 7.9 16.5 C 8.2 16.9 8.3 17.3 8.3 17.8 C 8.3 18.4 8.1 18.9 7.7 19.3 C 7.3 19.7 6.7 19.9 6.2 19.9 C 5.7 19.9 5 19.8 4.6 19.4 C 4.2 19 4 18.4 4 17.8\"\n                android:fillColor=\"#000\"\n                android:strokeWidth=\"1\"/>\n            <path\n                android:name=\"path_1\"\n                android:pathData=\"M 4 10.1 L 4 12.9 C 7.9 12.9 11.1 16.1 11.1 20 L 13.9 20 C 13.9 14.5 9.5 10.1 4 10.1 Z\"\n                android:fillColor=\"#000\"\n                android:strokeWidth=\"1\"/>\n            <path\n                android:name=\"path_3\"\n                android:pathData=\"M 4.9 16.5 L 4.9 17.2 C 5.9 17.2 6.7 18 6.7 19 L 7.4 19 C 7.5 17.7 6.3 16.5 4.9 16.5 Z\"\n                android:fillColor=\"#000000\"/>\n            <path\n                android:name=\"path_2\"\n                android:pathData=\"M 4 4.4 L 4 7.2 C 11 7.2 16.7 12.9 16.7 19.9 L 19.5 19.9 C 19.6 11.4 12.6 4.4 4 4.4 Z\"\n                android:fillColor=\"#000000\"/>\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@integer/config_defaultAnimTime\"\n                android:valueFrom=\"M 4 10.1 L 4 12.9 C 7.9 12.9 11.1 16.1 11.1 20 L 13.9 20 C 13.9 14.5 9.5 10.1 4 10.1 Z\"\n                android:valueTo=\"M 4 4.4 L 4 7.2 C 11 7.2 16.7 12.9 16.7 19.9 L 19.5 19.9 C 19.6 11.4 12.6 4.4 4 4.4 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n    <target android:name=\"path_3\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@integer/config_defaultAnimTime\"\n                android:valueFrom=\"M 4.9 16.5 L 4.9 17.2 C 5.9 17.2 6.7 18 6.7 19 L 7.4 19 C 7.5 17.7 6.3 16.5 4.9 16.5 Z\"\n                android:valueTo=\"M 4 10.1 L 4 12.9 C 7.9 12.9 11.1 16.1 11.1 20 L 13.9 20 C 13.9 14.5 9.5 10.1 4 10.1 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/avd_history_enter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:aapt=\"http://schemas.android.com/aapt\">\n\t<aapt:attr name=\"android:drawable\">\n\t\t<vector\n\t\t\tandroid:name=\"history_enter\"\n\t\t\tandroid:width=\"24dp\"\n\t\t\tandroid:height=\"24dp\"\n\t\t\tandroid:viewportWidth=\"24\"\n\t\t\tandroid:viewportHeight=\"24\">\n\t\t\t<path\n\t\t\t\tandroid:name=\"clock\"\n\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\tandroid:pathData=\"M 12 8 L 12 13 L 16.28 15.54 L 17 14.33 L 13.5 12.25 L 13.5 8 L 12 8 Z\" />\n\t\t\t<group\n\t\t\t\tandroid:name=\"arrow_head\"\n\t\t\t\tandroid:pivotX=\"13\"\n\t\t\t\tandroid:pivotY=\"12\">\n\t\t\t\t<path\n\t\t\t\t\tandroid:name=\"arrow\"\n\t\t\t\t\tandroid:fillColor=\"#000000\"\n\t\t\t\t\tandroid:pathData=\"M 13 3 C 8.03 3 4 7.03 4 12 L 1 12 L 4.89 15.89 L 4.96 16.03 L 9 12 L 6 12 C 6 8.13 9.13 5 13 5 C 16.87 5 20 8.13 20 12 C 20 15.87 16.87 19 13 19 C 11.07 19 9.32 18.21 8.06 16.94 L 6.64 18.36 C 8.27 19.99 10.51 21 13 21 C 17.97 21 22 16.97 22 12 C 22 7.03 17.97 3 13 3 Z M 13 3 Z\" />\n\t\t\t</group>\n\t\t</vector>\n\t</aapt:attr>\n\t<target android:name=\"arrow_head\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<objectAnimator\n\t\t\t\tandroid:duration=\"500\"\n\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\"\n\t\t\t\tandroid:propertyName=\"rotation\"\n\t\t\t\tandroid:valueFrom=\"360\"\n\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\tandroid:valueType=\"floatType\" />\n\t\t</aapt:attr>\n\t</target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/avd_splash.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector xmlns:tools=\"http://schemas.android.com/tools\"\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:aapt=\"http://schemas.android.com/aapt\">\n\t<aapt:attr name=\"android:drawable\">\n\t\t<vector\n\t\t\tandroid:name=\"splash\"\n\t\t\tandroid:width=\"320dp\"\n\t\t\tandroid:height=\"320dp\"\n\t\t\tandroid:viewportWidth=\"320\"\n\t\t\tandroid:viewportHeight=\"320\">\n\t\t\t<group\n\t\t\t\tandroid:name=\"scaleme\"\n\t\t\t\tandroid:pivotX=\"160\"\n\t\t\t\tandroid:pivotY=\"160\"\n\t\t\t\tandroid:scaleX=\"1\"\n\t\t\t\tandroid:scaleY=\"1\">\n\t\t\t\t<group android:name=\"bg\">\n\t\t\t\t\t<path\n\t\t\t\t\t\tandroid:name=\"circle\"\n\t\t\t\t\t\tandroid:pathData=\"M 160 110 C 146.744 110 134.018 115.271 124.645 124.645 C 115.271 134.018 110 146.744 110 160 C 110 173.256 115.271 185.982 124.645 195.355 C 134.018 204.729 146.744 210 160 210 C 173.256 210 185.982 204.729 195.355 195.355 C 204.729 185.982 210 173.256 210 160 C 210 146.744 204.729 134.018 195.355 124.645 C 185.982 115.271 173.256 110 160 110 Z\"\n\t\t\t\t\t\tandroid:fillColor=\"@color/m3_sys_color_dynamic_light_primary_container\"\n\t\t\t\t\t\tandroid:fillAlpha=\"0\"\n\t\t\t\t\t\tandroid:strokeWidth=\"1\" />\n\t\t\t\t</group>\n\t\t\t\t<group\n\t\t\t\t\tandroid:name=\"fg\"\n\t\t\t\t\tandroid:pivotX=\"160\"\n\t\t\t\t\tandroid:pivotY=\"160\"\n\t\t\t\t\tandroid:scaleX=\"0\"\n\t\t\t\t\tandroid:scaleY=\"0\">\n\t\t\t\t\t<clip-path\n\t\t\t\t\t\tandroid:name=\"mask\"\n\t\t\t\t\t\tandroid:pathData=\"M 160 60 C 133.489 60 108.036 70.543 89.289 89.289 C 70.543 108.036 60 133.489 60 160 C 60 186.511 70.543 211.964 89.289 230.711 C 108.036 249.457 133.489 260 160 260 C 186.511 260 211.964 249.457 230.711 230.711 C 249.457 211.964 260 186.511 260 160 C 260 133.489 249.457 108.036 230.711 89.289 C 211.964 70.543 186.511 60 160 60 Z\" />\n\t\t\t\t\t<group\n\t\t\t\t\t\tandroid:name=\"totoro_group\"\n\t\t\t\t\t\tandroid:scaleX=\"0.73903004444\"\n\t\t\t\t\t\tandroid:scaleY=\"0.73903004444\">\n\t\t\t\t\t\t<clip-path android:pathData=\"M0.81,0.07h432v432h-432z\" />\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tandroid:name=\"totoro\"\n\t\t\t\t\t\t\tandroid:pathData=\"M242.26,418.58c-44.77,0 -89.54,0 -134.31,0c-6.96,0 -7.12,-0.56 -6.31,-7.57c6.75,-58.64 31.14,-110.6 61.45,-160.07c6.71,-10.95 9.45,-22.06 11.05,-34.22c1.43,-10.89 5.32,-20.99 13.4,-28.93c5.67,-5.58 9.49,-5.72 14.67,0.29c6.42,7.45 12.41,15.28 18.2,23.23c2.01,2.76 3.88,4.35 7.39,4.3c9.86,-0.15 19.72,-0.13 29.58,-0.01c3.26,0.04 5.21,-1.26 7.14,-3.9c6.02,-8.18 12.19,-16.28 18.74,-24.04c4.56,-5.41 8.36,-5.29 13.65,-0.51c8.35,7.53 11.83,17.58 13.9,28.16c1.14,5.82 1.07,11.88 2.18,17.71c0.61,3.18 2.21,6.32 3.97,9.1c19.34,30.51 35.86,62.44 48.61,96.27c8.91,23.64 15.25,47.93 18.01,73.09c0.72,6.6 0.28,7.1 -6.53,7.1C332.12,418.59 287.19,418.58 242.26,418.58zM172.46,404.96c0.23,-0.04 1.58,0.09 2.4,-0.51c8.32,-6.01 16.3,-5.48 24.88,-0.06c3.28,2.07 7.64,0.92 10.26,-2.87c2.4,-3.47 2.93,-7.99 -0.37,-10.11c-6.96,-4.48 -14.69,-7.79 -22.16,-11.45c-0.71,-0.35 -1.97,0.02 -2.8,0.42c-6.1,2.95 -12.31,5.72 -18.16,9.11c-3.47,2.01 -4.86,5.62 -3.44,9.68C164.45,403.11 167.3,405.15 172.46,404.96zM218.13,370.48c-0.72,7.32 5.26,12.42 10.7,10.65c3.5,-1.14 6.64,-3.36 10.15,-4.45c2.19,-0.68 4.93,-0.73 7.1,-0.05c3.5,1.09 6.63,3.81 10.14,4.31c2.71,0.39 6.58,-0.8 8.42,-2.72c1.69,-1.77 2.38,-5.74 1.7,-8.26c-1.66,-6.13 -21.82,-15 -27.76,-12.78c-0.3,0.11 -0.6,0.25 -0.88,0.39c-4.76,2.37 -9.72,4.42 -14.2,7.23C220.99,366.38 219.28,369.23 218.13,370.48zM312.12,405.04c5.83,-0.02 8.75,-2.02 9.96,-6c1.29,-4.24 -0.29,-7.83 -4,-9.92c-5.46,-3.07 -11.09,-5.89 -16.87,-8.3c-1.97,-0.82 -4.88,-0.73 -6.87,0.1c-5.49,2.28 -10.81,5.01 -16.03,7.87c-4.63,2.54 -6.06,7.19 -3.97,11.49c2.04,4.18 6.73,5.95 11.55,4.09c2.69,-1.04 5.36,-2.29 7.8,-3.82c3.12,-1.96 5.81,-1.75 8.95,0.07C306.06,402.61 309.82,403.99 312.12,405.04zM186.6,262.09c0.05,8.66 5.72,15.1 14.55,16.52c7.21,1.16 15.06,-3.96 17.57,-11.47c2.13,-6.36 0.68,-9.3 -5.95,-10.15c-3.72,-0.47 -4.65,-1.86 -5.04,-5.35c-0.62,-5.66 -3.07,-7 -8.67,-5.72C191.46,247.66 186.55,254.03 186.6,262.09zM282.02,278.75c7.26,0.03 14.41,-5.46 16.13,-12.38c1.46,-5.89 0.26,-8.38 -5.73,-9.23c-4.07,-0.58 -5.45,-2.48 -5.77,-6.12c-0.33,-3.76 -2.15,-6.05 -6.28,-5.61c-8.6,0.91 -15.51,8.95 -14.97,17.39C265.96,271.4 273.59,278.72 282.02,278.75zM259.29,278.52c-0.09,-5.33 -3.97,-9.16 -8.9,-9.28c-5.17,-0.13 -10.35,0 -15.52,-0.04c-3.82,-0.03 -6.54,1.76 -8.08,5.12c-1.56,3.4 -0.95,6.7 1.57,9.44c2.62,2.85 5.33,5.66 8.23,8.22c3.42,3.02 7.46,3.55 11.05,0.59c3.59,-2.97 6.8,-6.44 9.9,-9.93C258.63,281.4 258.91,279.45 259.29,278.52z\"\n\t\t\t\t\t\t\tandroid:fillType=\"evenOdd\"\n\t\t\t\t\t\t\tandroid:fillColor=\"@color/m3_sys_color_dynamic_light_primary\"\n\t\t\t\t\t\t\tandroid:fillAlpha=\"0\"\n\t\t\t\t\t\t\tandroid:strokeWidth=\"1\"\n\t\t\t\t\t\t\ttools:targetApi=\"s\" />\n\t\t\t\t\t</group>\n\t\t\t\t</group>\n\t\t\t</group>\n\t\t</vector>\n\t</aapt:attr>\n\t<target android:name=\"circle\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"pathData\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:valueFrom=\"M 160 110 C 146.744 110 134.018 115.271 124.645 124.645 C 115.271 134.018 110 146.744 110 160 C 110 173.256 115.271 185.982 124.645 195.355 C 134.018 204.729 146.744 210 160 210 C 173.256 210 185.982 204.729 195.355 195.355 C 204.729 185.982 210 173.256 210 160 C 210 146.744 204.729 134.018 195.355 124.645 C 185.982 115.271 173.256 110 160 110 Z\"\n\t\t\t\t\tandroid:valueTo=\"M 160 60 C 133.489 60 108.036 70.543 89.289 89.289 C 70.543 108.036 60 133.489 60 160 C 60 186.511 70.543 211.964 89.289 230.711 C 108.036 249.457 133.489 260 160 260 C 186.511 260 211.964 249.457 230.711 230.711 C 249.457 211.964 260 186.511 260 160 C 260 133.489 249.457 108.036 230.711 89.289 C 211.964 70.543 186.511 60 160 60 Z\"\n\t\t\t\t\tandroid:valueType=\"pathType\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/overshoot_interpolator\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"fillAlpha\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"totoro_group\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:propertyName=\"translateY\"\n\t\t\t\t\tandroid:valueFrom=\"160\"\n\t\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:valueFrom=\"0.5\"\n\t\t\t\t\tandroid:valueTo=\"0.73903004444\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"totoro\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"fillAlpha\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"fg\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"scaleX\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:valueFrom=\"0.5\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/overshoot_interpolator\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:valueFrom=\"0.5\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/overshoot_interpolator\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_appwidget_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\t<corners android:radius=\"@dimen/appwidget_corner_radius_inner\" />\n\t<solid android:color=\"?android:panelColorBackground\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_appwidget_root.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\t<corners android:radius=\"@dimen/appwidget_corner_radius_background\" />\n\t<solid android:color=\"?android:colorBackground\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_badge_accent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"oval\">\n\t<solid android:color=\"?colorAccent\" />\n\t<padding\n\t\tandroid:bottom=\"2dp\"\n\t\tandroid:left=\"2dp\"\n\t\tandroid:right=\"2dp\"\n\t\tandroid:top=\"2dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_badge_default.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"oval\">\n\t<solid android:color=\"?attr/colorTertiary\" />\n\t<padding\n\t\tandroid:bottom=\"2dp\"\n\t\tandroid:left=\"2dp\"\n\t\tandroid:right=\"2dp\"\n\t\tandroid:top=\"2dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_badge_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"oval\">\n\t<solid android:color=\"?colorBackgroundFloating\" />\n\t<stroke\n\t\tandroid:width=\"1dp\"\n\t\tandroid:color=\"?colorOutline\" />\n\t<padding\n\t\tandroid:bottom=\"2dp\"\n\t\tandroid:left=\"2dp\"\n\t\tandroid:right=\"2dp\"\n\t\tandroid:top=\"2dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\n\t<corners android:radius=\"12dp\" />\n\t<solid android:color=\"?colorSurface\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_chip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\n\t<corners android:radius=\"4dp\" />\n\n\t<solid android:color=\"?colorSurface\" />\n\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_circle_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:color=\"@color/selector_overlay\">\n\t<item>\n\t\t<shape android:shape=\"oval\">\n\t\t\t<solid android:color=\"?colorSurface\" />\n\t\t</shape>\n\t</item>\n\t<item android:id=\"@android:id/mask\">\n\t\t<shape android:shape=\"oval\">\n\t\t\t<solid android:color=\"@color/selector_overlay\" />\n\t\t</shape>\n\t</item>\n</ripple>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_list_icons.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\n\t<corners\n\t\tandroid:bottomRightRadius=\"4dp\"\n\t\tandroid:topRightRadius=\"4dp\" />\n\t<solid android:color=\"?colorBackgroundFloating\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_reader_indicator.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\t<corners android:radius=\"16dp\" />\n\t<solid android:color=\"@color/dim\" />\n\t<padding\n\t\tandroid:bottom=\"8dp\"\n\t\tandroid:left=\"12dp\"\n\t\tandroid:right=\"12dp\"\n\t\tandroid:top=\"8dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_rounded_square.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\n\t<corners\n\t\tandroid:radius=\"4dp\" />\n\n\t<solid android:color=\"?colorSurface\" />\n\n\t<size\n\t\tandroid:width=\"12dp\"\n\t\tandroid:height=\"12dp\" />\n\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_rounded_transparency.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\n\t<corners android:radius=\"4dp\" />\n\n\t<solid android:color=\"@color/bg_background_transparency\" />\n\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_search_suggestion.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item android:drawable=\"?android:windowBackground\" />\n\t<item\n\t\tandroid:drawable=\"@drawable/divider_horizontal\"\n\t\tandroid:gravity=\"top\" />\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_tab_pill.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item\n\t\tandroid:bottom=\"5dp\"\n\t\tandroid:left=\"5dp\"\n\t\tandroid:right=\"5dp\"\n\t\tandroid:top=\"5dp\">\n\t\t<shape android:shape=\"rectangle\">\n\t\t\t<solid android:color=\"@android:color/white\" />\n\t\t\t<corners\n\t\t\t\tandroid:bottomLeftRadius=\"@dimen/list_selector_corner\"\n\t\t\t\tandroid:bottomRightRadius=\"@dimen/list_selector_corner\"\n\t\t\t\tandroid:topLeftRadius=\"@dimen/list_selector_corner\"\n\t\t\t\tandroid:topRightRadius=\"@dimen/list_selector_corner\" />\n\t\t</shape>\n\t</item>\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable/custom_selectable_item_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:color=\"?android:attr/colorControlHighlight\">\n\t<item\n\t\tandroid:id=\"@android:id/mask\">\n\t\t<shape android:shape=\"rectangle\">\n\t\t\t<corners android:radius=\"@dimen/list_selector_corner\" />\n\t\t\t<solid android:color=\"@color/selector_overlay\" />\n\t\t</shape>\n\t</item>\n</ripple>\n"
  },
  {
    "path": "app/src/main/res/drawable/divider_horizontal.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<size android:height=\"1dp\" />\n\t<solid android:color=\"?colorSurfaceDim\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/divider_transparent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<size\n\t\tandroid:width=\"8dp\"\n\t\tandroid:height=\"8dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/fastscroll_bubble.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\n\t<corners\n\t\tandroid:topLeftRadius=\"@dimen/fastscroll_bubble_radius\"\n\t\tandroid:topRightRadius=\"@dimen/fastscroll_bubble_radius\"\n\t\tandroid:bottomLeftRadius=\"@dimen/fastscroll_bubble_radius\"\n\t\tandroid:bottomRightRadius=\"8dp\" />\n\n\t<size\n\t\tandroid:height=\"@dimen/fastscroll_bubble_size\"\n\t\tandroid:width=\"@dimen/fastscroll_bubble_size\" />\n\n\t<padding\n\t\tandroid:left=\"@dimen/fastscroll_bubble_padding\"\n\t\tandroid:right=\"@dimen/fastscroll_bubble_padding\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/fastscroll_bubble_small.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:shape=\"rectangle\">\n\n\t<tools:solid android:color=\"#777777\" />\n\n\t<corners\n\t\tandroid:topLeftRadius=\"@dimen/fastscroll_bubble_radius_small\"\n\t\tandroid:topRightRadius=\"@dimen/fastscroll_bubble_radius_small\"\n\t\tandroid:bottomLeftRadius=\"@dimen/fastscroll_bubble_radius_small\"\n\t\tandroid:bottomRightRadius=\"8dp\" />\n\n\t<size\n\t\tandroid:height=\"@dimen/fastscroll_bubble_size_small\"\n\t\tandroid:width=\"@dimen/fastscroll_bubble_size_small\" />\n\n\t<padding\n\t\tandroid:left=\"@dimen/fastscroll_bubble_padding_small\"\n\t\tandroid:right=\"@dimen/fastscroll_bubble_padding_small\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/fastscroll_handle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:shape=\"rectangle\">\n\n\t<tools:solid android:color=\"#555555\" />\n\n\t<corners android:radius=\"@dimen/fastscroll_handle_radius\" />\n\n\t<size\n\t\tandroid:height=\"@dimen/fastscroll_handle_height\"\n\t\tandroid:width=\"@dimen/fastscroll_handle_width\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/fastscroll_track.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:shape=\"rectangle\">\n\n\t<tools:solid android:color=\"#CCCCCC\" />\n\n\t<size android:width=\"@dimen/fastscroll_track_width\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_pause.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M14,19H18V5H14M6,19H10V5H6V19Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_resume.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M8,5.14V19.14L19,12.14L8,5.14Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_skip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M16,18H18V6H16M6,18L14.5,12L6,6V18Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_add.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_alert_outline.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12 2L1 21h22M12 6l7.53 13H4.47M11 10v4h2v-4m-2 6v2h2v-2\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_anilist.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"18\"\n\tandroid:viewportHeight=\"18\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M4.758,2.234C3.178,6.758 1.582,11.271 0,15.785L3.706,15.785c0.265,-0.775 0.529,-1.55 0.801,-2.323 1.333,-0.042 2.672,-0.026 4.007,-0.01 0.306,0.637 0.481,1.385 0.735,2.065 -0.004,0.428 0.449,0.22 0.72,0.267 2.469,-0.006 4.938,0.012 7.407,-0.018 0.819,-0.122 0.581,-1.103 0.616,-1.685 -0.027,-0.612 0.207,-1.646 -0.655,-1.747 -1.672,-0.02 -3.345,-0.001 -5.018,-0.025 -0.031,-3.181 0.015,-6.364 -0.038,-9.543C12.064,1.997 11.13,2.269 10.542,2.221 9.88,2.238 8.762,2.097 8.844,3.079 8.844,3.375 8.878,4.149 8.701,3.441 8.415,3.087 8.586,2.088 7.959,2.216 6.892,2.222 5.824,2.2 4.758,2.234ZM6.614,6.971c0.381,1.142 0.772,2.291 1.155,3.436 -0.797,0.05 -1.605,0.018 -2.407,0.015 0.375,-1.249 0.766,-2.498 1.151,-3.747 0.034,0.099 0.069,0.198 0.101,0.296z\"\n\t\tandroid:strokeWidth=\"0.015281\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_app_update.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M17,1H7A2,2 0 0,0 5,3V21A2,2 0 0,0 7,23H17A2,2 0 0,0 19,21V3A2,2 0 0,0 17,1M17,19H7V5H17V19M16,13H13V8H11V13H8L12,17L16,13Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_appearance.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2C17.5,2 22,6 22,11A6,6 0 0,1 16,17H14.2C13.9,17 13.7,17.2 13.7,17.5C13.7,17.6 13.8,17.7 13.8,17.8C14.2,18.3 14.4,18.9 14.4,19.5C14.5,20.9 13.4,22 12,22M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C12.3,20 12.5,19.8 12.5,19.5C12.5,19.3 12.4,19.2 12.4,19.1C12,18.6 11.8,18.1 11.8,17.5C11.8,16.1 12.9,15 14.3,15H16A4,4 0 0,0 20,11C20,7.1 16.4,4 12,4M6.5,10C7.3,10 8,10.7 8,11.5C8,12.3 7.3,13 6.5,13C5.7,13 5,12.3 5,11.5C5,10.7 5.7,10 6.5,10M9.5,6C10.3,6 11,6.7 11,7.5C11,8.3 10.3,9 9.5,9C8.7,9 8,8.3 8,7.5C8,6.7 8.7,6 9.5,6M14.5,6C15.3,6 16,6.7 16,7.5C16,8.3 15.3,9 14.5,9C13.7,9 13,8.3 13,7.5C13,6.7 13.7,6 14.5,6M17.5,10C18.3,10 19,10.7 19,11.5C19,12.3 18.3,13 17.5,13C16.7,13 16,12.3 16,11.5C16,10.7 16.7,10 17.5,10Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_forward.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:autoMirrored=\"true\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_auth_key_large.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"48dp\"\n\tandroid:height=\"48dp\"\n\tandroid:tint=\"@color/error\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M21 18H15V15H13.3C12.2 17.4 9.7 19 7 19C3.1 19 0 15.9 0 12S3.1 5 7 5C9.7 5 12.2 6.6 13.3 9H24V15H21V18M17 16H19V13H22V11H11.9L11.7 10.3C11 8.3 9.1 7 7 7C4.2 7 2 9.2 2 12S4.2 17 7 17C9.1 17 11 15.7 11.7 13.7L11.9 13H17V16M7 15C5.3 15 4 13.7 4 12S5.3 9 7 9 10 10.3 10 12 8.7 15 7 15M7 11C6.4 11 6 11.4 6 12S6.4 13 7 13 8 12.6 8 12 7.6 11 7 11Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_auto_fix.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_backup_restore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M12,3A9,9 0 0,0 3,12H0L4,16L8,12H5A7,7 0 0,1 12,5A7,7 0 0,1 19,12A7,7 0 0,1 12,19C10.5,19 9.09,18.5 7.94,17.7L6.5,19.14C8.04,20.3 9.94,21 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M14,12A2,2 0 0,0 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_battery_outline.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M16,20H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_book_page.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M19 1l-5 5v11l5-4.5V1m2 4v13.5c-1.1-0.35-2.3-0.5-3.5-0.5-1.7 0-4.15 0.65-5.5 1.5V6c-1.45-1.1-3.55-1.5-5.5-1.5C4.55 4.5 2.45 4.9 1 6v14.65c0 0.25 0.25 0.5 0.5 0.5 0.1 0 0.15-0.05 0.25-0.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05 0.4 5.5 1.5 1.35-0.85 3.8-1.5 5.5-1.5 1.65 0 3.35 0.3 4.75 1.05 0.1 0.05 0.15 0.05 0.25 0.05 0.25 0 0.5-0.25 0.5-0.5V6c-0.6-0.45-1.25-0.75-2-1M10 18.41C8.75 18.09 7.5 18 6.5 18c-1.06 0-2.32 0.19-3.5 0.5V7.13C3.91 6.73 5.14 6.5 6.5 6.5c1.36 0 2.59 0.23 3.5 0.63v11.28z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bookmark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M17,18L12,15.82L7,18V5H17M17,3H7A2,2 0 0,0 5,5V21L12,18L19,21V5C19,3.89 18.1,3 17,3Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bookmark_added.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M9.47 9.65L8.06 11.07L11 14L16.19 8.82L14.78 7.4L11 11.18M17 3H7C5.9 3 5 3.9 5 5L5 21L12 18L19 21V5C19 3.9 18.1 3 17 3M17 18L12 15.82L7 18V5H17Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bookmark_checked.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- drawable/bookmark.xml -->\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M17,3H7A2,2 0 0,0 5,5V21L12,18L19,21V5C19,3.89 18.1,3 17,3Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bookmark_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_bookmark\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_bookmark_checked\"\n\t\tandroid:state_checked=\"true\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bot_large.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"48dp\"\n\tandroid:height=\"48dp\"\n\tandroid:tint=\"@color/error\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M17.5 15.5C17.5 16.61 16.61 17.5 15.5 17.5S13.5 16.61 13.5 15.5 14.4 13.5 15.5 13.5 17.5 14.4 17.5 15.5M8.5 13.5C7.4 13.5 6.5 14.4 6.5 15.5S7.4 17.5 8.5 17.5 10.5 16.61 10.5 15.5 9.61 13.5 8.5 13.5M23 15V18C23 18.55 22.55 19 22 19H21V20C21 21.11 20.11 22 19 22H5C3.9 22 3 21.11 3 20V19H2C1.45 19 1 18.55 1 18V15C1 14.45 1.45 14 2 14H3C3 10.13 6.13 7 10 7H11V5.73C10.4 5.39 10 4.74 10 4C10 2.9 10.9 2 12 2S14 2.9 14 4C14 4.74 13.6 5.39 13 5.73V7H14C17.87 7 21 10.13 21 14H22C22.55 14 23 14.45 23 15M21 16H19V14C19 11.24 16.76 9 14 9H10C7.24 9 5 11.24 5 14V16H3V17H5V20H19V17H21V16Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cancel_multiple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M18.54 9.88L17.12 8.47L15 10.59L12.88 8.47L11.47 9.88L13.59 12L11.47 14.12L12.88 15.54L15 13.41L17.12 15.54L18.54 14.12L16.41 12M2 12C2 9.21 3.64 6.8 6 5.68V3.5C2.5 4.76 0 8.09 0 12S2.5 19.24 6 20.5V18.32C3.64 17.2 2 14.79 2 12M15 3C10.04 3 6 7.04 6 12S10.04 21 15 21 24 16.96 24 12 19.96 3 15 3M15 19C11.14 19 8 15.86 8 12S11.14 5 15 5 22 8.14 22 12 18.86 19 15 19Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_check.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_clear_all.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M5 13h14v-2H5v2zm-2 4h14v-2H3v2zM7 7v2h14V7H7z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_current_chapter.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"16dp\"\n\tandroid:height=\"16dp\"\n\tandroid:tint=\"@color/common_green\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M8,5v14l11,-7z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_data_privacy.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M17 14.4C17.6 14.4 18.1 14.9 18.1 15.5S17.6 16.6 17 16.6 15.9 16.1 15.9 15.5 16.4 14.4 17 14.4M17 17.5C16.3 17.5 14.8 17.9 14.8 18.6C15.3 19.3 16.1 19.8 17 19.8S18.7 19.3 19.2 18.6C19.2 17.9 17.7 17.5 17 17.5M18 11.1V6.3L10.5 3L3 6.3V11.2C3 15.7 6.2 20 10.5 21C11.1 20.9 11.6 20.7 12.1 20.5C13.2 22 15 23 17 23C20.3 23 23 20.3 23 17C23 14 20.8 11.6 18 11.1M11 17C11 17.6 11.1 18.1 11.2 18.6C11 18.7 10.7 18.8 10.5 18.9C7.3 17.9 5 14.7 5 11.2V7.6L10.5 5.2L16 7.6V11.1C13.2 11.6 11 14 11 17M17 21C14.8 21 13 19.2 13 17S14.8 13 17 13 21 14.8 21 17 19.2 21 17 21Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_delete.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_delete_all.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M15,16H19V18H15V16M15,8H22V10H15V8M15,12H21V14H15V12M11,10V18H5V10H11M13,8H3V18A2,2 0 0,0 5,20H11A2,2 0 0,0 13,18V8M14,5H11L10,4H6L5,5H2V7H14V5Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_denied_large.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"48dp\"\n\tandroid:height=\"48dp\"\n\tandroid:tint=\"@color/error\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12 2c5.5 0 10 4.5 10 10s-4.5 10-10 10S2 17.5 2 12 6.5 2 12 2m0 2c-1.9 0-3.6 0.6-4.9 1.7l11.2 11.2c1-1.4 1.7-3.1 1.7-4.9 0-4.4-3.6-8-8-8m4.9 14.3L5.7 7.1C4.6 8.4 4 10.1 4 12c0 4.4 3.6 8 8 8 1.9 0 3.6-0.6 4.9-1.7z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_dice.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M19 5V19H5V5H19M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M7.5 6C6.7 6 6 6.7 6 7.5S6.7 9 7.5 9 9 8.3 9 7.5 8.3 6 7.5 6M16.5 15C15.7 15 15 15.7 15 16.5C15 17.3 15.7 18 16.5 18C17.3 18 18 17.3 18 16.5C18 15.7 17.3 15 16.5 15M16.5 6C15.7 6 15 6.7 15 7.5S15.7 9 16.5 9C17.3 9 18 8.3 18 7.5S17.3 6 16.5 6M12 10.5C11.2 10.5 10.5 11.2 10.5 12S11.2 13.5 12 13.5 13.5 12.8 13.5 12 12.8 10.5 12 10.5M7.5 15C6.7 15 6 15.7 6 16.5C6 17.3 6.7 18 7.5 18S9 17.3 9 16.5C9 15.7 8.3 15 7.5 15Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_disable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M22.11 21.46L2.39 1.73L1.11 3L4.06 5.95C2.78 7.63 2 9.72 2 12C2 17.5 6.5 22 12 22C14.28 22 16.37 21.23 18.05 19.94L20.84 22.73L22.11 21.46M12 20C7.58 20 4 16.42 4 12C4 10.27 4.56 8.68 5.5 7.38L16.62 18.5C15.32 19.45 13.73 20 12 20M8.17 4.97L6.72 3.5C8.25 2.56 10.06 2 12 2C17.5 2 22 6.5 22 12C22 13.94 21.44 15.75 20.5 17.28L19.03 15.83C19.65 14.69 20 13.39 20 12C20 7.58 16.42 4 12 4C10.61 4 9.31 4.35 8.17 4.97Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_discord.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M19.27,5.33C17.94,4.71 16.5,4.26 15,4c-0.03,0 -0.05,0.01 -0.07,0.03c-0.18,0.33 -0.39,0.76 -0.53,1.09c-1.61,-0.24 -3.22,-0.24 -4.8,0C9.46,4.78 9.25,4.36 9.06,4.03C9.05,4.01 9.02,4 8.99,4c-1.5,0.26 -2.93,0.71 -4.27,1.33c-0.01,0 -0.02,0.01 -0.03,0.02c-2.72,4.07 -3.47,8.03 -3.1,11.95c0,0.02 0.01,0.04 0.03,0.05c1.8,1.32 3.53,2.12 5.24,2.65c0.03,0.01 0.06,0 0.07,-0.02c0.4,-0.55 0.76,-1.13 1.07,-1.74c0.02,-0.04 0,-0.08 -0.04,-0.09c-0.57,-0.22 -1.11,-0.48 -1.64,-0.78c-0.04,-0.02 -0.04,-0.08 -0.01,-0.11c0.11,-0.08 0.22,-0.17 0.33,-0.25c0.02,-0.02 0.05,-0.02 0.07,-0.01c3.44,1.57 7.15,1.57 10.55,0c0.02,-0.01 0.05,-0.01 0.07,0.01c0.11,0.09 0.22,0.17 0.33,0.26c0.04,0.03 0.04,0.09 -0.01,0.11c-0.52,0.31 -1.07,0.56 -1.64,0.78c-0.04,0.01 -0.05,0.06 -0.04,0.09c0.32,0.61 0.68,1.19 1.07,1.74C17.07,20 17.1,20.01 17.13,20c1.72,-0.53 3.45,-1.33 5.25,-2.65c0.02,-0.01 0.03,-0.03 0.03,-0.05c0.44,-4.53 -0.73,-8.46 -3.1,-11.95C19.3,5.34 19.29,5.33 19.27,5.33zM8.52,14.91c-1.03,0 -1.89,-0.95 -1.89,-2.12s0.84,-2.12 1.89,-2.12c1.06,0 1.9,0.96 1.89,2.12C10.41,13.96 9.57,14.91 8.52,14.91zM15.49,14.91c-1.03,0 -1.89,-0.95 -1.89,-2.12s0.84,-2.12 1.89,-2.12c1.06,0 1.9,0.96 1.89,2.12C17.38,13.96 16.55,14.91 15.49,14.91z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_drawer_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_drawer_menu_open.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:autoMirrored=\"true\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"960\"\n\tandroid:viewportHeight=\"960\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M120,720v-80L640,640v80L120,720ZM784,680L584,480L784,280l56,56L696,480L840,624l-56,56ZM120,520v-80L520,440v80L120,520ZM120,320v-80L640,240v80L120,320Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_empty_common.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"200dp\"\n\tandroid:height=\"200dp\"\n\tandroid:viewportWidth=\"500\"\n\tandroid:viewportHeight=\"500\">\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M200,445.88c-1.58,3.6 -3.58,6.54 -4,9.7 -0.29,2.12 1.64,4.55 2.58,6.84 1.84,-1.27 4.24,-2.18 5.43,-3.9 2.12,-3.09 3.27,-6.83 5.34,-10 0.72,-1.1 2.94,-1.2 4.84,-0.43 -1.79,2.44 -4,4.7 -5.19,7.41 -0.6,1.4 0.14,4 1.2,5.26 0.57,0.68 3.12,-0.28 4.77,-0.53a1,1 0,0 0,0.54 -0.38c7.13,-9.57 16.13,-7.26 25.95,-5.13A261.12,261.12 0,0 0,320 459.44c1.54,-0.14 3.09,-0.19 5.34,-0.33 -0.91,2.6 -2.24,4.76 -2.26,6.93 0,1.88 1,4.69 2.39,5.39s3.95,-0.61 5.56,-1.7 2.5,-3 3.85,-4.47c1.19,-1.28 2.56,-2.39 4.12,-3.83a98.72,98.72 0,0 0,5.39 9.53c0.84,1.18 3.19,2.35 4.32,2s1.68,-2.69 2.21,-4.24a3.79,3.79 0,0 0,-0.18 -2.63c-4.4,-9.48 0.48,-13.75 8.41,-18.29 28.18,-16.1 43.15,-41.5 47.47,-73.07 5.15,-37.59 5.7,-75 -4.13,-112.3 -3.12,-11.84 -2.79,-24.63 -3.57,-37 -0.21,-3.41 1.11,-7 2.06,-10.39 6.64,-23.84 9.68,-47.38 15.1,-71.49 2.33,-10.33 1.65,-24.36 0,-35 -2.07,-13.4 -22.85,-28.78 -34,-21 -11.95,8.35 -18.48,27.88 -26.56,40 -10,15 -14.32,31 -26.06,46a167.77,167.77 0,0 0,-35.38 -4,228.48 228.48,0 0,0 2.45,-39.91c-0.72,-20.81 -2.72,-41.51 -13.66,-60.08 -0.79,-1.34 -1.51,-2.73 -2.4,-4 -8.22,-11.87 -18.6,-12.77 -26,-0.45a257.41,257.41 0,0 0,-21.3 44.53c-7.09,19.66 -11.89,40.15 -17.78,60.26a18,18 0,0 1,-3.06 6.49c-12.55,15.09 -20.19,32.55 -25,51.34 -3.57,13.84 -7.86,27.2 -14.33,40.17 -12.67,25.37 -13.33,52.94 -10.85,80.6 2,22.6 7,44.57 17,65.05 3.88,7.92 9.07,15.19 13.89,23.13 -2.19,1 -5,1.91 -7.23,3.54 -1.52,1.1 -2.27,3.25 -3.37,4.92 1.87,0.91 3.74,2.59 5.6,2.57C191.77,447.6 195.45,446.61 200,445.88ZM333.54,185c1.47,-1.9 2.9,-3.57 4.13,-5.38 9,-13.2 12.83,-27.13 21.83,-42.13 10.61,-17.68 14,-33 27,-43 6.87,-5.29 22.28,8.5 23,17a113.44,113.44 0,0 1,-2 32c-4.29,20 -4.35,40.82 -11.09,60.15 -7,20.06 -7.64,39.81 -1.74,59.76a189.23,189.23 0,0 1,7.25 47.46c0.95,27.06 1.21,54.12 -7,80.27 -9.82,31.12 -30.29,52.61 -62.63,58.56 -37.84,7 -75.6,3.14 -112.42,-8.38 -14.28,-4.47 -24.81,-13.11 -31.74,-26.78 -11.94,-23.56 -17.36,-48.66 -19,-74.68 -1.56,-24.54 0.81,-48.51 11.57,-71.22 4.64,-9.8 8.9,-20 11.61,-30.46 5.6,-21.63 12.88,-42.23 27.85,-59.4a14.07,14.07 0,0 0,2.65 -6.69c4,-24.13 11.65,-47.2 21.17,-69.61a271.67,271.67 0,0 1,16.47 -32.8c4.73,-7.94 9.65,-7.78 13.8,0.33 4.52,8.84 8.73,18.19 10.86,27.82a176.48,176.48 0,0 1,1.25 70.49c-0.7,3.81 -0.38,6.93 4.58,6.7A78.67,78.67 0,0 1,333.54 185Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M341.73,212.06a33.09,33.09 0,1 0,33.39 33C375.09,227 359.82,211.92 341.73,212.06ZM342.02,220.69c12.75,0 24.29,11.71 24.19,24.64 -0.1,12.64 -11.59,24.23 -23.94,24.14 -13.11,-0.09 -24.47,-11.33 -24.52,-24.24S329.12,220.73 342,220.69Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M238.3,258.3A30.42,30.42 0,0 0,268.53 228c0.1,-16.7 -14,-30.6 -30.84,-30.43a30.38,30.38 0,1 0,0.61 60.76ZM216.65,228c0.06,-11.42 10.08,-21.56 21.31,-21.56s21.56,10.38 21.52,21.5c0,11.28 -10.21,21.45 -21.48,21.48S216.59,239.32 216.65,228Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M338,246.27c3.37,-0.21 7.65,-1.22 7.84,-7.17 0.18,-5.73 -4.81,-11.6 -9.65,-11.66 -5.38,-0.06 -11.43,6 -11.16,11.15C325.28,243.09 330.13,246.31 338,246.27Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M232.6,231.45c5.29,-0.11 10.12,-4.56 10.24,-9.44 0.11,-4.56 -6.08,-9 -12.24,-8.78 -6,0.22 -8.84,5.54 -8.54,10.27S225.39,231.6 232.6,231.45Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M72.51,49c0.1,0.25 0.12,0.62 0.29,0.72 0.67,0.37 1.38,0.65 2.13,1L74.93,48.47c0.82,0.06 1.66,0.41 1.7,-0.78 0,-0.49 -0.13,-0.79 -0.67,-0.78l-0.47,0c-1.45,0 -1.44,0 -1.86,-1.43 -0.14,-0.44 -0.42,-0.83 -0.59,-1.26a11.6,11.6 0,0 1,-0.52 -1.71c0,-0.17 0.17,-0.56 0.3,-0.57a1.53,1.53 0,0 1,0.71 0.27l1,0.56a6.05,6.05 0,0 0,0.55 1c0.48,0.61 1,1.18 1.53,1.76s0.84,0.93 1.27,1.39a6.14,6.14 0,0 0,0.76 0.77,0.83 0.83,0 0,0 0.64,0.21c0.19,-0.06 0.45,-0.39 0.41,-0.53a5.1,5.1 0,0 0,-0.62 -1.46c-0.4,-0.61 -0.92,-1.16 -1.39,-1.74a6.75,6.75 0,0 1,-0.91 -1.25c-0.39,-0.82 -0.19,-1.16 0.72,-1.21 0.68,0 0.89,-0.34 1,-1 0.19,-1.08 0.54,-1.2 1.42,-0.5s1.83,1.54 2.77,2.28a1,1 0,0 0,0.74 0.19c0.15,0 0.26,-0.37 0.31,-0.59s-0.06,-0.63 0.08,-0.82 0.63,-0.44 0.74,-0.36c0.46,0.34 0.81,0.81 1.23,1.19a5.28,5.28 0,0 0,0.82 0.5c0.11,-0.31 0.4,-0.72 0.29,-0.92a14.18,14.18 0,0 0,-1.72 -2.82A1.74,1.74 0,0 1,85 37.11c0,-0.13 0.5,-0.31 0.57,-0.25a10.82,10.82 0,0 1,1.4 1.47,34.46 34.46,0 0,0 2.27,3.05 2,2 0,0 0,1.68 0.14c0.43,-0.14 0.22,-1.6 -0.19,-1.93a22.83,22.83 0,0 1,-2.52 -2.17c-0.28,-0.3 -0.13,-1 -0.18,-1.5 0.29,0 0.66,-0.06 0.85,0.08a22.35,22.35 0,0 1,1.84 1.67,12.21 12.21,0 0,1 0.91,1.11A18.48,18.48 0,0 1,93.14 41c0.25,0.47 0.51,0.75 1,0.58a1.36,1.36 0,0 0,0.6 -0.77,8.19 8.19,0 0,0 0.19,-1c0.22,-0.81 0.44,-0.73 1.21,-0.43s1.27,1 2.19,0.71c0.22,-0.06 0.61,0.3 0.86,0.54s0.83,0.41 1,-0.09c0.43,-1.81 0.76,-3.63 1.12,-5.45a1.94,1.94 0,0 1,2.1 1,3 3,0 0,0 0.5,0.58 4.74,4.74 0,0 0,0.46 -0.62c0.16,-0.3 0.19,-0.73 0.43,-0.91a2.27,2.27 0,0 1,1.23 -0.48c0.17,0 0.36,0.52 0.56,0.79s0.62,0.87 0.68,0.84a3.74,3.74 0,0 0,1.27 -1,25.23 25.23,0 0,0 1.36,-2.44 1.65,1.65 0,0 1,0.8 1.82c-0.23,1.11 -0.09,2.31 -1,3.23a0.81,0.81 0,0 0,-0.1 0.64,10.41 10.41,0 0,0 0.33,1c0.21,-0.26 0.44,-0.5 0.63,-0.77 0.55,-0.81 1.09,-1.63 1.62,-2.45 0.15,-0.23 0.24,-0.51 0.4,-0.73s0.46,-0.77 0.77,-0.85c0.5,-0.13 1.16,-0.24 1.36,0.58 0,0.08 0.61,0.13 0.63,0.08 0.5,-1.24 1,-2.5 1.41,-3.77 0,0 0,-0.11 0,-0.17a7.79,7.79 0,0 0,0.94 -0.28c0.3,-0.13 0.75,-0.54 0.84,-0.46 0.48,0.38 1.12,0.83 1.23,1.36a15.31,15.31 0,0 1,0 2.71c0,0.28 0.25,0.55 0.38,0.83a5.09,5.09 0,0 0,0.76 -0.52c0.57,-0.51 1.1,-1.06 1.66,-1.58a5.78,5.78 0,0 1,1.28 -1.06,2.88 2.88,0 0,1 1.35,-0.13c0.58,0 0.82,0.38 0.77,0.95a4.65,4.65 0,0 1,0 0.75c-0.21,1 -0.17,1.71 1.08,1.82a0.77,0.77 0,0 1,0.56 0.44c0.39,1.59 1.59,2 3,2.18 1,0.13 1.3,1 1,2.12a3.53,3.53 0,0 0,0 0.92,5 5,0 0,0 1.41,-0.2c0.54,-0.25 0.95,-0.82 1.5,-1a1.91,1.91 0,0 1,1.43 0.33c0.22,0.17 0.19,0.79 0.12,1.18s-0.35,0.5 -0.53,0.76c-0.37,0.53 -0.5,1 0.23,1.38a2.33,2.33 0,0 1,0.46 0.43c0.85,0.75 0.31,1.56 0.13,2.37a4.61,4.61 0,0 0,0 1.11,8.38 8.38,0 0,0 1.13,-0.23 4.73,4.73 0,0 0,0.81 -0.47c0.51,-0.3 0.85,-0.26 1.07,0.37 0.1,0.28 0.49,0.69 0.65,0.65a4.1,4.1 0,0 0,1.4 -0.7c0.58,-0.43 1,-1 1.66,-1.41a1.14,1.14 0,0 1,1.07 0.2,2.39 2.39,0 0,1 0.17,1.51 3.74,3.74 0,0 1,-1 1.85c-0.68,0.53 -0.82,1.26 -1.2,1.9s-0.92,1.31 -1.57,2.22c0.61,0 1.11,0.15 1.41,0a7.8,7.8 0,0 0,1.54 -1.34,2.43 2.43,0 0,1 2.67,-0.13 1.07,1.07 0,0 1,0.14 0.7,6 6,0 0,1 -1.63,4.13 4.58,4.58 0,0 0,-1 1.35c-0.14,0.33 0,0.79 0.07,1.34 0.81,-0.45 1.71,-0.31 1.87,-1.31 0,-0.12 0.28,-0.28 0.41,-0.27a0.55,0.55 0,0 1,0.35 0.36c0.1,0.36 0.11,0.75 0.21,1.11 0.05,0.18 0.21,0.48 0.32,0.48a1.25,1.25 0,0 0,0.61 -0.28,5.49 5.49,0 0,0 0.52,-0.44c0.15,0.22 0.38,0.42 0.43,0.66 0.17,0.77 0.24,1.45 1.39,1.23 0.64,-0.13 0.55,0.54 0.34,0.94 -0.42,0.83 -1,1.61 -1.33,2.48a2.24,2.24 0,0 0,0.05 1.55c0.13,0.33 1.37,-0.22 1.58,-0.72 0.09,-0.2 0.11,-0.52 0.25,-0.6a5.13,5.13 0,0 1,1.49 -0.54,1.54 1.54,0 0,1 0.42,1c-0.13,1.28 -0.16,2.65 -1.14,3.65 -0.51,0.52 -1.18,0.88 -1.75,1.36 -0.38,0.31 -0.71,0.7 -1.06,1.05 -0.73,0 -1.93,0.71 -2,1.33 0,0.2 0.33,0.64 0.51,0.63 0.87,0 1.82,0.34 2.57,-0.48a0.91,0.91 0,0 1,0.75 0.26c0.54,1 -0.2,2 -1.42,2.33 -0.86,0.25 -1.18,0.49 -1.07,1.44 0.21,1.78 0.17,1.8 2,1.79 0.48,0 1.32,0 1.36,0.23a6.08,6.08 0,0 1,0.06 2.29c0,0.22 -0.73,0.32 -1.11,0.5s-0.56,0.27 -0.6,0.46 0.19,0.48 0.38,0.65c0.37,0.35 0.8,0.65 1.19,1a0.51,0.51 0,0 1,0.19 0.31,16.5 16.5,0 0,1 0.19,2.32c0,0.75 -1.66,2 -2.43,2 -1,0 -1.93,0 -2.89,0a1,1 0,0 0,-1.19 1.14c0,0.7 0.08,1.43 1.1,1.34 0.37,0 0.74,0 1.11,0 0.54,0.34 1.07,0.72 1.63,1s0.59,0.68 0.09,1a1.29,1.29 0,0 0,-0.06 2.25,4.64 4.64,0 0,1 1.29,1.24 4.82,4.82 0,0 1,0.26 1.88c0,0.19 -0.21,0.55 -0.35,0.56s-0.48,-0.16 -0.7,-0.28c-0.95,-0.54 -1.87,-1.14 -2.85,-1.61 -0.22,-0.1 -0.64,0.21 -1,0.32a4,4 0,0 1,0.42 0.83,21.88 21.88,0 0,1 0.58,2.67c0,0.17 -0.49,0.61 -0.7,0.59a3.31,3.31 0,0 1,-1.6 -0.57c-0.91,-0.75 -1.67,-1.68 -2.57,-2.45a1.56,1.56 0,0 0,-1.22 -0.21c-0.19,0 -0.34,0.62 -0.35,1 0,0.68 0.07,1.37 0.11,2a1.65,1.65 0,0 1,-1.74 2c-0.31,0 -0.62,0 -0.94,0 -1.32,0 -1.29,0 -1.58,1.29a1.26,1.26 0,0 1,-0.67 0.71,5.74 5.74,0 0,1 -1.27,0.29c0,-0.1 0,-0.21 0,-0.28 -0.19,-0.29 -0.41,-0.57 -0.62,-0.85l-0.4,0.41c0.25,0.21 0.5,0.42 0.77,0.62a0.71,0.71 0,0 0,0.26 0.07l-0.37,1.79c-0.34,-0.3 -0.46,-0.52 -0.63,-0.55a1.68,1.68 0,0 0,-1 0,2.9 2.9,0 0,0 -0.9,1c-0.09,0.12 0,0.41 0.1,0.61s0.45,0.86 0.35,1c-0.44,0.46 -0.45,1.44 -1.51,1.22a3.75,3.75 0,0 1,-2.08 -1.67,20.78 20.78,0 0,1 -1.37,-2.16c-0.07,-0.11 -0.31,-0.22 -0.38,-0.18a1.21,1.21 0,0 0,-0.4 0.44c-0.23,0.39 -0.34,0.93 -0.67,1.16a0.88,0.88 0,0 1,-1.41 -0.32,8.26 8.26,0 0,0 -1.24,-1.48 1.76,1.76 0,0 0,-1.85 0.3c-0.72,1.28 -1.88,1 -3,0.81a1.86,1.86 0,0 0,-2.18 0.68c-0.07,0.09 -0.18,0.21 -0.28,0.22 -1.26,0.14 -1.73,0.88 -1.82,2.08a1.15,1.15 0,0 1,-1.32 1.06c-0.76,0 -0.81,-0.74 -0.76,-1.19a3.88,3.88 0,0 0,-1.06 -3.08c-0.79,-0.85 -1.78,-1.53 -2.67,-2.32 -0.59,-0.52 -1.52,0.28 -1.56,1.26 0,0.6 0,1.21 0,2.09 -0.36,-0.34 -0.51,-0.47 -0.64,-0.61 -0.49,-0.52 -0.85,-0.53 -1.23,0.14a1.24,1.24 0,0 1,-0.88 0.57c-0.34,0 -0.69,-0.32 -1,-0.51s-0.65,-0.38 -1,-0.55 -0.91,-0.41 -1.38,-0.59c-0.92,-0.38 -0.88,-1 -0.37,-1.74 -0.82,-0.33 -1.15,0.11 -1.25,0.7a12.62,12.62 0,0 0,-0.1 2,16.74 16.74,0 0,1 -0.06,2.13c0,0.29 -0.45,0.73 -0.7,0.74 -0.52,0 -1.2,0 -1.26,-0.79 -0.09,-1.24 -0.19,-2.49 -0.21,-3.73s-0.56,-1.81 -1.41,-1.29a2.18,2.18 0,0 0,-0.69 1c-0.24,0.5 -0.41,0.64 -0.86,0.16a5.07,5.07 0,0 0,-1.33 -1c-0.12,-0.07 -0.6,0.17 -0.6,0.28 0,0.9 -0.83,1.22 -1.28,1.8 -0.09,0.11 -0.4,0.2 -0.48,0.14a0.65,0.65 0,0 1,-0.24 -0.52,3.22 3.22,0 0,1 0.54,-1c0.37,-0.44 0.43,-0.79 0,-1.2s-1,-1 -1.49,-1.52a1.32,1.32 0,0 0,-2.07 -0.07c-0.22,0.21 -0.42,0.49 -0.69,0.58 -1.18,0.4 -1.67,0 -1.66,-1.25a3.53,3.53 0,0 0,0 -0.65c-0.08,-0.4 -0.2,-0.78 -0.31,-1.17a4.43,4.43 0,0 0,-1.53 0.48,1.72 1.72,0 0,1 -2.29,-0.08 3.07,3.07 0,0 1,-1.11 -2.2c0,-0.19 -0.44,-0.42 -0.69,-0.44a2.34,2.34 0,0 1,-2.07 -2c0,-0.43 -0.05,-0.87 -0.09,-1.31 -0.07,-0.91 0.11,-1.59 1.23,-1.69 0.25,0 0.46,-0.48 0.69,-0.73l0.74,-0.8a1.09,1.09 0,0 0,-1.71 0,3.13 3.13,0 0,1 -3.58,0.12c0,-0.08 0.07,-0.19 0.14,-0.22A24.23,24.23 0,0 1,76 90c0.91,-0.34 0.57,-1 0.57,-1.56s-0.49,-0.67 -1,-0.63a20.94,20.94 0,0 1,-2.24 0.06c-0.64,0 -1,-0.32 -0.48,-1 0.14,-0.18 0.31,-0.52 0.24,-0.65 -0.53,-0.93 0.17,-1.23 0.76,-1.58s1.39,-0.82 2.28,-1.34A7.48,7.48 0,0 0,75 83.06c-1.31,-0.06 -2.63,0 -3.94,-0.14 -0.72,-0.06 -1.46,-0.25 -1.17,-1.35 0,-0.17 -0.39,-0.53 -0.66,-0.7a6.9,6.9 0,0 0,-1.11 -0.41c0.54,-1.64 2.12,-1.44 3.14,-2.1 -0.59,-0.57 -1.19,-1.09 -1.72,-1.67s-0.4,-0.89 0.4,-1 1.91,-0.25 2.87,-0.38a2,2 0,0 0,0.36 -0.18A1.92,1.92 0,0 0,71 73.6a6,6 0,0 1,-1.38 -0.19v-0.24l1.57,-1.12 -2,-1.52c-0.05,0 -0.15,0 -0.16,-0.09 -0.24,-0.56 -0.32,-1.37 -0.74,-1.64 -0.59,-0.37 -1.1,-0.55 -1.08,-1.39s0.26,-1.25 1.15,-1.17a9.41,9.41 0,0 0,1.5 0c0.18,0 0.35,-0.19 0.52,-0.29 -0.12,-0.15 -0.21,-0.36 -0.37,-0.45a25.58,25.58 0,0 1,-2.23 -1.26,1.34 1.34,0 0,1 -0.24,-1.07 0.91,0.91 0,0 1,0.65 -0.61,1.52 1.52,0 0,0 1.44,-0.9c0.21,-0.41 1.17,-0.45 1.81,-0.62 0.33,-0.09 0.91,0 1,-0.19a1.79,1.79 0,0 0,0 -1.25c-0.09,-0.21 -0.62,-0.34 -0.94,-0.33 -1.59,0 -2.34,-1 -1.64,-2.45 0.15,-0.29 0.65,-0.41 1,-0.63s0.59,-0.29 0.59,-0.45 -0.23,-0.49 -0.37,-0.74a3.44,3.44 0,0 1,-0.49 -0.95c-0.05,-0.26 0.19,-0.56 0.3,-0.85 0.2,0.17 0.49,0.3 0.6,0.52 0.47,0.89 1.23,0.52 1.89,0.44 0.28,0 0.54,-0.28 1,-0.56 -0.87,-0.92 -1.68,-1.73 -2.42,-2.61A12,12 0,0 1,71 49.35ZM120.51,61.3a20.59,20.59 0,0 0,-2.48 0.37,13 13,0 0,0 -1.86,0.76 2,2 0,0 0,-0.42 0.18,12.65 12.65,0 0,0 -3.46,3.64A11.57,11.57 0,0 0,110.85 71c-0.13,1 0.44,2 0.55,3.07 0.16,1.38 1.07,2.33 1.76,3.4a13.89,13.89 0,0 0,1.07 1.11c0.6,0.73 1.56,0.87 2.24,1.35a5.57,5.57 0,0 0,3.26 0.9,9.3 9.3,0 0,0 4.73,-1 13.33,13.33 0,0 0,3.28 -2.51c0.72,-0.68 0.93,-1.9 1.37,-2.87 0.12,-0.26 0.26,-0.5 0.37,-0.75a5.3,5.3 0,0 0,0.45 -1.18,9.58 9.58,0 0,0 -1.12,-6.13 10.64,10.64 0,0 0,-2.34 -2.92c-0.7,-0.63 -1.78,-0.81 -2.57,-1.35A6,6 0,0 0,120.5 61.25ZM84.65,71.57c-0.35,2.05 0.53,3.81 1.36,5.57a1.62,1.62 0,0 0,0.36 0.43c0.49,0.45 1,0.91 1.48,1.34a6.46,6.46 0,0 0,3.81 1.94c1.28,0.05 2.56,0.08 3.84,0.05 2.06,-0.06 3.44,-1.42 4.83,-2.71a7.38,7.38 0,0 0,1.25 -1.71,9.18 9.18,0 0,0 0.75,-1.66 9.59,9.59 0,0 0,0.27 -1.18,7 7,0 0,0 0.49,-2.22 20.62,20.62 0,0 0,-1 -3.93,6.19 6.19,0 0,0 -1.21,-1.95c-0.46,-0.53 -1.19,-0.84 -1.65,-1.37a5.78,5.78 0,0 0,-3.47 -1.74,8.86 8.86,0 0,0 -8.21,2.23 8.14,8.14 0,0 0,-2.8 5C84.68,70.32 84.68,70.94 84.65,71.57ZM133.42,100.19a1.44,1.44 0,0 0,-0.48 0,8 8,0 0,0 -0.8,0.47l0.59,0.56a2.74,2.74 0,0 0,0.5 0.4,5.63 5.63,0 0,0 1.44,0.7 3.26,3.26 0,0 0,1.22 -0.24l-0.62,-0.79s-0.1,-0.09 -0.11,-0.15C135,100.17 134.13,100.26 133.42,100.19ZM120.62,104.87c0,-0.82 0,-1.35 -0.8,-1.33s-2.17,0.88 -1.94,1.33a2.35,2.35 0,0 0,1.31 1C119.72,106 120.62,105.12 120.62,104.87ZM82.3,44.14l-0.48,0.28 0.64,1.23a4.77,4.77 0,0 0,0.36 0.62,7.68 7.68,0 0,0 0.59,0.55c0.11,-0.26 0.37,-0.63 0.29,-0.77C83.28,45.38 82.77,44.77 82.3,44.14ZM147.22,68.08 L147.14,67.75c-0.87,-0.29 -1.17,-0.2 -1.42,0.6Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M125.89,74.16s0,0 0,0l-0.15,0.36 0,0 -0.23,0.31v0l-0.3,0.24 0,0 -0.36,0.15 0.07,0 -0.4,0.06h0.07l-0.4,0h0l-0.36,-0.15 0.05,0 -0.31,-0.23 0,0 -0.24,-0.3s0,0 0,0l-0.15,-0.36a0.08,0.08 0,0 1,0 0l-0.05,-0.4v0l0.21,-0.76 -0.07,0.09 0.24,-0.3 -0.11,0.11 0.3,-0.24a0.57,0.57 0,0 1,-0.13 0.08l0.36,-0.15 -0.17,0 0.39,0h-0.08l1.44,1.1a2.19,2.19 0,0 1,-0.16 -0.57l0,0.4a1,1 0,0 1,0 -0.3l-0.06,0.4a1.05,1.05 0,0 1,0.06 -0.24l-0.15,0.36 0.09,-0.16 -0.23,0.31a0.54,0.54 0,0 1,0.15 -0.15l-0.31,0.23 0.18,-0.1L125,74a1.05,1.05 0,0 1,0.24 -0.06l-0.4,0a0.89,0.89 0,0 1,0.23 0l-0.4,0 0.18,0 -0.36,-0.15 0.16,0.09 -0.31,-0.23a0.53,0.53 0,0 1,0.13 0.13l-0.23,-0.31a1.62,1.62 0,0 1,0.1 0.18l-0.15,-0.36a0.86,0.86 0,0 1,0.06 0.23l0,-0.4a0.82,0.82 0,0 1,0 0.27l0.06,-0.4a1,1 0,0 1,-0.07 0.25l0.15,-0.36a0.78,0.78 0,0 1,-0.11 0.2l0.23,-0.31a0.66,0.66 0,0 1,-0.14 0.15l0.3,-0.24a1,1 0,0 1,-0.17 0.1l0.36,-0.15a1.66,1.66 0,0 1,-0.21 0.06l0.4,-0.06a1.13,1.13 0,0 1,-0.26 0l0.4,0.06 -0.29,-0.08 0.36,0.15 -0.24,-0.14 0.3,0.23a1.26,1.26 0,0 1,-0.2 -0.2l0.24,0.31a1.13,1.13 0,0 1,-0.12 -0.21l0.15,0.36 0,-0.07a1,1 0,0 0,-0.27 -0.51,1 1,0 0,0 -0.42,-0.39A1.12,1.12 0,0 0,124 72a1.22,1.22 0,0 0,-0.6 0l-0.36,0.15a1.4,1.4 0,0 0,-0.54 0.54l-0.15,0.36a1.51,1.51 0,0 0,0 0.79,2.31 2.31,0 0,0 0.28,0.62 2.38,2.38 0,0 0,0.44 0.51,2.34 2.34,0 0,0 0.55,0.38 2.73,2.73 0,0 0,0.64 0.24,4.6 4.6,0 0,0 0.52,0.06c0.12,0 0.23,0 0.35,0s0.22,0 0.33,-0.06a3,3 0,0 0,0.49 -0.2l0.12,-0.08a3,3 0,0 0,0.4 -0.32,2.17 2.17,0 0,0 0.38,-0.52 3.85,3.85 0,0 0,0.19 -0.47,0.74 0.74,0 0,0 0,-0.14 4.43,4.43 0,0 0,0.06 -0.51,0.86 0.86,0 0,0 0,-0.16 4.18,4.18 0,0 0,-0.07 -0.51,0.58 0.58,0 0,0 0,-0.14 4.47,4.47 0,0 0,-0.19 -0.46,4.4 4.4,0 0,0 -0.31,-0.41l-0.1,-0.09a2.55,2.55 0,0 0,-0.41 -0.3,2.81 2.81,0 0,0 -0.47,-0.21l-0.08,0A3.55,3.55 0,0 0,125 71h-0.14l-0.27,0 -0.27,0 -0.15,0a4.31,4.31 0,0 0,-0.49 0.22,2.55 2.55,0 0,0 -0.76,0.74 2.66,2.66 0,0 0,-0.23 0.52,0.83 0.83,0 0,0 -0.06,0.21 3.15,3.15 0,0 0,-0.06 0.61,3.94 3.94,0 0,0 0.08,0.6 2.57,2.57 0,0 0,0.15 0.47l0.15,0.36a1.21,1.21 0,0 0,0.39 0.42,0.69 0.69,0 0,0 0.33,0.19 1.06,1.06 0,0 0,0.58 0.13,1.81 1.81,0 0,0 0.61,-0.09 3,3 0,0 0,0.45 -0.19,3.6 3.6,0 0,0 0.39,-0.29l0.09,-0.08 0.26,-0.33 0,-0.06a0.88,0.88 0,0 0,0.1 -0.22,1 1,0 0,0 0.13,-0.57 0.78,0.78 0,0 0,0 -0.22,1 1,0 0,0 0,-0.22 5692915712000,5692915712000 0,0 0,-0.16 -0.38,1.72 1.72,0 0,0 -0.13,-0.18l-0.14,-0.16a1.22,1.22 0,0 0,-0.36 -0.27,1.66 1.66,0 0,0 -0.87,-0.22l-0.4,0.05h0l-0.19,0.08 -0.19,0.08 0,0a1.73,1.73 0,0 0,-0.57 0.57,1.28 1.28,0 0,0 -0.17,0.42 1.22,1.22 0,0 0,0 0.6,1.16 1.16,0 0,0 0.18,0.56 1.12,1.12 0,0 0,0.39 0.42,1 1,0 0,0 0.51,0.26l0.39,0.06a1.45,1.45 0,0 0,0.76 -0.21l0.3,-0.23a1.49,1.49 0,0 0,0.39 -0.66Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M96.74,74.52v0l0,-0.4v0.07l0,-0.4a0.43,0.43 0,0 1,0 0l0.15,-0.35a0.21,0.21 0,0 1,0 0.06l0.24,-0.31 0,0 0.3,-0.23 -0.1,0.06 0.36,-0.15 -0.11,0 0.39,-0.06a0.21,0.21 0,0 1,-0.11 0l0.39,0.06 -0.09,0 0.36,0.15 -0.07,0 0.3,0.24 -0.08,-0.09 0.23,0.3a0.75,0.75 0,0 1,-0.07 -0.12l0.15,0.36a0.9,0.9 0,0 1,-0.06 -0.22l0.06,0.4a1.93,1.93 0,0 1,0 -0.34l0,0.4a1.47,1.47 0,0 1,0.08 -0.3l-0.16,0.35a0.62,0.62 0,0 1,0.11 -0.17l-0.24,0.3a0.86,0.86 0,0 1,0.14 -0.13l-0.31,0.23a1.18,1.18 0,0 1,0.19 -0.11l-0.36,0.15 0.23,-0.07 -0.4,0.06a1,1 0,0 1,0.24 0l-0.4,-0.06 0.25,0.07 -0.36,-0.15a0.88,0.88 0,0 1,0.23 0.14l-0.3,-0.24a1.7,1.7 0,0 1,0.19 0.19L97.72,74a0.88,0.88 0,0 1,0.12 0.21l-0.15,-0.36a0.66,0.66 0,0 1,0.06 0.2l-0.06,-0.4a1.48,1.48 0,0 1,0 0.21l0,-0.4a1.21,1.21 0,0 1,0 0.18l0.15,-0.36a0.6,0.6 0,0 1,-0.09 0.15l0.23,-0.3a1.14,1.14 0,0 1,-0.15 0.16l0.3,-0.24 -0.21,0.13 0.36,-0.15a1.05,1.05 0,0 1,-0.24 0.06l0.4,0a0.81,0.81 0,0 1,-0.22 0l0.4,0 -0.17,0 0.36,0.15 -0.14,-0.08 0.3,0.23 0,0a1.11,1.11 0,0 0,-0.48 -0.31,1.14 1.14,0 0,0 -0.58,-0.12 1.13,1.13 0,0 0,-0.57 0.12,1.21 1.21,0 0,0 -0.49,0.31 1.59,1.59 0,0 0,-0.44 1.07,1.56 1.56,0 0,0 0.44,1.06 2.4,2.4 0,0 0,0.89 0.51,1.71 1.71,0 0,0 0.67,0.1 4.63,4.63 0,0 0,0.52 -0.07l0.15,0a2,2 0,0 0,0.61 -0.29,3 3,0 0,0 0.4,-0.31 3.22,3.22 0,0 0,0.32 -0.41,0.54 0.54,0 0,0 0.07,-0.13 2,2 0,0 0,0.19 -0.48,2.23 2.23,0 0,0 0,-1.09 1.74,1.74 0,0 0,-0.27 -0.66,4.63 4.63,0 0,0 -0.32,-0.42A0.86,0.86 0,0 0,99.9 72a4.23,4.23 0,0 0,-0.42 -0.32,5.44 5.44,0 0,0 -0.5,-0.22l-0.17,0a2.6,2.6 0,0 0,-0.56 -0.07,2.48 2.48,0 0,0 -0.54,0.06 0.76,0.76 0,0 0,-0.2 0.06,2.43 2.43,0 0,0 -1,0.58c-0.06,0.06 -0.11,0.14 -0.17,0.21s-0.11,0.14 -0.16,0.22A2.31,2.31 0,0 0,96 73a1.55,1.55 0,0 0,0 0.21,3.83 3.83,0 0,0 -0.07,0.58 3.55,3.55 0,0 0,0.06 0.56,1.16 1.16,0 0,0 0,0.18c0.06,0.15 0.12,0.3 0.19,0.44l0,0.08a3.26,3.26 0,0 0,0.29 0.36l0,0a2.6,2.6 0,0 0,0.33 0.25,3 3,0 0,0 0.41,0.18l0.09,0a1.76,1.76 0,0 0,0.91 0l0.08,0a2.17,2.17 0,0 0,0.42 -0.17,1.94 1.94,0 0,0 0.42,-0.32l0.13,-0.17a1.4,1.4 0,0 0,0.13 -0.18l0,0a3,3 0,0 0,0.16 -0.37v0l0,-0.4v0a2.52,2.52 0,0 0,-0.06 -0.47,1.15 1.15,0 0,0 -0.27,-0.51 1,1 0,0 0,-0.42 -0.38,1.12 1.12,0 0,0 -0.56,-0.18 1.08,1.08 0,0 0,-0.59 0,1.53 1.53,0 0,0 -0.9,0.69l-0.15,0.36a1.55,1.55 0,0 0,0 0.8Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_empty_favourites.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"200dp\"\n\tandroid:height=\"200dp\"\n\tandroid:viewportWidth=\"500\"\n\tandroid:viewportHeight=\"500\">\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M349.53,465.88l-26.34,-16.53a1.27,1.27 0,0 0,-1.29 -0.05l-5.15,2.75a1.3,1.3 0,0 0,-0.09 2.24c4.95,3.23 8.25,7 8.71,12l-4.13,1.14a26.39,26.39 0,0 0,-2.84 -4.76c-1.94,-2.45 -4.66,-4.28 -6.56,-5.83a1.31,1.31 0,0 0,-1.06 -0.28c-15.62,2.84 -30.88,5.37 -46,8.47 -14.78,3 -29.24,0.22 -43.67,-1.93 -12.72,-1.88 -25.29,-4.8 -37.91,-7.31 -5,-1 -9.31,1.38 -10.91,6.26a95.63,95.63 0,0 1,-3.42 9.42,1.3 1.3,0 0,1 -2.4,-0.1c-2,-5.77 -0.29,-10.93 2.28,-16l-3.09,-2.94 -12.28,7.23a1.31,1.31 0,0 1,-2 -1,7.54 7.54,0 0,1 0.87,-4.27l-2.88,-2.81 -10.87,6.19a1.3,1.3 0,0 1,-1.91 -1.43c1.78,-7.16 7.4,-10.16 13.91,-12.81a1.3,1.3 0,0 0,0.29 -2.25c-3.68,-2.75 -7,-5.19 -10.27,-7.78 -16.33,-13.07 -29.29,-28.45 -34.83,-49.25 -3.91,-14.65 -9.23,-29 -10,-44.31 -1.51,-30.62 1.84,-60.64 12.51,-89.53 7.78,-21.09 11.08,-43 13.13,-65.21 0.24,-2.66 -0.56,-5.54 -1.39,-8.16 -4.07,-12.91 -9,-25.58 -12.3,-38.67 -4.07,-16.25 -6.65,-32.86 -10.12,-49.27 -2.71,-12.8 -3,-25.52 0.19,-38.26 0.08,-0.34 0.16,-0.69 0.23,-1 2.36,-11 7.24,-14.1 18.3,-11.33 8.72,2.19 17.12,5.87 22.83,12.74 14.32,17.21 28,35 41.71,52.68 2,2.61 3.38,5.74 5.12,9a1.3,1.3 0,0 0,1.64 0.58c3.21,-1.33 6.4,-2.58 9.5,-4 10.57,-4.92 21.61,-7.77 33.31,-7.23 3.24,0.14 4.57,-1 4.82,-4 1.67,-20 8.44,-38.66 17.09,-56.44 4.31,-8.88 11.1,-16.64 17.3,-24.48 3,-3.77 8.75,-3.17 12.09,0.94a43.86,43.86 0,0 1,6.22 10.38c11.39,26.6 22.74,53.23 33.74,80 2.46,6 3.74,12.51 5.14,18.88 1.56,7.12 2.61,14 7.82,20.09 5.39,6.33 7.25,12.67 8.88,20.79a0.84,0.84 0,0 0,0 0.17c1.52,4.52 5.32,10.85 5.9,11.8a1.18,1.18 0,0 0,0.17 0.22c9.74,9.8 17.3,25.16 23.73,41.77 9.53,24.63 16.05,50.11 18.48,76.45 1.24,13.37 1.27,26.77 -2.19,40 -4.54,17.41 -8.65,34.93 -13,52.4 -3.23,13.09 -11,23.59 -19.44,33.64 -1.76,2.09 -4.57,3.26 -6.78,5 -2.47,1.93 -4.85,4 -7.64,6.3a1.31,1.31 0,0 0,0.4 2.23c5.91,2.17 12.26,1.91 16.43,7.23a1.31,1.31 0,0 1,-1.14 2.11c-1.9,-0.17 -3.51,-0.3 -5.08,-0.58 -5,-0.9 -10,-2.14 -15.09,-2.84a7.25,7.25 0,0 0,-3.39 0.73,1.3 1.3,0 0,0 -0.6,2 13.73,13.73 0,0 0,2.13 2.53c4.36,3.77 9,7.2 13.22,11.16a8.53,8.53 0,0 1,1.79 3.69A1.31,1.31 0,0 1,349.53 465.88ZM125.87,157.44h0a1.3,1.3 0,0 1,1.28 0.92,28 28,0 0,1 1.5,6c1.51,23.66 -1.94,46.92 -7.11,69.85 -3.61,16 -8.54,31.69 -13.39,47.36 -5.93,19.18 -4.56,38.89 -3.77,58.31 0.51,12.46 5.16,24.75 7.87,37.13a79.85,79.85 0,0 0,19.4 37.1c10.1,11.12 21.71,20.39 35.33,26.55a150.79,150.79 0,0 0,45.68 12.95c18.18,2 36.21,6.55 54.7,2.72 8.28,-1.72 16.58,-3.55 25,-4.61 16.29,-2.05 30.23,-10.62 43.37,-18.67 16.8,-10.29 28.67,-26.52 33.67,-46.51 4.46,-17.8 8.93,-35.6 13.1,-53.47a80.54,80.54 0,0 0,1.83 -15.19c0.91,-23.89 -3.3,-47.09 -9.9,-69.93 -5.71,-19.79 -13.94,-38 -26.37,-54.88 -8.52,-11.57 -11.53,-22.53 -16.05,-36.77 0,-0.1 0,-0.22 -0.05,-0.32 -1.45,-2.52 -2.69,-6.16 -4.77,-8.7 -2.93,-3.58 -5.46,-7 -6.37,-11.79 -1.67,-8.83 -3.17,-17.89 -6.43,-26.18C304,83.08 293,57.1 282.07,31a31.45,31.45 0,0 0,-4.62 -8.25c-0.85,-1 -4.6,-1.34 -5.09,-0.64 -4.62,6.65 -9.92,13.15 -12.91,20.54C253.24,58 247.86,73.75 243.11,89.6c-1.6,5.36 -5.63,11.36 -1,17.48 0.2,0.26 -1,2.22 -1.87,2.51a11.33,11.33 0,0 1,-5.18 0.61c-15.23,-2.3 -28.94,2.79 -42.49,8.59 -1.31,0.56 -2.18,2.29 -3.16,3.33a7.24,7.24 0,0 0,-1.47 2.1,1.29 1.29,0 0,1 -1.86,0.63 8.48,8.48 0,0 1,-3 -2.34c-2.82,-4.48 -4.77,-9.57 -8,-13.72 -13.23,-17.2 -26.28,-34.58 -40.52,-50.92 -4.67,-5.36 -12.5,-8.27 -19.32,-11.29 -3.28,-1.45 -5.63,-2.57 -10.3,0.43a1.31,1.31 0,0 0,-0.51 0.56c-3.06,6.82 -2.06,24.87 -0.19,37.07 2.69,17.6 6.86,35 10.69,52.38 1.8,8.21 4,16.33 6.24,24.89a1.29,1.29 0,0 0,2.5 0l1,-3.55A1.3,1.3 0,0 1,125.87 157.44Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M390.66,124.48c-0.85,1.46 -1.4,3.23 -2.58,4.32 -6.34,5.87 -12.69,11.73 -19.39,17.17 -1.36,1.11 -4.19,0.4 -6.35,0.53 1,-1.79 1.69,-4 3.14,-5.29q8,-7.28 16.52,-14c2.2,-1.75 4.9,-2.87 7.37,-4.28Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M172.59,239c-27.78,-0.26 -40.9,-29.74 -33.25,-46.93a20.49,20.49 0,0 1,2.5 -4.64c7.7,-9.58 29.81,-20.3 43.22,-13.69 16.31,8 24.63,25.2 20,42.69C201.75,229 187.08,239.15 172.59,239ZM197.66,207c0.06,-13.49 -10.5,-26.28 -21.8,-26.42 -15.48,-0.18 -29.57,10.34 -29.57,22.07 0,15.15 12.21,27.7 26.93,27.72C186.53,230.45 197.61,219.86 197.66,207.07Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M293.15,213.79c-17.72,-0.28 -37,-17.79 -36.16,-33.64 1.14,-21.68 11.75,-32.57 27.5,-32.86 25.94,-0.48 43.16,14.66 42.75,36.5C326.91,201.81 312.1,212.64 293.15,213.79ZM287.46,155.38c-4.42,1.19 -9.48,1.39 -13.13,3.77 -9.26,6 -12.31,24.83 -4.78,32.77 11.71,12.34 22.69,17.38 36.92,9.87 9.26,-4.89 13.88,-13.23 11.61,-23.44C315.18,165.31 302.85,153.58 287.46,155.38Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M170.8,199.76c4.19,0.19 6.1,2.61 6.11,6.45s-2.39,5.59 -6.11,5.56c-3.88,0 -6.56,-1.67 -6.44,-5.87C164.47,201.9 167.1,200.13 170.8,199.76Z\"\n\t\tandroid:strokeWidth=\"6\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M292.31,184.34c-4.9,0 -6.1,-2.93 -6.55,-5.27 -0.65,-3.36 1.8,-5.68 5.22,-5.88 3.66,-0.22 6,1.8 6.29,5.56C297.53,182.81 297.21,182.7 292.31,184.34Z\"\n\t\tandroid:strokeWidth=\"5\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M428.31,117.86c-5.36,-5.11 -13.62,-6 -19.4,-10.59a18.35,18.35 0,0 1,-6.58 -11.48,11.29 11.29,0 0,1 0.55,-6c1.78,-4.26 6.87,-6.23 11.48,-6.18a7.31,7.31 0,0 1,3.58 0.78,7 7,0 0,1 2.33,2.6 18.47,18.47 0,0 1,2.43 7.21c-0.41,-4.75 3.44,-9.2 8,-10.47s9.63,0.13 13.65,2.7A5.29,5.29 0,0 1,446 87.82a4.75,4.75 0,0 1,0.7 2,12.53 12.53,0 0,1 -1.19,7.66c-2.14,4.17 -6.39,6.74 -10.1,9.61s-7.31,6.81 -7.13,11.49\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M430.43,115.74c-3.28,-3.06 -7.3,-4.78 -11.39,-6.47 -3.58,-1.47 -7.3,-3.06 -10,-6a14.39,14.39 0,0 1,-3.87 -10.61c0.38,-3.76 3.74,-5.41 7.17,-6h0.08a8.69,8.69 0,0 1,0.88 -0.07,10.62 10.62,0 0,1 1.87,0 2.52,2.52 0,0 1,1.85 0.86,13.71 13.71,0 0,1 2.66,6.69 3.1,3.1 0,0 0,3 3,3 3,0 0,0 3,-3A7.58,7.58 0,0 1,430.55 87c3.57,-1.5 7.92,-0.45 11.24,1.42 0.63,0.35 1.52,0.73 1.82,1.39a6.55,6.55 0,0 1,0.19 3.08,10 10,0 0,1 -2.14,5.06c-2.54,3.16 -6.23,5.21 -9.32,7.78 -3.87,3.21 -7.17,7.63 -7.08,12.87 0.06,3.86 6.06,3.87 6,0 -0.07,-4.27 3.78,-7.53 7.06,-10 4,-3 8.26,-6 10.28,-10.78a15.4,15.4 0,0 0,1 -8.82c-0.57,-3.11 -2.83,-4.84 -5.5,-6.2 -4.69,-2.4 -10.45,-3.51 -15.48,-1.48s-9.26,7.14 -8.89,12.89h6a22.56,22.56 0,0 0,-2.84 -8.73,9.17 9.17,0 0,0 -7.26,-4.79c-4.8,-0.49 -10.46,1.17 -13.63,5 -3.88,4.64 -3.21,11.08 -0.88,16.28a22.29,22.29 0,0 0,10.77 10.69c4.78,2.37 10.39,3.69 14.33,7.36 2.82,2.63 7.08,-1.6 4.24,-4.24Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_empty_feed.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"200dp\"\n\tandroid:height=\"200dp\"\n\tandroid:viewportWidth=\"200\"\n\tandroid:viewportHeight=\"200\">\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M116.81,165.77a129.74,129.74 0,0 0,28.63 -3.31l1.91,-0.46a9.23,9.23 0,0 0,-0.38 2.64,2.52 2.52,0 0,0 1.21,1.79c0.5,0.15 1.38,-0.47 1.89,-1a10.05,10.05 0,0 0,1.11 -1.86c0.35,-0.54 0.77,-1 1.24,-1.64a38.77,38.77 0,0 0,2.55 3.09,1.88 1.88,0 0,0 1.68,0.43c0.38,-0.2 0.44,-1.07 0.53,-1.67a1.36,1.36 0,0 0,-0.23 -0.93c-2.19,-3.14 -0.7,-5 1.87,-7.13 8.68,-10.52 10.57,-17.58 10.12,-29.23 -0.53,-13.89 -0.37,-27.56 -6.28,-40.37 -1.88,-4.07 -2.57,-8.71 -3.64,-13.13a12.3,12.3 0,0 1,0.08 -3.87c0.88,-9 0.48,-17.7 0.9,-26.74a38,38 0,0 0,-2.22 -12.63c-1.6,-4.7 -10.07,-8.92 -13.6,-5.41 -3.78,3.77 -4.89,11.23 -7,16.12 -2.66,6 -3.2,12.08 -6.48,18.24a61.29,61.29 0,0 0,-13 0.81A84.19,84.19 0,0 0,116 45c-1.59,-7.45 -3.62,-14.8 -8.75,-20.79 -0.37,-0.44 -0.72,-0.9 -1.12,-1.3 -3.72,-3.76 -7.52,-3.42 -9.39,1.49A94.12,94.12 0,0 0,91.9 41.81c-1.3,7.54 -1.73,15.24 -2.58,22.86a6.62,6.62 0,0 1,-0.69 2.54,47.22 47.22,0 0,0 -5.76,20.1 73.07,73.07 0,0 1,-2.61 15.4c-3,10 -1.44,19.94 1.21,29.75a67,67 0,0 0,10.28 22.38,36 36,0 0,0 6.48,7.46c1.37,1.08 3.54,1.13 3.54,1.46s-0.93,0.63 -3.33,2c-1.34,0.77 -1.75,1.11 -1.76,1.55s0.91,0.65 1.39,0.91a0.57,0.57 0,0 0,0.54 0,10.4 10.4,0 0,0 3.07,-1.89 6.66,6.66 0,0 0,1.83 -1.91,18.27 18.27,0 0,0 -1.49,4.2c-0.1,0.58 1.81,0.8 2.23,0 0.55,-1 0.08,-2.59 1.72,-3.41 0,0 -1.47,3.43 -0.4,4.39a1.59,1.59 0,0 0,1.86 0c1.08,-0.85 0,-2.83 0.7,-4S110.85,165 116.81,165.77ZM132.88,62.63c0.41,-0.78 0.82,-1.47 1.15,-2.2 2.41,-5.33 2.9,-10.6 5.19,-16.58 2.71,-7.05 3,-12.79 7,-17.22 2.14,-2.35 8.57,1.64 9.37,4.66A41.64,41.64 0,0 1,156.92 43c-0.28,7.49 1,15 -0.18,22.4A34.77,34.77 0,0 0,159.91 87a49.61,49.61 0,0 1,5.27 16.54c1.59,12.34 2.91,23.56 0.45,35.4 -2.54,8.1 -5.1,15 -16.38,19.2 -13.2,4.92 -27.06,5.94 -41.07,4.13A18.7,18.7 0,0 1,95 154.65a69.33,69.33 0,0 1,-11.61 -25.72c-2.12,-8.75 -2.79,-17.54 -0.36,-26.42a63.49,63.49 0,0 0,2.25 -11.72c0.65,-8.16 2,-16.05 6.27,-23.19A5.1,5.1 0,0 0,92.11 65a112.69,112.69 0,0 1,3.21 -26.45,100.9 100.9,0 0,1 3.85,-12.88c1.2,-3.16 3,-3.41 5,-0.75a43.48,43.48 0,0 1,5.69 9.34,64.61 64.61,0 0,1 4.93,25.34c0,1.42 0.3,2.52 2.08,2.12A28.78,28.78 0,0 1,132.88 62.63Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M138.66,76.15a12.12,12.12 0,1 0,14.14 9.77A12.14,12.14 0,0 0,138.66 76.15ZM139.32,79.24a9.22,9.22 0,0 1,10.28 7.35c0.77,4.56 -2.63,9.47 -7.09,10.23a8.93,8.93 0,1 1,-3.19 -17.58Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M103.2,95.12a11.12,11.12 0,1 0,-13 -9A11.14,11.14 0,0 0,103.2 95.12ZM93.46,85.55a8.13,8.13 0,0 1,6.32 -9.13,7.87 7.87,0 1,1 2.74,15.49A8,8 0,0 1,93.46 85.55Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M141.11,94.33c1.2,-0.29 2.39,-0.65 2.39,-2.83a3.67,3.67 0,0 0,-4.24 -3.84c-1.95,0.32 -3.18,3 -2.76,4.84C136.87,94.11 138.27,94.85 141.11,94.33Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M101,89.58a3.46,3.46 0,0 0,2.52 -4.08c-0.25,-1.65 -2.19,-2.84 -4.4,-2.36a3.34,3.34 0,0 0,-2.42 4.24C97.09,89.09 98.39,90.09 101,89.58Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?android:colorBackground\"\n\t\tandroid:pathData=\"M31.54,84.88l0,60.92a4.52,4.52 0,0 0,0.4 2.28,4.66 4.66,0 0,0 1.52,1.37c12.43,8.11 17.64,12.94 30.07,21.05 1.28,0.83 4.49,3.75 6,4 6,1 8.09,2.19 9.57,2.38 16.52,2.14 32.76,2.57 49.43,2.62 0,-22 0.44,-43.4 0.45,-65.43a1.69,1.69 0,0 0,-0.2 -1,1.79 1.79,0 0,0 -1.43,-0.55l-45.25,-2.78a40.24,40.24 0,0 1,-9.35 -1.31,37.7 37.7,0 0,1 -7,-3.19A284.06,284.06 0,0 1,32.57 83.87\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M29.79,84.88l0,53.06c0,3.13 -0.72,7.09 0.29,10.05 0.69,2 2.39,2.87 4.08,4 5.76,3.86 11.33,8 16.94,12 5.16,3.75 10.41,8.05 15.92,11.22 3.12,1.8 8,2.59 11.61,3.32 16.06,3.24 33.6,2.61 49.9,2.68a1.77,1.77 0,0 0,1.75 -1.75c0,-15.38 0.23,-30.75 0.36,-46.13 0,-3.85 0.06,-7.7 0.07,-11.55 0,-2.58 0.81,-6.27 -0.14,-8.72 -1.53,-3.93 -8.71,-2.66 -12.07,-2.87l-22.63,-1.39c-7.73,-0.47 -16.79,0.16 -24.1,-2.6 -6.91,-2.61 -13.46,-7.18 -19.68,-11.1q-9.56,-6 -18.6,-12.79c-1.81,-1.34 -3.56,1.7 -1.77,3a260.74,260.74 0,0 0,34.54 22.1c11.4,6 25.94,4.63 38.46,5.4l10.83,0.67c2.86,0.17 8.81,-0.49 10.76,1.53s0.9,7.74 0.88,10.6c0,3.52 0,7 -0.08,10.57 -0.13,14.42 -0.32,28.83 -0.33,43.25l1.75,-1.75c-15.23,-0.06 -30.49,-0.39 -45.62,-2.16 -4.27,-0.49 -10.37,-1.38 -14.12,-3.36 -4.9,-2.58 -9.62,-6.69 -14.12,-9.92 -4.82,-3.45 -9.54,-7 -14.42,-10.4 -3.67,-2.55 -6.41,-3.4 -7,-8.07 -0.45,-3.8 0,-8 0,-11.79l0,-47.17a1.75,1.75 0,0 0,-3.5 0Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M69.75,113.5c-0.08,17.69 -2,35.31 -2,53a1.75,1.75 0,0 0,3.5 0c0,-17.69 1.92,-35.31 2,-53a1.75,1.75 0,0 0,-3.5 0Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M78.52,156.22l8.93,0.45 -0.85,-1.11a11.6,11.6 0,0 1,-5.67 7.62c-1,0.54 -0.11,2 0.88,1.52A13.29,13.29 0,0 0,88.29 156a0.87,0.87 0,0 0,-0.84 -1.1l-8.93,-0.45c-1.13,-0.05 -1.12,1.7 0,1.75Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M77.56,161.84c2.46,1.85 4.83,3.78 6.31,6.54 0.53,1 2,0.11 1.51,-0.88 -1.62,-3 -4.24,-5.14 -6.94,-7.17 -0.9,-0.67 -1.77,0.84 -0.88,1.51Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M92.25,156.63a13.6,13.6 0,0 0,4.31 3.13c1,0.43 1.92,-1.08 0.88,-1.52a12.21,12.21 0,0 1,-4 -2.85c-0.79,-0.8 -2,0.43 -1.24,1.24Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M102.12,156a21.58,21.58 0,0 1,-1.1 7.14c-1.37,3.69 -4.49,4.09 -8,3.94 -1.13,0 -1.12,1.71 0,1.75 3.06,0.13 6.83,-0.06 8.66,-2.91s2.15,-6.63 2.22,-9.92a0.88,0.88 0,0 0,-1.76 0Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M111.12,155c0,2.48 0.54,4.95 0.46,7.44 -0.11,3.1 -2,4.79 -4.81,5.72 -1.06,0.35 -0.61,2 0.46,1.68 2.63,-0.88 4.89,-2.29 5.83,-5.1 1,-3 -0.14,-6.66 -0.18,-9.74a0.88,0.88 0,0 0,-1.76 0Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M107,159.88h9.45c0.79,0 1.28,-0.21 1.76,0.53 0.29,0.46 0,1.91 0,2.42 0,1.59 0.62,6.37 -2.39,5.41 -1.07,-0.35 -1.53,1.34 -0.46,1.69 5.52,1.76 4.64,-6.57 4.58,-9.52 0,-2.36 -0.77,-2.28 -3.06,-2.28H107a0.88,0.88 0,0 0,0 1.76Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M118.89,156.58a7.65,7.65 0,0 1,1.58 1.31c0.77,0.83 2,-0.41 1.24,-1.24a9.46,9.46 0,0 0,-1.93 -1.58,0.88 0.88,0 0,0 -0.89,1.51Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M120.67,154.89a4.09,4.09 0,0 1,1.57 1.7c0.53,1 2,0.11 1.52,-0.89a5.85,5.85 0,0 0,-2.2 -2.32,0.88 0.88,0 0,0 -0.89,1.51Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M117,132c0.38,-0.18 0.08,-1.13 0.08,-1.55 0,-6.64 -0.11,-13.81 -0.08,-20.45l-11,-1a148.65,148.65 0,0 1,-0.89 22.33,55.65 55.65,0 0,0 5.63,-4.3 1.64,1.64 0,0 1,2.3 0.21Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M117.84,132.23c0.76,-2.84 0.12,-6.48 0.1,-9.42 0,-4.27 -0.08,-8.54 -0.06,-12.81a0.92,0.92 0,0 0,-0.88 -0.88l-11,-1a0.89,0.89 0,0 0,-0.88 0.88,157.13 157.13,0 0,1 -0.88,22.33 0.88,0.88 0,0 0,1.32 0.75c2.23,-1.5 5.15,-5 7.59,-3.35 1.23,0.81 2.28,2.74 3.23,3.89 0.72,0.87 2,-0.38 1.24,-1.24 -1.26,-1.51 -2.52,-3.79 -4.14,-4.89 -3.09,-2.13 -6.53,2.55 -8.81,4.08l1.32,0.76c0.74,-6 1.8,-13 0.93,-18.94 0,-0.26 -0.07,-3.12 -0.71,-2.5 0.13,-0.12 0.84,0.13 1.05,0.1a19.08,19.08 0,0 1,4.24 0.39,24.43 24.43,0 0,1 3.93,0.35c2,0.53 1,0 0.69,2.63a79.42,79.42 0,0 0,0.06 8.59c0,3 0.74,7 0,9.82A0.87,0.87 0,0 0,117.84 132.23Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_empty_history.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"200dp\"\n\tandroid:height=\"200dp\"\n\tandroid:viewportWidth=\"500\"\n\tandroid:viewportHeight=\"500\">\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M281.23,442.93a352.9,352.9 0,0 1,-78.58 4.68c-1.79,-0.1 -3.57,-0.2 -5.35,-0.33 0.92,2.6 2.25,4.76 2.27,6.93 0,1.88 -1,4.69 -2.39,5.39s-4,-0.61 -5.56,-1.7 -2.5,-3 -3.85,-4.47c-1.19,-1.28 -2.56,-2.39 -4.13,-3.83a96.65,96.65 0,0 1,-5.39 9.53c-0.84,1.18 -3.18,2.35 -4.31,2s-1.68,-2.69 -2.22,-4.24a3.85,3.85 0,0 1,0.18 -2.63c4.41,-9.48 -0.47,-13.75 -8.41,-18.29 -28.33,-24.18 -36.76,-42.25 -41.08,-73.82 -5.14,-37.59 -12.08,-74.29 -2.25,-111.55 3.12,-11.84 2.79,-24.63 3.56,-37 0.21,-3.41 -1.1,-7 -2,-10.39 -6.64,-23.84 -9.68,-47.38 -15.11,-71.49 -2.32,-10.33 -1.65,-24.36 0,-35 2.08,-13.4 22.86,-28.78 34,-21 12,8.35 18.48,27.88 26.56,40 10,15 14.33,31 26.06,46a168.54,168.54 0,0 1,35.38 -4,229.46 229.46,0 0,1 -2.45,-39.91c0.73,-20.81 2.72,-41.51 13.67,-60.08 0.79,-1.34 1.51,-2.73 2.39,-4 8.22,-11.87 18.61,-12.77 26,-0.45a257.46,257.46 0,0 1,21.31 44.53c7.08,19.66 11.89,40.15 17.77,60.25a18.12,18.12 0,0 0,3.06 6.5c12.55,15.09 20.2,32.55 25,51.34 3.56,13.84 7.85,27.2 14.33,40.17 12.66,25.37 13.32,52.94 10.85,80.6 -2,22.6 -7,44.57 -17,65.05 -3.88,7.92 -7.72,16.2 -13.9,23.13 -3.18,3.57 -9,4.74 -8.82,5.62 0.21,1.11 2.8,1.25 9.91,3.85 3.95,1.43 5.23,2.14 5.47,3.33 0.33,1.6 -1.37,3.37 -2.73,4.18 -9.83,-0.68 -9.14,-2.49 -11.19,-4.44 -3.25,-3.07 -5.75,-4.33 -5.82,-4.27s3.94,5 6,10.6c0.55,1.5 -4.48,3 -6,1.11 -2,-2.48 -1.46,-6.92 -6.24,-8.37 0,0 5.57,8.54 3.16,11.62a4.34,4.34 0,0 1,-5 0.77c-3.3,-1.76 -1.28,-7.62 -3.76,-10.34C302.25,436 296.9,438 281.23,442.93ZM189.09,173.18c-1.47,-1.9 -2.9,-3.57 -4.13,-5.38 -9,-13.2 -12.84,-27.13 -21.84,-42.13 -10.6,-17.68 -14,-33 -27,-43 -6.87,-5.29 -22.27,8.5 -23,17a113.81,113.81 0,0 0,2 32c4.3,20 4.35,40.82 11.1,60.15 7,20.06 7.64,39.81 1.73,59.76 -4.61,15.57 -8,30.58 -7.24,47.46C122.3,333 125,363.32 137.2,394c10.69,20.58 20.83,37.93 53.18,43.88 37.83,7 75.6,3.14 112.41,-8.38 14.29,-4.47 24.81,-13.11 31.74,-26.78 11.94,-23.56 17.37,-48.66 19,-74.68 1.56,-24.54 -0.81,-48.51 -11.56,-71.22 -4.64,-9.8 -8.9,-20 -11.61,-30.46 -5.61,-21.63 -12.88,-42.23 -27.85,-59.4a14.07,14.07 0,0 1,-2.65 -6.69c-4,-24.13 -11.65,-47.2 -21.18,-69.61a273.08,273.08 0,0 0,-16.46 -32.8c-4.73,-7.94 -9.65,-7.78 -13.8,0.33C243.92,67 239.7,76.37 237.57,86a176.71,176.71 0,0 0,-1.25 70.5c0.7,3.81 0.38,6.93 -4.58,6.7A78.66,78.66 0,0 0,189.09 173.18Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M180,212.28a33.09,33.09 0,1 1,-33.39 33C146.6,227.2 161.86,212.13 180,212.28ZM179.71,220.91c-12.76,0 -24.29,11.71 -24.19,24.64 0.1,12.63 11.59,24.22 23.94,24.14 13.11,-0.09 24.47,-11.33 24.52,-24.24S192.57,221 179.67,220.91Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M284.32,246.47a30.41,30.41 0,0 1,-30.22 -30.33c-0.11,-16.7 14,-30.6 30.84,-30.43a30.38,30.38 0,1 1,-0.62 60.76ZM306,216.12c-0.06,-11.42 -10.08,-21.56 -21.3,-21.56s-21.56,10.38 -21.53,21.5c0,11.28 10.22,21.45 21.48,21.48S306,227.49 306,216.12Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M177.89,254.74c-3.37,-0.21 -7.65,-1.22 -7.84,-7.17 -0.18,-5.73 4.81,-11.6 9.65,-11.66 5.38,-0.06 11.43,6 11.16,11.15C190.63,251.56 185.78,254.79 177.89,254.74Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M284,225.62c-5.29,-0.11 -10.12,-4.56 -10.24,-9.44 -0.12,-4.56 6.08,-9 12.24,-8.78 6,0.22 8.84,5.54 8.53,10.27S291.24,225.77 284,225.62Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M366.34,305a101.85,101.85 0,0 1,0.57 -12q0.15,-1.44 0.33,-2.88l0.09,-0.69c0.09,-0.72 -0.16,1.13 0,0 0.08,-0.58 0.16,-1.16 0.25,-1.74q0.48,-3.19 1.07,-6.37c1.71,-9.22 4,-18.32 6.51,-27.36 5.22,-19 11.35,-37.69 16.09,-56.8a283.86,283.86 0,0 0,7.57 -47.61c1,-13.81 0.69,-28.26 -4.8,-41.18a40.11,40.11 0,0 0,-11.19 -15.29,47.46 47.46,0 0,0 -20.35,-9.55c-2.31,-0.49 -4.64,-0.83 -7,-1.12S350.9,84.73 351,87c0.13,2.69 2,4.19 4.5,4.5 0,0 1.21,0.17 0.47,0.06l0.27,0 1.54,0.25c1,0.17 2,0.37 3,0.59a51.07,51.07 0,0 1,5.44 1.54c0.2,0.07 2.48,1 2.5,0.94s-1.05,-0.46 -0.28,-0.12l0.53,0.23 1.28,0.61A38.7,38.7 0,0 1,374.44 98c0.62,0.41 1.22,0.86 1.82,1.29s-0.9,-0.73 -0.19,-0.15l0.39,0.33c0.33,0.27 0.66,0.54 1,0.82a36.94,36.94 0,0 1,3.17 3.21c0.22,0.25 0.43,0.51 0.64,0.76l0.32,0.39c0.56,0.68 -0.52,-0.72 -0.18,-0.23 0.46,0.64 0.93,1.27 1.36,1.92a40.18,40.18 0,0 1,2.18 3.75c0.32,0.64 0.63,1.27 0.92,1.92 0.08,0.19 0.16,0.39 0.25,0.58 0.24,0.51 -0.36,-0.92 -0.16,-0.38 0.15,0.39 0.32,0.78 0.46,1.17 0.55,1.44 1,2.91 1.44,4.39s0.78,3 1.09,4.6c0.14,0.74 0.28,1.49 0.4,2.24 0.06,0.35 0.11,0.71 0.16,1.06 0,0.13 0.13,1 0,0.21s0,0 0,0.21a113.13,113.13 0,0 1,0.47 20.41c-0.19,3.52 -0.5,7 -0.87,10.54 -0.19,1.76 -0.4,3.51 -0.61,5.26 -0.11,0.82 -0.21,1.64 -0.32,2.47 0,0.29 -0.28,1.83 -0.05,0.39 -0.08,0.48 -0.13,1 -0.2,1.44 -1.12,8.16 -2.55,16.23 -4.44,24.25 -4.42,18.78 -10.39,37.15 -15.64,55.7 -4.87,17.17 -9.61,34.83 -10.45,52.76 -0.09,1.89 -0.13,3.79 -0.1,5.69a4.5,4.5 0,0 0,9 0Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorPrimary\"\n\t\tandroid:pathData=\"M354.62,86C342.45,72.37 329.1,60.22 312.5,52.5s-31.71,-12.79 -45,-14c-11,-1 -17.87,-0.3 -27,1a82.54,82.54 0,0 0,-28 9c-17.19,8.6 -35.65,16.73 -48,2 7.24,10.61 10.87,18.31 18,29 4,6 13.68,14.75 17,17a77.36,77.36 0,0 0,19 9c12,3.73 13.14,4.19 25.53,6.07a204.89,204.89 0,0 0,29.61 1.9c14.84,0.07 29.85,-0.61 44.12,-4.7s27.85,-11.91 36.62,-23.88\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M357.8,82.85c-9.85,-11 -20.75,-21.36 -33.44,-29.1a150.31,150.31 0,0 0,-21.75 -10.43,148.4 148.4,0 0,0 -21.67,-7.11A101.94,101.94 0,0 0,259 33.53a126.2,126.2 0,0 0,-21.33 1.88,82.67 82.67,0 0,0 -18.83,5.23c-5.3,2.2 -10.35,4.93 -15.55,7.35 -0.8,0.36 -1.59,0.73 -2.39,1.08l-1.11,0.48 -0.55,0.24c0.41,-0.17 0.34,-0.14 0,0 -1.63,0.66 -3.27,1.3 -4.93,1.87s-3.12,1 -4.7,1.4c-0.71,0.18 -1.42,0.35 -2.14,0.49 -0.41,0.09 -0.82,0.16 -1.24,0.23 -0.2,0 -1.38,0.21 -0.8,0.14s-0.46,0 -0.62,0.06l-1.21,0.09c-0.74,0.05 -1.48,0.06 -2.22,0.05s-1.45,-0.07 -2.18,-0.13c-0.88,-0.07 0.76,0.15 -0.12,0 -0.38,-0.07 -0.76,-0.13 -1.13,-0.21 -0.7,-0.16 -1.4,-0.34 -2.08,-0.56l-0.94,-0.33c-0.1,0 -0.64,-0.23 -0.08,0s-0.25,-0.12 -0.38,-0.18a19.42,19.42 0,0 1,-1.8 -1c-0.3,-0.18 -0.59,-0.37 -0.88,-0.56s-1.25,-1 -0.53,-0.35a31.53,31.53 0,0 1,-3.54 -3.49c-1.45,-1.71 -4.26,-1.62 -5.91,-0.35a4.48,4.48 0,0 0,-1.16 5.8c4.45,6.54 8.35,13.4 12.43,20.17 2,3.34 4,6.74 6.31,9.88a70.37,70.37 0,0 0,6.57 7.64,72.48 72.48,0 0,0 12.7,10.83 89.64,89.64 0,0 0,21.26 9.35,126.62 126.62,0 0,0 16.22,4.19A200.41,200.41 0,0 0,262 117.71c17.56,0.76 35.72,0.62 52.89,-3.52 14.81,-3.56 29.39,-10.73 39.61,-22.26 1.35,-1.53 2.6,-3.13 3.81,-4.77 1.4,-1.9 0.38,-5.1 -1.61,-6.15a4.63,4.63 0,0 0,-6.16 1.61c-0.16,0.21 -0.72,0.95 -0.25,0.36l-0.87,1.08c-0.59,0.71 -1.19,1.41 -1.82,2.09 -1.19,1.31 -2.46,2.57 -3.77,3.77 -0.59,0.53 -1.18,1 -1.79,1.55 -0.35,0.3 -0.71,0.59 -1.07,0.88l-0.46,0.37c0.34,-0.27 0.27,-0.21 0,0q-2.15,1.61 -4.41,3.05a72.9,72.9 0,0 1,-9.3 5l-1.28,0.56c-0.47,0.21 0.09,0 0.16,-0.06l-0.56,0.23c-0.86,0.35 -1.73,0.68 -2.61,1 -1.68,0.62 -3.39,1.18 -5.11,1.69A101.15,101.15 0,0 1,305.27 107q-1.51,0.26 -3,0.48l-0.7,0.1c-0.79,0.11 0.57,-0.07 -0.21,0l-1.65,0.2c-2.11,0.25 -4.24,0.45 -6.36,0.61 -8.49,0.63 -17,0.67 -25.52,0.5 -4.3,-0.09 -8.6,-0.27 -12.89,-0.61q-3.28,-0.26 -6.54,-0.64l-1.45,-0.18 -1,-0.14c-1.07,-0.15 -2.13,-0.32 -3.19,-0.48a117.2,117.2 0,0 1,-15.93 -3.48c-3.49,-1.05 -7,-2.07 -10.43,-3.33 -0.92,-0.34 -1.84,-0.7 -2.75,-1.07l-0.28,-0.12 -0.26,-0.11L211.54,98c-1.62,-0.75 -3.22,-1.55 -4.78,-2.41 -1.26,-0.69 -2.51,-1.41 -3.72,-2.18 -0.47,-0.3 -0.94,-0.6 -1.4,-0.91l-0.5,-0.36c-0.56,-0.41 -0.22,-0.24 0.18,0.14 -1.91,-1.83 -4.07,-3.42 -6,-5.24 -2.26,-2.14 -4.46,-4.36 -6.5,-6.72 -0.47,-0.54 -0.92,-1.09 -1.37,-1.64s0.44,0.6 -0.06,-0.07c-0.23,-0.32 -0.47,-0.63 -0.69,-1 -1.1,-1.57 -2.12,-3.2 -3.14,-4.82 -4,-6.38 -7.66,-13 -11.7,-19.36q-1.7,-2.67 -3.47,-5.28l-7.07,5.45a26.57,26.57 0,0 0,18 9.34c6.54,0.63 13.26,-1.12 19.34,-3.39 6.27,-2.34 12.23,-5.36 18.23,-8.3q1.57,-0.78 3.18,-1.5l1.55,-0.69c0.45,-0.2 -0.41,0.19 -0.4,0.17a2.19,2.19 0,0 1,0.41 -0.17l1.13,-0.45a77.36,77.36 0,0 1,8.54 -2.78,97.16 97.16,0 0,1 11.2,-2.19l1.19,-0.16 0.37,-0.05 2.67,-0.33c1.62,-0.18 3.24,-0.33 4.86,-0.44A91.65,91.65 0,0 1,262 42.62c2,0.09 4.07,0.23 6.09,0.43 0.74,0.07 1.47,0.16 2.2,0.25 0.53,0.06 -0.22,0 -0.27,0l0.56,0.08 1.5,0.24A120.5,120.5 0,0 1,293 49.22c1.86,0.67 3.7,1.36 5.53,2.09 0.87,0.34 1.74,0.69 2.6,1l0.65,0.27 0.56,0.23 0.34,0.14 0.88,0.38c3.67,1.58 7.32,3.2 10.85,5.06a101.45,101.45 0,0 1,9.46 5.66q2.24,1.5 4.39,3.12c0.33,0.24 0.67,0.49 1,0.74l0.5,0.38c-0.07,0 -0.49,-0.39 -0.1,-0.07 0.76,0.61 1.52,1.2 2.27,1.82a164.89,164.89 0,0 1,15.87 15.07c1.25,1.34 2.47,2.69 3.69,4.06a4.53,4.53 0,0 0,6.36 0,4.61 4.61,0 0,0 0,-6.37Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M353.18,80.05c-9.46,0.34 -18.91,0.51 -28.37,0.21 -6.1,-0.2 -12.19,-0.57 -18.26,-1.18 -1.6,-0.16 -3.2,-0.33 -4.8,-0.52l-2.41,-0.31c1.05,0.14 -0.3,0 -0.42,0L297.3,78q-4.91,-0.72 -9.75,-1.69a142.18,142.18 0,0 1,-21.85 -6.11,4.53 4.53,0 0,0 -5.54,3.14 4.63,4.63 0,0 0,3.14 5.54c13,4.84 26.71,7.42 40.46,9a292.64,292.64 0,0 0,35.4 1.61c4.67,0 9.34,-0.18 14,-0.35a4.5,4.5 0,0 0,0 -9Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_empty_local.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"200dp\"\n\tandroid:height=\"200dp\"\n\tandroid:viewportWidth=\"200\"\n\tandroid:viewportHeight=\"200\">\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M81.94,186.19a154.12,154.12 0,0 0,33 -9.82l2.14,-0.93a10.54,10.54 0,0 0,0.11 3.18c0.27,0.77 1.1,1.78 1.78,1.85s1.53,-0.84 2,-1.52a11.55,11.55 0,0 0,0.91 -2.41c0.3,-0.71 0.7,-1.37 1.12,-2.19a43.76,43.76 0,0 0,3.64 3.11,2.27 2.27,0 0,0 2.07,0.16c0.4,-0.32 0.29,-1.36 0.27,-2.07a1.7,1.7 0,0 0,-0.46 -1.06c-3.23,-3.23 -1.86,-5.71 0.72,-8.76 8,-14.16 8.77,-22.84 5.83,-36.44 -3.51,-16.2 -6.14,-32.31 -15.74,-46.14 -3,-4.39 -4.83,-9.69 -7,-14.66a14.64,14.64 0,0 1,-0.71 -4.58c-0.83,-10.78 -3.1,-20.89 -4.48,-31.6 -0.59,-4.59 -3,-10.25 -5.23,-14.37 -2.85,-5.19 -13.68,-8.4 -17.09,-3.54 -3.66,5.21 -3.42,14.21 -4.93,20.39C78,42.44 78.64,49.65 76.06,57.56a73.93,73.93 0,0 0,-15.12 3.65,99.69 99.69,0 0,0 -5,-16.75C52.58,36 48.67,27.83 41.4,21.84c-0.53,-0.43 -1,-0.9 -1.59,-1.29 -5.14,-3.64 -9.54,-2.46 -10.72,3.69A112.24,112.24 0,0 0,27 45.71C27,54.84 28.12,64 28.7,73.1a8,8 0,0 1,-0.28 3.13C25.52,84.29 25,92.61 25.81,101a86.56,86.56 0,0 1,0.12 18.63c-1.4,12.31 2.45,23.73 7.59,34.71 4.21,9 9.53,17.26 16.72,24.17C53,181.22 55.82,184 59.4,186c1.84,1 4.4,0.6 4.46,1s-1,0.93 -3.5,3c-1.4,1.19 -1.82,1.67 -1.74,2.19s1.2,0.58 1.82,0.78a0.7,0.7 0,0 0,0.64 -0.11A12.45,12.45 0,0 0,64.29 190,7.41 7.41,0 0,0 66,187.39a22.49,22.49 0,0 0,-0.87 5.25c0,0.7 2.29,0.56 2.62,-0.44 0.44,-1.31 -0.44,-3.06 1.31,-4.37 0,0 -1,4.34 0.44,5.24a1.9,1.9 0,0 0,2.18 -0.43c1.09,-1.22 -0.61,-3.32 0,-4.81S74.78,186.52 81.94,186.19ZM79.47,61.68c0.32,-1 0.65,-1.9 0.89,-2.82C82.08,52.09 81.57,45.8 83,38.3c1.71,-8.84 0.81,-15.64 4.65,-21.69 2,-3.2 10.42,0.16 12,3.54a50.1,50.1 0,0 1,4 13.44c1.23,8.86 4.31,17.41 4.43,26.35a41.43,41.43 0,0 0,8.22 24.79,59.62 59.62,0 0,1 9.62,18.34c4.41,14.18 8.3,27.09 7.86,41.51 -1.31,10 -2.88,18.68 -15.27,26 -14.5,8.51 -30.57,12.59 -47.41,13.36a22.35,22.35 0,0 1,-17 -6.25c-8.42,-7.89 -14.4,-17.39 -19,-27.82 -4.3,-9.84 -6.91,-20 -5.89,-31a76.17,76.17 0,0 0,0.22 -14.24c-0.94,-9.72 -1,-19.26 2.55,-28.55a6.05,6.05 0,0 0,0.09 -3.14,134.53 134.53,0 0,1 -1.71,-31.74A121.63,121.63 0,0 1,32.2 25.26c0.76,-4 2.8,-4.63 5.72,-1.92a52,52 0,0 1,8.61 9.79,77.09 77.09,0 0,1 11,28.76c0.28,1.67 0.88,2.9 2.88,2.06A34.41,34.41 0,0 1,79.47 61.68Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M89.05,76.37a14.46,14.46 0,1 0,18.64 8.55A14.49,14.49 0,0 0,89.05 76.37ZM90.46,79.87c5.23,-1.92 11.73,1.18 13.62,6.5s-1.14,11.68 -6.23,13.49a10.66,10.66 0,0 1,-7.39 -20Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M51.32,106a13.27,13.27 0,1 0,-17.15 -7.9A13.29,13.29 0,0 0,51.32 106ZM37.9,96.78a9.68,9.68 0,0 1,5.52 -12,9.39 9.39,0 1,1 6.44,17.64A9.57,9.57 0,0 1,37.9 96.78Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M96.25,93.49c1.35,-0.59 3,-1.64 2.15,-4.11s-3.71,-4 -5.71,-3.35c-2.22,0.78 -3.79,4.16 -2.91,6.25C90.55,94.09 93,94.69 96.25,93.49Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M48.33,97.4A4.61,4.61 0,0 0,51.12 92c-0.64,-1.89 -3.84,-2.79 -6.34,-1.78a4,4 0,0 0,-2 5.5C43.66,97.63 45.39,98.53 48.33,97.4Z\"\n\t\tandroid:strokeWidth=\"1\"\n\t\tandroid:strokeColor=\"?colorOnSurface\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M121.32,84.4 L128.48,62c0.93,-2.88 1.67,-9 5.08,-9.14 2.75,-0.11 7.08,2.25 9.73,3.16 3.51,1.21 7,2.47 10.53,3.64 3.31,1.1 8,1.52 11,3.33 1.87,1.15 3.29,4.78 4.1,6.74a48.92,48.92 0,0 1,2.78 8.59c1.33,6.43 -1.94,13.48 -3.61,19.76 -1.79,6.74 -3.53,13.49 -5.44,20.2 -0.9,3.17 -1.83,6.33 -2.83,9.47 -0.45,1.43 -0.76,3.75 -1.65,5 0,1.24 -0.39,1.58 -1.24,1l-2.39,-0.77c-6.61,-2.11 -13.24,-4.18 -19.85,-6.31 -2.15,-0.7 -3.07,2.68 -0.93,3.37 4.4,1.42 8.8,2.81 13.2,4.2 3.06,1 7.86,3.63 11.07,3.38 2.36,-0.19 2.81,-2 3.51,-4 1.38,-3.9 2.58,-7.88 3.74,-11.86 2.71,-9.27 5,-18.65 7.57,-28 1,-3.63 2.83,-7.74 3.09,-11.48 0.2,-2.79 -1,-6.24 -1.84,-8.85 -1.43,-4.26 -3.33,-11.21 -7.51,-13.54 -3.42,-1.91 -8.09,-2.45 -11.81,-3.69 -4.7,-1.56 -9.35,-3.27 -14.06,-4.83 -2.73,-0.91 -6.59,-3 -9.58,-2.49 -2.5,0.46 -2.91,2.52 -3.61,4.66 -3.29,9.9 -6.37,19.88 -9.56,29.81 -0.68,2.15 2.69,3.07 3.38,0.93Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M132.76,56.37a34.42,34.42 0,0 0,-3.06 9.42c-0.36,2.2 3,3.15 3.38,0.93a31.17,31.17 0,0 1,2.7 -8.58c1,-2 -2,-3.79 -3,-1.77Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M139.22,57.27a21,21 0,0 0,-4 9.44c-0.35,2.21 3,3.16 3.37,0.93A18.93,18.93 0,0 1,142.24 59c1.35,-1.8 -1.69,-3.55 -3,-1.76Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M144,59.9l-2.4,8.66a1.75,1.75 0,0 0,3.38 0.93l2.4,-8.65A1.76,1.76 0,0 0,144 59.9Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M148.93,62.23a25.59,25.59 0,0 1,-0.94 7.83,1.76 1.76,0 0,0 3.38,0.94 28.9,28.9 0,0 0,1.06 -8.77c-0.07,-2.25 -3.57,-2.26 -3.5,0Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M155.87,63l-2.58,9.22a1.75,1.75 0,0 0,3.38 0.93L159.24,64a1.75,1.75 0,0 0,-3.37 -0.93Z\" />\n\t<path\n\t\tandroid:fillColor=\"?colorOnSurface\"\n\t\tandroid:pathData=\"M161.44,64.86a64.54,64.54 0,0 1,-2.72 9.35c-0.75,2.13 2.63,3 3.38,0.93a65.83,65.83 0,0 0,2.72 -9.35c0.42,-2.2 -2.95,-3.14 -3.38,-0.93Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_error_large.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"48dp\"\n\tandroid:height=\"48dp\"\n\tandroid:tint=\"@color/error\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M11 15h2v2h-2zm0-8h2v6h-2zm0.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_error_small.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"@color/error\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M11 15h2v2h-2zm0-8h2v6h-2zm0.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_expand.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M10,21V19H6.41L10.91,14.5L9.5,13.09L5,17.59V14H3V21H10M14.5,10.91L19,6.41V10H21V3H14V5H17.59L13.09,9.5L14.5,10.91Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_expand_more.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M7,10l5,5 5,-5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_explore_checked.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"#000000\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM14.19,14.19L6,18l3.81,-8.19L18,6l-3.81,8.19z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_explore_normal.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"?attr/colorControlNormal\"\n\t\tandroid:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM6.5,17.5l7.51,-3.49L17.5,6.5 9.99,9.99 6.5,17.5zM12,10.9c0.61,0 1.1,0.49 1.1,1.1s-0.49,1.1 -1.1,1.1 -1.1,-0.49 -1.1,-1.1 0.49,-1.1 1.1,-1.1z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_explore_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_explore_normal\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_explore_checked\"\n\t\tandroid:state_checked=\"true\" />\n\n\t<transition\n\t\tandroid:drawable=\"@drawable/avd_explore_enter\"\n\t\tandroid:fromId=\"@id/normal\"\n\t\tandroid:toId=\"@id/checked\" />\n\n\t<transition\n\t\tandroid:drawable=\"@drawable/avd_explore_leave\"\n\t\tandroid:fromId=\"@id/checked\"\n\t\tandroid:toId=\"@id/normal\" />\n\n</animated-selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_eye.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- drawable/eye_outline.xml -->\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,4.5C17,4.5 21.27,7.61 23,12C21.27,16.39 17,19.5 12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C15.76,17.5 19.17,15.36 20.82,12C19.17,8.64 15.76,6.5 12,6.5C8.24,6.5 4.83,8.64 3.18,12Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_eye_check.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M23.5,17L18.5,22L15,18.5L16.5,17L18.5,19L22,15.5L23.5,17M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,4.5C17,4.5 21.27,7.61 23,12C22.75,12.65 22.44,13.26 22.08,13.85C21.5,13.5 20.86,13.25 20.18,13.12L20.82,12C19.17,8.64 15.76,6.5 12,6.5C8.24,6.5 4.83,8.64 3.18,12C4.83,15.36 8.24,17.5 12,17.5L13.21,17.43C13.07,17.93 13,18.46 13,19V19.46L12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_eye_off.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M2,5.27L3.28,4L20,20.72L18.73,22L15.65,18.92C14.5,19.3 13.28,19.5 12,19.5C7,19.5 2.73,16.39 1,12C1.69,10.24 2.79,8.69 4.19,7.46L2,5.27M12,9A3,3 0 0,1 15,12C15,12.35 14.94,12.69 14.83,13L11,9.17C11.31,9.06 11.65,9 12,9M12,4.5C17,4.5 21.27,7.61 23,12C22.18,14.08 20.79,15.88 19,17.19L17.58,15.76C18.94,14.82 20.06,13.54 20.82,12C19.17,8.64 15.76,6.5 12,6.5C10.91,6.5 9.84,6.68 8.84,7L7.3,5.47C8.74,4.85 10.33,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C12.69,17.5 13.37,17.43 14,17.29L11.72,15C10.29,14.85 9.15,13.71 9,12.28L5.6,8.87C4.61,9.72 3.78,10.78 3.18,12Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_favourites_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_heart_outline\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_heart\"\n\t\tandroid:state_checked=\"true\" />\n\n\t<transition\n\t\tandroid:drawable=\"@drawable/avd_favourites_enter\"\n\t\tandroid:fromId=\"@id/normal\"\n\t\tandroid:toId=\"@id/checked\" />\n\n\t<transition\n\t\tandroid:drawable=\"@drawable/avd_favourites_leave\"\n\t\tandroid:fromId=\"@id/checked\"\n\t\tandroid:toId=\"@id/normal\" />\n\n</animated-selector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_feed.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M4 17.82a2.18 2.18 0 1 1 4.36 0 2.18 2.18 0 1 1-4.36 0\" />\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_feed_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_feed\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_feed\"\n\t\tandroid:state_checked=\"true\" />\n\n\t<transition\n\t\tandroid:drawable=\"@drawable/avd_feed_enter\"\n\t\tandroid:fromId=\"@id/normal\"\n\t\tandroid:toId=\"@id/checked\" />\n\n</animated-selector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_file_zip.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M14,2H6A2,2 0,0 0,4 4v16a2,2 0,0 0,2 2h12a2,2 0,0 0,2 -2V8L14,2m4,18H6V4h7v5h5z\" />\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"m16,16v-2h-2v2h2m-4,-2h2V12h-2v2m4,6v-2h-2v2h2m-4,-2h2V16h-2v2\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_filter_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12 18.88A1 1 0 0 1 11.71 19.71A1 1 0 0 1 10.3 19.71L6.3 15.71A1 1 0 0 1 6 14.87V9.75L1.21 3.62A1 1 0 0 1 1.38 2.22A1 1 0 0 1 2 2H16A1 1 0 0 1 16.62 2.22A1 1 0 0 1 16.79 3.62L12 9.75V18.88M4 4L8 9.06V14.58L10 16.58V9.05L14 4M13 16L18 21L23 16Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_folder_file.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M4 18H11V20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.89 4 4 4H10L12 6H20C21.1 6 22 6.89 22 8V10.17L20.41 8.59L20 8.17V8H4V18M23 14V21C23 22.11 22.11 23 21 23H15C13.9 23 13 22.11 13 21V12C13 10.9 13.9 10 15 10H19L23 14M21 15H18V12H15V21H21V15Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_gesture_vertical.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M4,3L1,6H3V9H1L4,12L7,9H5V6H7L4,3M11,8A1,1 0 0,0 10,9V19L6.8,17.28H6.58C6.3,17.28 6.03,17.39 5.84,17.6L5.1,18.37L10,22.57C10.26,22.85 10.62,23 11,23H17.5A1.5,1.5 0 0,0 19,21.5V17.14C19,16.56 18.68,16.03 18.15,15.79L13.21,13.6L12,13.47V9A1,1 0 0,0 11,8Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_grid.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M3 11h8V3H3m2 2h4v4H5m8 12h8v-8h-8m2 2h4v4h-4M3 21h8v-8H3m2 2h4v4H5m8-16v8h8V3m-2 6h-4V5h4z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_heart.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41 0.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_heart_off.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M2.39,1.73L1.11,3L3.19,5.08C2.45,6 2,7.19 2,8.5C2,12.27 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C14.32,19.24 15.14,18.5 15.9,17.79L20,22L21.27,20.73M12.1,18.55L12,18.65L11.89,18.55C7.14,14.24 4,11.39 4,8.5C4,7.74 4.22,7.06 4.61,6.5L14.5,16.37C13.74,17.06 12.95,17.78 12.1,18.55M8.3,5.1L6.33,3.13C6.7,3.05 7.1,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,10.84 20.69,12.92 18.47,15.27L17.06,13.86C18.91,11.88 20,10.2 20,8.5C20,6.5 18.5,5 16.5,5C15.1,5 13.74,5.83 13.11,7H10.89C10.38,6.06 9.39,5.34 8.3,5.1Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_heart_outline.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M16.5 3c-1.74 0-3.41 0.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-0.1 0.1-0.1-0.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04 0.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_history.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89 0.07 0.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-0.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54 0.72-1.21-3.5-2.08V8H12z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_history_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_history\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_history\"\n\t\tandroid:state_checked=\"true\" />\n\n\t<transition\n\t\tandroid:drawable=\"@drawable/avd_history_enter\"\n\t\tandroid:fromId=\"@id/normal\"\n\t\tandroid:toId=\"@id/checked\" />\n\n</animated-selector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_images.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3M15.96,10.29L13.21,13.83L11.25,11.47L8.5,15H19.5L15.96,10.29Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_incognito.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M17.06 13C15.2 13 13.64 14.33 13.24 16.1C12.29 15.69 11.42 15.8 10.76 16.09C10.35 14.31 8.79 13 6.94 13C4.77 13 3 14.79 3 17C3 19.21 4.77 21 6.94 21C9 21 10.68 19.38 10.84 17.32C11.18 17.08 12.07 16.63 13.16 17.34C13.34 19.39 15 21 17.06 21C19.23 21 21 19.21 21 17C21 14.79 19.23 13 17.06 13M6.94 19.86C5.38 19.86 4.13 18.58 4.13 17S5.39 14.14 6.94 14.14C8.5 14.14 9.75 15.42 9.75 17S8.5 19.86 6.94 19.86M17.06 19.86C15.5 19.86 14.25 18.58 14.25 17S15.5 14.14 17.06 14.14C18.62 14.14 19.88 15.42 19.88 17S18.61 19.86 17.06 19.86M22 10.5H2V12H22V10.5M15.53 2.63C15.31 2.14 14.75 1.88 14.22 2.05L12 2.79L9.77 2.05L9.72 2.04C9.19 1.89 8.63 2.17 8.43 2.68L6 9H18L15.56 2.68L15.53 2.63Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_info_outline.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_interaction_large.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"48dp\"\n\tandroid:height=\"48dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M13 5C15.21 5 17 6.79 17 9C17 10.5 16.2 11.77 15 12.46V11.24C15.61 10.69 16 9.89 16 9C16 7.34 14.66 6 13 6S10 7.34 10 9C10 9.89 10.39 10.69 11 11.24V12.46C9.8 11.77 9 10.5 9 9C9 6.79 10.79 5 13 5M20 20.5C19.97 21.32 19.32 21.97 18.5 22H13C12.62 22 12.26 21.85 12 21.57L8 17.37L8.74 16.6C8.93 16.39 9.2 16.28 9.5 16.28H9.7L12 18V9C12 8.45 12.45 8 13 8S14 8.45 14 9V13.47L15.21 13.6L19.15 15.79C19.68 16.03 20 16.56 20 17.14V20.5M20 2H4C2.9 2 2 2.9 2 4V12C2 13.11 2.9 14 4 14H8V12L4 12L4 4H20L20 12H18V14H20V13.96L20.04 14C21.13 14 22 13.09 22 12V4C22 2.9 21.11 2 20 2Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_kitsu.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:pathData=\"M1.429,5.441a12.478,12.478 0,0 0,1.916 2.056c0.011,0.011 0.022,0.011 0.022,0.022 0.452,0.387 1.313,0.947 1.937,1.173 0,0 3.886,1.496 4.091,1.582a1.4,1.4 0,0 0,0.237 0.075,0.694 0.694,0 0,0 0.808,-0.549c0.011,-0.065 0.022,-0.172 0.022,-0.248L10.462,5.161c0.011,-0.667 -0.205,-1.679 -0.398,-2.239 0,-0.011 -0.011,-0.022 -0.011,-0.032A11.979,11.979 0,0 0,8.824 0.36L8.781,0.285a0.697,0.697 0,0 0,-0.958 -0.162c-0.054,0.032 -0.086,0.075 -0.129,0.119L7.608,0.36a4.743,4.743 0,0 0,-0.786 3.412,8.212 8.212,0 0,0 -0.775,0.463c-0.043,0.032 -0.42,0.291 -0.71,0.56A4.803,4.803 0,0 0,1.87 4.3c-0.043,0.011 -0.097,0.021 -0.14,0.032 -0.054,0.022 -0.107,0.043 -0.151,0.076a0.702,0.702 0,0 0,-0.193 0.958l0.043,0.075zM8.222,1.07c0.366,0.614 0.678,1.249 0.925,1.917 -0.495,0.086 -0.98,0.215 -1.453,0.388a3.918,3.918 0,0 1,0.528 -2.305zM4.658,5.463a7.467,7.467 0,0 0,-0.893 1.216,11.68 11.68,0 0,1 -1.453,-1.55 3.825,3.825 0,0 1,2.346 0.334zM17.706,5.161a7.673,7.673 0,0 0,-2.347 -0.474,7.583 7.583,0 0,0 -3.811,0.818l-0.215,0.108v3.918c0,0.054 0,0.258 -0.032,0.431a1.535,1.535 0,0 1,-0.646 0.98,1.545 1.545,0 0,1 -1.152,0.247 2.618,2.618 0,0 1,-0.409 -0.118,747.6 747.6,0 0,1 -3.402,-1.313 8.9,8.9 0,0 0,-0.323 -0.129,30.597 30.597,0 0,0 -3.822,3.832l-0.075,0.086a0.698,0.698 0,0 0,0.538 1.098,0.676 0.676,0 0,0 0.42,-0.118c0.011,-0.011 0.022,-0.022 0.043,-0.032 1.313,-0.947 2.756,-1.712 4.284,-2.325a0.7,0.7 0,0 1,0.818 0.13,0.704 0.704,0 0,1 0.054,0.915l-0.237,0.388a20.277,20.277 0,0 0,-1.97 4.306l-0.032,0.129a0.646,0.646 0,0 0,0.108 0.538,0.713 0.713,0 0,0 0.549,0.301 0.657,0.657 0,0 0,0.42 -0.118c0.054,-0.043 0.108,-0.086 0.151,-0.14l0.043,-0.065a18.95,18.95 0,0 1,1.765 -2.153,20.156 20.156,0 0,1 10.797,-6.018c0.032,-0.011 0.065,-0.011 0.097,-0.011 0.237,0.011 0.42,0.215 0.409,0.452a0.424,0.424 0,0 1,-0.344 0.398c-3.908,0.829 -10.948,5.469 -8.483,12.208 0.043,0.108 0.075,0.172 0.129,0.269a0.71,0.71 0,0 0,0.538 0.301,0.742 0.742,0 0,0 0.657,-0.398c0.398,-0.754 1.152,-1.593 3.326,-2.497 6.061,-2.508 7.062,-6.093 7.17,-8.364v-0.129a7.716,7.716 0,0 0,-5.016 -7.451zM11.623,22.923c-0.56,-1.669 -0.506,-3.283 0.151,-4.823 1.26,2.035 3.456,2.207 3.456,2.207 -2.25,0.937 -3.133,1.863 -3.607,2.616z\"\n      android:fillColor=\"#000000\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_language.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12.87,15.07L10.33,12.56L10.36,12.53C12.1,10.59 13.34,8.36 14.07,6H17V4H10V2H8V4H1V6H12.17C11.5,7.92 10.44,9.75 9,11.35C8.07,10.32 7.3,9.19 6.69,8H4.69C5.42,9.63 6.42,11.17 7.67,12.56L2.58,17.58L4,19L9,14L12.11,17.11L12.87,15.07M18.5,10H16.5L12,22H14L15.12,19H19.87L21,22H23L18.5,10M15.88,17L17.5,12.67L19.12,17H15.88Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M3 4h4v4H3V4m6 1v2h12V5H9m-6 5h4v4H3v-4m6 1v2h12v-2H9m-6 5h4v4H3v-4m6 1v2h12v-2H9\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_list_detailed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M2 14h6v6H2M16 8h-6v2h6M2 10h6V4H2m8 0v2h12V4M10 20h6v-2h-6m0-2h12v-2H10\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_list_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M5 5V19H7V21H3V3H7V5H5M20 7H7V9H20V7M20 11H7V13H20V11M20 15H7V17H20V15Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_lock.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 0.9-2 2v10c0 1.1 0.9 2 2 2h12c1.1 0 2-0.9 2-2V10c0-1.1-0.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-0.9 2-2s-0.9-2-2-2-2 0.9-2 2 0.9 2 2 2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_mal.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"30.72\"\n\tandroid:viewportHeight=\"30.72\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M10.589,9.275L10.589,20.057L7.898,20.053L7.898,13.379L5.299,16.456 2.753,13.31 2.728,20.075L0,20.075L0,9.278L2.82,9.278L5.207,12.535 7.787,9.277ZM21.633,11.924 L21.665,20.033L18.638,20.033l-0.01,-3.675h-3.584c0.089,0.639 0.269,1.62 0.534,2.28 0.199,0.488 0.382,0.96 0.746,1.444l-2.182,1.44C13.695,20.707 13.346,19.81 13.018,18.856A11.899,11.899 0,0 1,12.369 16.067c-0.109,-0.96 -0.124,-1.883 0.137,-2.831a5.002,5.002 0,0 1,1.486 -2.389c0.401,-0.375 0.96,-0.64 1.408,-0.88 0.448,-0.239 0.951,-0.338 1.417,-0.46a9.48,9.48 0,0 1,1.525 -0.234c0.509,-0.044 1.417,-0.085 3.06,-0.036l0.698,2.239L18.573,11.476c-0.759,0.01 -1.124,0 -1.717,0.268a2.862,2.862 0,0 0,-1.636 2.46l3.409,0.042 0.049,-2.317h2.956zM26.743,9.239L26.743,17.72l3.977,0.039 -0.55,2.272h-6.153L24.017,9.199Z\"\n\t\tandroid:strokeWidth=\"0.06\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_manga_source.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M4 20h14v2H4c-1.1 0-2-0.9-2-2V6h2v14M22 4v12c0 1.1-0.9 2-2 2H8c-1.1 0-2-0.9-2-2V4c0-1.1 0.9-2 2-2h12c1.1 0 2 0.9 2 2m-2 0H8v12h12V4m-2 2h-5v7l2.5-1.5L18 13V6z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_move_horizontal.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_network_cellular.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M19,17H21V9H19M19,21H21V19H19M1,21H17V7H21V1\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_new.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"8dp\"\n\tandroid:height=\"8dp\"\n\tandroid:tint=\"?attr/colorError\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_next.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M6,18L14.5,12L6,6M8,9.86L11.03,12L8,14.14M16,6H18V18H16\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_nsfw.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"m4.8,1.4c-0.258,-0.344 -0.708,-0.485 -1.116,-0.349 -0.408,0.136 -0.684,0.518 -0.684,0.949v8c0,0.552 0.448,1 1,1s1,-0.448 1,-1v-5l4.2,5.6c0.258,0.344 0.708,0.485 1.116,0.349 0.408,-0.136 0.684,-0.518 0.684,-0.949v-8c0,-0.552 -0.448,-1 -1,-1 -0.552,0 -1,0.448 -1,1v5z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"m15,1c-1.105,0 -2,0.895 -2,2v2c0,1.105 0.895,2 2,2h4v2h-5c-0.552,0 -1,0.448 -1,1 0,0.552 0.448,1 1,1h5c1.105,0 2,-0.896 2,-2v-2c0,-1.105 -0.895,-2 -2,-2h-4v-2h5c0.552,0 1,-0.448 1,-1 0,-0.552 -0.448,-1 -1,-1z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"m3,14c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1s-0.448,1 -1,1h-5v2h5c0.552,0 1,0.448 1,1 0,0.552 -0.448,1 -1,1h-5v3c0,0.552 -0.448,1 -1,1s-1,-0.448 -1,-1z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"m15,14c0,-0.552 -0.448,-1 -1,-1s-1,0.448 -1,1v8c0,0.449 0.3,0.844 0.733,0.964 0.433,0.12 0.893,-0.064 1.124,-0.449l2.142,-3.571 2.142,3.571c0.231,0.385 0.691,0.569 1.124,0.449 0.433,-0.12 0.733,-0.514 0.733,-0.964v-8c0,-0.552 -0.448,-1 -1,-1s-1,0.448 -1,1v4.39l-1.143,-1.904c-0.181,-0.301 -0.506,-0.486 -0.858,-0.486s-0.677,0.184 -0.858,0.486l-1.143,1.904z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_off_small.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"16dp\"\n\tandroid:height=\"16dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M22.11 21.46L2.39 1.73L1.11 3L6.25 8.14C6.1 8.41 6 8.7 6 9V14.5L9.5 18V21H14.5V18L15.31 17.2L20.84 22.73L22.11 21.46M13.09 16.59L12.67 17H11.33L10.92 16.59L8 13.67V9.89L13.89 15.78L13.09 16.59M12.2 9L10.2 7H14V3H16V7C17 7 18 8 18 9V14.5L17.85 14.65L16 12.8V9.09C16 9.06 15.95 9 15.92 9H12.2M10 6.8L8 4.8V3H10V6.8Z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_offline.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M2.28,3L1,4.27L2.47,5.74C2.04,6 1.61,6.29 1.2,6.6L3,9C3.53,8.6 4.08,8.25 4.66,7.93L6.89,10.16C6.15,10.5 5.44,10.91 4.8,11.4L6.6,13.8C7.38,13.22 8.26,12.77 9.2,12.47L11.75,15C10.5,15.07 9.34,15.5 8.4,16.2L12,21L14.46,17.73L17.74,21L19,19.72M12,3C9.85,3 7.8,3.38 5.9,4.07L8.29,6.47C9.5,6.16 10.72,6 12,6C15.38,6 18.5,7.11 21,9L22.8,6.6C19.79,4.34 16.06,3 12,3M12,9C11.62,9 11.25,9 10.88,9.05L14.07,12.25C15.29,12.53 16.43,13.07 17.4,13.8L19.2,11.4C17.2,9.89 14.7,9 12,9Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_open_external.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M19 19H5V5h7V3H5C3.89 3 3 3.9 3 5v14c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_pin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_pin_small.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"14dp\"\n\tandroid:height=\"14dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_placeholder.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"60dp\"\n\tandroid:height=\"60dp\"\n\tandroid:tint=\"?attr/colorTertiary\"\n\tandroid:viewportWidth=\"60\"\n\tandroid:viewportHeight=\"60\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M21.7,40c-0.4,0 -0.8,-0.2 -1.2,-0.5c-0.3,-0.3 -0.5,-0.7 -0.5,-1.2V21.7c0,-0.4 0.2,-0.8 0.5,-1.2s0.7,-0.5 1.2,-0.5h16.7c0.4,0 0.8,0.2 1.2,0.5s0.5,0.7 0.5,1.2v16.7c0,0.4 -0.2,0.8 -0.5,1.2c-0.3,0.3 -0.7,0.5 -1.2,0.5H21.7zM21.7,38.4h16.7V21.7H21.7V38.4zM23.2,35.7h13.6l-4.1,-5.5L29.1,35l-2.6,-3.5C26.5,31.4 23.2,35.7 23.2,35.7z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_play.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?android:colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M10 8.64L15.27 12 10 15.36V8.64M8 5v14l11-7L8 5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_plug_large.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"48dp\"\n\tandroid:height=\"48dp\"\n\tandroid:tint=\"@color/error\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M21.4 7.5C22.2 8.3 22.2 9.6 21.4 10.3L18.6 13.1L10.8 5.3L13.6 2.5C14.4 1.7 15.7 1.7 16.4 2.5L18.2 4.3L21.2 1.3L22.6 2.7L19.6 5.7L21.4 7.5M15.6 13.3L14.2 11.9L11.4 14.7L9.3 12.6L12.1 9.8L10.7 8.4L7.9 11.2L6.4 9.8L3.6 12.6C2.8 13.4 2.8 14.7 3.6 15.4L5.4 17.2L1.4 21.2L2.8 22.6L6.8 18.6L8.6 20.4C9.4 21.2 10.7 21.2 11.4 20.4L14.2 17.6L12.8 16.2L15.6 13.3Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_prev.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M6,6H8V18H6M9.5,12L18,18V6M16,14.14L12.97,12L16,9.86V14.14Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_read.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12 9c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0-6c1.1 0 2 0.9 2 2s-0.9 2-2 2-2-0.9-2-2 0.9-2 2-2zm0 8.55C9.64 9.35 6.48 8 3 8v11c3.48 0 6.64 1.35 9 3.55 2.36-2.19 5.52-3.55 9-3.55V8c-3.48 0-6.64 1.35-9 3.55zm7 5.58c-2.53 0.34-4.93 1.3-7 2.82-2.06-1.52-4.47-2.49-7-2.83v-6.95c2.1 0.38 4.05 1.35 5.64 2.83L12 14.28l1.36-1.27c1.59-1.48 3.54-2.45 5.64-2.83v6.95z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_reader_ltr.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"m18,2c1.11,0 2,0.9 2,2v16c0,1.11 -0.89,2 -2,2L6,22C4.89,22 4,21.11 4,20L4,4C4,2.9 4.89,2 6,2ZM18,4L6,4v3.844h-0.002v8.229L6,16.072L6,20L18,20L18,16.072 18,16 18,8 18,7.844Z\" />\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M17.009,8l-4,0l-0,8l4,0z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_reader_rtl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M6,2C4.89,2 4,2.9 4,4L4,20C4,21.11 4.89,22 6,22L18,22C19.11,22 20,21.11 20,20L20,4C20,2.9 19.11,2 18,2L6,2zM6,4L18,4L18,7.844L18.002,7.844L18.002,16.072L18,16.072L18,20L6,20L6,16.072L6,16L6,8L6,7.844L6,4z\" />\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M6.991,8h4v8h-4z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_reader_vertical.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M18 2H6C4.89 2 4 2.9 4 4V20C4 21.11 4.89 22 6 22H18C19.11 22 20 21.11 20 20V4C20 2.9 19.11 2 18 2M18 20H6V16H18V20M18 8H6V4H18V8Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_reorder_handle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M7 19v-2h2v2H7m4 0v-2h2v2h-2m4 0v-2h2v2h-2m-8-4v-2h2v2H7m4 0v-2h2v2h-2m4 0v-2h2v2h-2m-8-4V9h2v2H7m4 0V9h2v2h-2m4 0V9h2v2h-2M7 7V5h2v2H7m4 0V5h2v2h-2m4 0V5h2v2h-2z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_replace.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M14,3L12,1H4A2,2 0 0,0 2,3V15A2,2 0 0,0 4,17H11V19L15,16L11,13V15H4V3H14M21,10V21A2,2 0 0,1 19,23H8A2,2 0 0,1 6,21V19H8V21H19V12H14V7H8V13H6V7A2,2 0 0,1 8,5H16L21,10Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_retry.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12,5V2L8,6l4,4V7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93C20,8.58 16.42,5 12,5z\" />\n\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_revert.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M13.5,7A6.5,6.5 0 0,1 20,13.5A6.5,6.5 0 0,1 13.5,20H10V18H13.5C16,18 18,16 18,13.5C18,11 16,9 13.5,9H7.83L10.91,12.09L9.5,13.5L4,8L9.5,2.5L10.92,3.91L7.83,7H13.5M6,18H8V20H6V18Z\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_save.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M17 3H5C3.89 3 3 3.9 3 5v14c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V7l-4-4zm2 16H5V5h11.17L19 7.83V19zm-7-7c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3zM6 6h9v4H6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_save_ok.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M14 12.8c-0.5-0.49-1.22-0.8-2-0.8-1.66 0-3 1.34-3 3 0 1.31 0.84 2.41 2 2.82 0.07-2.15 1.27-4.02 3-5.02M11.09 19H5V5h11.17L19 7.83v4.52c0.75 0.26 1.42 0.65 2 1.19V7l-4-4H5C3.89 3 3 3.9 3 5v14c0 1.1 0.89 2 2 2h6.81c-0.35-0.61-0.6-1.28-0.72-2M6 10h9V6H6v4m9.75 11L13 18l1.16-1.16 1.59 1.59 3.59-3.59 1.16 1.41L15.75 21\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_screen_rotation.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M16.48 2.52c3.27 1.55 5.61 4.72 5.97 8.48h1.5C23.44 4.84 18.29 0 12 0l-0.66 0.03 3.81 3.81 1.33-1.32zm-6.25-0.77c-0.59-0.59-1.54-0.59-2.12 0L1.75 8.11c-0.59 0.59-0.59 1.54 0 2.12l12.02 12.02c0.59 0.59 1.54 0.59 2.12 0l6.36-6.36c0.59-0.59 0.59-1.54 0-2.12L10.23 1.75zm4.6 19.44L2.81 9.17l6.36-6.36 12.02 12.02-6.36 6.36zm-7.31 0.29C4.25 19.94 1.91 16.76 1.55 13h-1.5C0.56 19.16 5.71 24 12 24l0.66-0.03-3.81-3.81-1.33 1.32z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_screen_rotation_lock.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M16.8,2.5C16.8,1.56 17.56,0.8 18.5,0.8C19.44,0.8 20.2,1.56 20.2,2.5V3H16.8V2.5M16,9H21A1,1 0 0,0 22,8V4A1,1 0 0,0 21,3V2.5A2.5,2.5 0 0,0 18.5,0A2.5,2.5 0 0,0 16,2.5V3A1,1 0 0,0 15,4V8A1,1 0 0,0 16,9M8.47,20.5C5.2,18.94 2.86,15.76 2.5,12H1C1.5,18.16 6.66,23 12.95,23L13.61,22.97L9.8,19.15L8.47,20.5M23.25,12.77L20.68,10.2L19.27,11.61L21.5,13.83L15.83,19.5L4.5,8.17L10.17,2.5L12.27,4.61L13.68,3.2L11.23,0.75C10.64,0.16 9.69,0.16 9.11,0.75L2.75,7.11C2.16,7.7 2.16,8.65 2.75,9.23L14.77,21.25C15.36,21.84 16.31,21.84 16.89,21.25L23.25,14.89C23.84,14.3 23.84,13.35 23.25,12.77Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_script.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M15 20a1 1 0 0 0 1-1V4H8a1 1 0 0 0-1 1v11H5V5a3 3 0 0 1 3-3h11a3 3 0 0 1 3 3v1h-2V5a1 1 0 0 0-1-1 1 1 0 0 0-1 1v14a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3v-1h11a2 2 0 0 0 2 2M9 6h5v2H9V6m0 4h5v2H9v-2m0 4h5v2H9v-2z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_select_group.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M5 3A2 2 0 0 0 3 5H5M7 3V5H9V3M11 3V5H13V3M15 3V5H17V3M19 3V5H21A2 2 0 0 0 19 3M3 7V9H5V7M7 7V11H11V7M13 7V11H17V7M19 7V9H21V7M3 11V13H5V11M19 11V13H21V11M7 13V17H11V13M13 13V17H17V13M3 15V17H5V15M19 15V17H21V15M3 19A2 2 0 0 0 5 21V19M7 19V21H9V19M11 19V21H13V19M15 19V21H17V19M19 19V21A2 2 0 0 0 21 19Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_select_range.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M4,3H5V5H3V4A1,1 0,0 1,4 3M20,3A1,1 0,0 1,21 4V5H19V3H20M15,5V3H17V5H15M11,5V3H13V5H11M7,5V3H9V5H7M21,20A1,1 0,0 1,20 21H19V19H21V20M15,21V19H17V21H15M11,21V19H13V21H11M7,21V19H9V21H7M4,21A1,1 0,0 1,3 20V19H5V21H4M3,15H5V17H3V15M21,15V17H19V15H21M3,11H5V13H3V11M21,11V13H19V11H21M3,7H5V9H3V7M21,7V9H19V7H21Z\" />\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M8.687,5.585L8.687,9.514L6.201,9.514L9.514,12.828 12.828,9.514L10.345,9.514L10.345,5.585ZM14.486,11.172 L11.172,14.486h2.483v3.929h1.658v-3.929h2.486z\"\n\t\tandroid:strokeWidth=\"0.828309\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_services.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_settings.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M19.43 12.98c0.04-0.32 0.07-0.64 0.07-0.98 0-0.34-0.03-0.66-0.07-0.98l2.11-1.65c0.19-0.15 0.24-0.42 0.12-0.64l-2-3.46c-0.09-0.16-0.26-0.25-0.44-0.25-0.06 0-0.12 0.01-0.17 0.03l-2.49 1c-0.52-0.4-1.08-0.73-1.69-0.98l-0.38-2.65C14.46 2.18 14.25 2 14 2h-4C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05l-2.49-1C4.89 5.03 4.83 5.02 4.77 5.02c-0.17 0-0.34 0.09-0.43 0.25l-2 3.46C2.21 8.95 2.27 9.22 2.46 9.37l2.11 1.65C4.53 11.34 4.5 11.67 4.5 12c0 0.33 0.03 0.66 0.07 0.98l-2.11 1.65c-0.19 0.15-0.24 0.42-0.12 0.64l2 3.46c0.09 0.16 0.26 0.25 0.44 0.25 0.06 0 0.12-0.01 0.17-0.03l2.49-1c0.52 0.4 1.08 0.73 1.69 0.98l0.38 2.65C9.54 21.82 9.75 22 10 22h4c0.25 0 0.46-0.18 0.49-0.42l0.38-2.65c0.61-0.25 1.17-0.59 1.69-0.98l2.49 1c0.06 0.02 0.12 0.03 0.18 0.03 0.17 0 0.34-0.09 0.43-0.25l2-3.46c0.12-0.22 0.07-0.49-0.12-0.64l-2.11-1.65zm-1.98-1.71c0.04 0.31 0.05 0.52 0.05 0.73 0 0.21-0.02 0.43-0.05 0.73l-0.14 1.13 0.89 0.7 1.08 0.84-0.7 1.21-1.27-0.51-1.04-0.42-0.9 0.68c-0.43 0.32-0.84 0.56-1.25 0.73l-1.06 0.43-0.16 1.13L12.7 20h-1.4l-0.19-1.35-0.16-1.13-1.06-0.43c-0.43-0.18-0.83-0.41-1.23-0.71l-0.91-0.7-1.06 0.43-1.27 0.51-0.7-1.21 1.08-0.84 0.89-0.7-0.14-1.13C6.52 12.43 6.5 12.2 6.5 12s0.02-0.43 0.05-0.73l0.14-1.13-0.89-0.7L4.72 8.6l0.7-1.21L6.69 7.9l1.04 0.42 0.9-0.68c0.43-0.32 0.84-0.56 1.25-0.73l1.06-0.43 0.16-1.13L11.3 4h1.39l0.19 1.35 0.16 1.13 1.06 0.43c0.43 0.18 0.83 0.41 1.23 0.71l0.91 0.7 1.06-0.43 1.27-0.51 0.7 1.21-1.07 0.85-0.89 0.7 0.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_sfw.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"0.72\"\n\tandroid:viewportHeight=\"0.72\">\n\t<clip-path android:pathData=\"M0,0L0,0.72L0.72,0.72L0.72,0L0,0zM0.12164,0.00691L0.71326,0.59883L0.67518,0.63691L0.08326,0.045L0.12164,0.00691z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"M0.14033,0.03836C0.13258,0.02803 0.1191,0.02382 0.10685,0.0279 0.0946,0.03198 0.08634,0.04344 0.08634,0.05635l0,0.23993c0,0.01656 0.01343,0.02999 0.02999,0.02999 0.01656,0 0.02999,-0.01343 0.02999,-0.02999L0.14632,0.14633l0.12596,0.16795c0.00775,0.01033 0.02123,0.01454 0.03348,0.01045C0.31801,0.32065 0.32627,0.30919 0.32627,0.29628L0.32627,0.05635c0,-0.01656 -0.01343,-0.02999 -0.02999,-0.02999 -0.01656,0 -0.02999,0.01343 -0.02999,0.02999l0,0.14995z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"m0.44623,0.02636c-0.03313,0 -0.05998,0.02685 -0.05998,0.05998l0,0.05998c0,0.03313 0.02685,0.05998 0.05998,0.05998l0.11996,0l0,0.05998l-0.14995,0c-0.01656,0 -0.02999,0.01343 -0.02999,0.02999 0,0.01656 0.01343,0.02999 0.02999,0.02999l0.14995,0c0.03313,0 0.05998,-0.02686 0.05998,-0.05998l0,-0.05998c0,-0.03313 -0.02685,-0.05998 -0.05998,-0.05998l-0.11996,0l0,-0.05998l0.14995,0c0.01656,0 0.02999,-0.01343 0.02999,-0.02999 0,-0.01656 -0.01343,-0.02999 -0.02999,-0.02999z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"m0.08634,0.41624c0,-0.01656 0.01343,-0.02999 0.02999,-0.02999l0.17994,0c0.01656,0 0.02999,0.01343 0.02999,0.02999 0,0.01656 -0.01343,0.02999 -0.02999,0.02999L0.14632,0.44623l0,0.05998l0.14995,0c0.01656,0 0.02999,0.01343 0.02999,0.02999 0,0.01656 -0.01343,0.02999 -0.02999,0.02999L0.14632,0.5662l0,0.08997c0,0.01656 -0.01343,0.02999 -0.02999,0.02999 -0.01656,0 -0.02999,-0.01343 -0.02999,-0.02999z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"m0.44623,0.41624c0,-0.01656 -0.01343,-0.02999 -0.02999,-0.02999 -0.01656,0 -0.02999,0.01343 -0.02999,0.02999l0,0.23993c0,0.01348 0.00899,0.0253 0.02199,0.0289 0.01299,0.0036 0.02678,-0.00191 0.03372,-0.01347L0.50621,0.5645l0.06425,0.10709c0.00694,0.01156 0.02073,0.01707 0.03372,0.01347C0.61718,0.68147 0.62617,0.66965 0.62617,0.65617l0,-0.23993c0,-0.01656 -0.01343,-0.02999 -0.02999,-0.02999 -0.01656,0 -0.02999,0.01343 -0.02999,0.02999l0,0.13165L0.53193,0.49078C0.52651,0.48175 0.51675,0.47622 0.50621,0.47622c-0.01054,0 -0.0203,0.00553 -0.02572,0.01456L0.44623,0.54789Z\" />\n\t<path\n\t\tandroid:fillColor=\"#0f0f0f\"\n\t\tandroid:pathData=\"M0.045,0.0831 L0.0834,0.045 0.675,0.6369 0.6369,0.675Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_shikimori.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<bitmap\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:src=\"@drawable/ic_shikimori_raw\"\n\tandroid:tint=\"?colorControlNormal\" />\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_shortcut.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M14,14.5V12H10V15H8V11A1,1 0 0,1 9,10H14V7.5L17.5,11M21.71,11.29L12.71,2.29H12.7C12.31,1.9 11.68,1.9 11.29,2.29L2.29,11.29C1.9,11.68 1.9,12.32 2.29,12.71L11.29,21.71C11.68,22.09 12.31,22.1 12.71,21.71L21.71,12.71C22.1,12.32 22.1,11.68 21.71,11.29Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_size_large.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M21,15H23V17H21V15M21,11H23V13H21V11M23,19H21V21C22,21 23,20 23,19M13,3H15V5H13V3M21,7H23V9H21V7M21,3V5H23C23,4 22,3 21,3M1,7H3V9H1V7M17,3H19V5H17V3M17,19H19V21H17V19M3,3C2,3 1,4 1,5H3V3M9,3H11V5H9V3M5,3H7V5H5V3M1,11V19A2,2 0 0,0 3,21H15V11H1M3,19L5.5,15.79L7.29,17.94L9.79,14.72L13,19H3Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_sort_asc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M19 17H22L18 21L14 17H17V3H19M2 17H12V19H2M6 5V7H2V5M2 11H9V13H2V11Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_sort_desc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M19 7H22L18 3L14 7H17V21H19M2 17H12V19H2M6 5V7H2V5M2 11H9V13H2V11Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_split_horizontal.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"M4,4C2.89,4 2,4.89 2,6L2,18C2,19.11 2.89,20 4,20L9,20L9,18L8,18L6,18L4,18L4,6L6,6L8,6L9,6L9,4L4,4zM15,4L15,6L16,6L18,6L20,6L20,18L18,18L16,18L15,18L15,20L20,20C21.1,20 22,19.11 22,18L22,6C22,4.89 21.1,4 20,4L15,4z\" />\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"m11,21l-0,-3l2,0l-0,3z\" />\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"m13,3l-0,3l-2,0l-0,-3l2,0\" />\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"m11,16l-0,-3l2,0l-0,3l-2,0\" />\n\t<path\n\t\tandroid:fillColor=\"#FF000000\"\n\t\tandroid:pathData=\"m11,11l-0,-3l2,0l-0,3l-2,0\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_star_small.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"16dp\"\n\tandroid:height=\"16dp\"\n\tandroid:tint=\"@color/common_yellow\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M14.43,10l-2.43,-8l-2.43,8l-7.57,0l6.18,4.41l-2.35,7.59l6.17,-4.69l6.18,4.69l-2.35,-7.59l6.17,-4.41z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_state_abandoned.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_state_finished.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M0.41,13.41L6,19L7.41,17.58L1.83,12M22.24,5.58L11.66,16.17L7.5,12L6.07,13.41L11.66,19L23.66,7M18,7L16.59,5.58L10.24,11.93L11.66,13.34L18,7Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_state_ongoing.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M13,2.03V2.05L13,4.05C17.39,4.59 20.5,8.58 19.96,12.97C19.5,16.61 16.64,19.5 13,19.93V21.93C18.5,21.38 22.5,16.5 21.95,11C21.5,6.25 17.73,2.5 13,2.03M11,2.06C9.05,2.25 7.19,3 5.67,4.26L7.1,5.74C8.22,4.84 9.57,4.26 11,4.06V2.06M4.26,5.67C3,7.19 2.25,9.04 2.05,11H4.05C4.24,9.58 4.8,8.23 5.69,7.1L4.26,5.67M2.06,13C2.26,14.96 3.03,16.81 4.27,18.33L5.69,16.9C4.81,15.77 4.24,14.42 4.06,13H2.06M7.1,18.37L5.67,19.74C7.18,21 9.04,21.79 11,22V20C9.58,19.82 8.23,19.25 7.1,18.37M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_storage.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M18 4v16H6V8.83L10.83 4H18m0-2h-8L4 8v12c0 1.1 0.9 2 2 2h12c1.1 0 2-0.9 2-2V4c0-1.1-0.9-2-2-2zM9 7h2v4H9zm3 0h2v4h-2zm3 0h2v4h-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_storage_checked.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- drawable/sd.xml -->\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M18,8H16V4H18M15,8H13V4H15M12,8H10V4H12M18,2H10L4,8V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_storage_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_storage\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_storage_checked\"\n\t\tandroid:state_checked=\"true\" />\n</selector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_suggestion.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7M9 21v-1h6v1a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1m3-17a5 5 0 0 0-5 5c0 2.05 1.23 3.81 3 4.58V16h4v-2.42c1.77-0.77 3-2.53 3-4.58a5 5 0 0 0-5-5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_suggestion_checked.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- drawable/lightbulb.xml -->\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M12,2A7,7 0 0,0 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H15A1,1 0 0,0 16,17V14.74C17.81,13.47 19,11.38 19,9A7,7 0 0,0 12,2M9,21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9V21Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_suggestion_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_suggestion\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_suggestion_checked\"\n\t\tandroid:state_checked=\"true\" />\n</selector>\n\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_sync.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_tag.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M21.41 11.58L12.41 2.58A2 2 0 0 0 11 2H4A2 2 0 0 0 2 4V11A2 2 0 0 0 2.59 12.42L11.59 21.42A2 2 0 0 0 13 22A2 2 0 0 0 14.41 21.41L21.41 14.41A2 2 0 0 0 22 13A2 2 0 0 0 21.41 11.58M13 20L4 11V4H11L20 13M6.5 5A1.5 1.5 0 1 1 5 6.5A1.5 1.5 0 0 1 6.5 5Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_tap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M10,9A1,1 0 0,1 11,8A1,1 0 0,1 12,9V13.47L13.21,13.6L18.15,15.79C18.68,16.03 19,16.56 19,17.14V21.5C18.97,22.32 18.32,22.97 17.5,23H11C10.62,23 10.26,22.85 10,22.57L5.1,18.37L5.84,17.6C6.03,17.39 6.3,17.28 6.58,17.28H6.8L10,19V9M11,5A4,4 0 0,1 15,9C15,10.5 14.2,11.77 13,12.46V11.24C13.61,10.69 14,9.89 14,9A3,3 0 0,0 11,6A3,3 0 0,0 8,9C8,9.89 8.39,10.69 9,11.24V12.46C7.8,11.77 7,10.5 7,9A4,4 0 0,1 11,5Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_tap_reorder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M4,3L1,6H3V9H1L4,12L7,9H5V6H7L4,3M11,8A1,1 0 0,0 10,9V19L6.8,17.28H6.58C6.3,17.28 6.03,17.39 5.84,17.6L5.1,18.37L10,22.57C10.26,22.85 10.62,23 11,23H17.5A1.5,1.5 0 0,0 19,21.5V17.14C19,16.56 18.68,16.03 18.15,15.79L13.21,13.6L12,13.47V9A1,1 0 0,0 11,8Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_timelapse.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M16.24,7.75c-1.17,-1.17 -2.7,-1.76 -4.24,-1.76v6l-4.24,4.24c2.34,2.34 6.14,2.34 8.49,0 2.34,-2.34 2.34,-6.14 -0.01,-8.48zM12,1.99c-5.52,0 -10,4.48 -10,10s4.48,10 10,10 10,-4.48 10,-10 -4.48,-10 -10,-10zM12,19.99c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_timer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M19.03,7.39L20.45,5.97C20,5.46 19.55,5 19.04,4.56L17.62,6C16.07,4.74 14.12,4 12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22C17,22 21,17.97 21,13C21,10.88 20.26,8.93 19.03,7.39M11,14H13V8H11M15,1H9V3H15V1Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_timer_run.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M15 3H9V1H15V3M11 14H13V8H11V14M19 13C19.7 13 20.36 13.13 21 13.35C21 13.23 21 13.12 21 13C21 10.88 20.26 8.93 19.03 7.39L20.45 5.97C20 5.46 19.55 5 19.04 4.56L17.62 6C16.07 4.74 14.12 4 12 4C7.03 4 3 8.03 3 13S7.03 22 12 22C12.59 22 13.16 21.94 13.71 21.83C13.4 21.25 13.18 20.6 13.08 19.91C12.72 19.96 12.37 20 12 20C8.13 20 5 16.87 5 13S8.13 6 12 6 19 9.13 19 13M17 16V22L22 19L17 16Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_unpin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M8,6.2V4H7V2H17V4H16V12L18,14V16H17.8L14,12.2V4H10V8.2L8,6.2M20,20.7L18.7,22L12.8,16.1V22H11.2V16H6V14L8,12V11.3L2,5.3L3.3,4L20,20.7M8.8,14H10.6L9.7,13.1L8.8,14Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_updated.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M12 20C12.4 20 12.7 20 13.1 19.9C13.2 20.6 13.4 21.3 13.7 21.8C13.2 21.9 12.6 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2C17.5 2 22 6.5 22 12C22 12.5 21.92 12.97 21.84 13.5L21.8 13.7C21.2 13.4 20.6 13.2 19.9 13.1C20 12.7 20 12.4 20 12C20 7.6 16.4 4 12 4C7.6 4 4 7.6 4 12C4 16.4 7.6 20 12 20M12.5 12.3L15.6 14.1C15.2 14.4 14.8 14.7 14.5 15.1L11 13V7H12.5V12.3M17.74 17.75L19 15L20.25 17.75L23 19L20.25 20.26L19 23L17.74 20.26L15 19L17.74 17.75Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_updated_checked.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M14.4 15.1C13.5 16.2 13 17.5 13 19C13 20 13.3 21 13.7 21.8C13.2 21.9 12.6 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2C17.5 2 22 6.5 22 12C22 12.5 21.92 12.97 21.84 13.5L21.8 13.7C21 13.3 20 13 19 13C17.7 13 16.6 13.4 15.6 14.1L12.5 12.2V7H11V13L14.4 15.1M17.74 17.75L19 15L20.25 17.75L23 19L20.25 20.26L19 23L17.74 20.26L15 19L17.74 17.75Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_updated_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/normal\"\n\t\tandroid:drawable=\"@drawable/ic_updated\"\n\t\tandroid:state_checked=\"false\" />\n\n\t<item\n\t\tandroid:id=\"@+id/checked\"\n\t\tandroid:drawable=\"@drawable/ic_updated_checked\"\n\t\tandroid:state_checked=\"true\" />\n\n\t<!--<transition\n\t\tandroid:drawable=\"@drawable/TODO\"\n\t\tandroid:fromId=\"@id/normal\"\n\t\tandroid:toId=\"@id/checked\" />-->\n\n</animated-selector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_usage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#000\"\n        android:pathData=\"M13,2.05V5.08C16.39,5.57 19,8.47 19,12C19,12.9 18.82,13.75 18.5,14.54L21.12,16.07C21.68,14.83 22,13.45 22,12C22,6.82 18.05,2.55 13,2.05M12,19A7,7 0 0,1 5,12C5,8.47 7.61,5.57 11,5.08V2.05C5.94,2.55 2,6.81 2,12A10,10 0 0,0 12,22C15.3,22 18.23,20.39 20.05,17.91L17.45,16.38C16.17,18 14.21,19 12,19Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_user.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_voice_input.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:height=\"24dp\"\n\tandroid:width=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path android:fillColor=\"#000\" android:pathData=\"M17.3,11C17.3,14 14.76,16.1 12,16.1C9.24,16.1 6.7,14 6.7,11H5C5,14.41 7.72,17.23 11,17.72V21H13V17.72C16.28,17.23 19,14.41 19,11M10.8,4.9C10.8,4.24 11.34,3.7 12,3.7C12.66,3.7 13.2,4.24 13.2,4.9L13.19,11.1C13.19,11.76 12.66,12.3 12,12.3C11.34,12.3 10.8,11.76 10.8,11.1M12,14A3,3 0 0,0 15,11V5A3,3 0 0,0 12,2A3,3 0 0,0 9,5V11A3,3 0 0,0 12,14Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_web.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-0.32-1.25-0.78-2.45-1.38-3.56 1.84 0.63 3.37 1.91 4.33 3.56zM12 4.04c0.83 1.2 1.48 2.53 1.91 3.96h-3.82c0.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s0.1-1.36 0.26-2h3.38c-0.08 0.66-0.14 1.32-0.14 2 0 0.68 0.06 1.34 0.14 2H4.26zm0.82 2h2.95c0.32 1.25 0.78 2.45 1.38 3.56-1.84-0.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c0.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-0.83-1.2-1.48-2.53-1.91-3.96h3.82c-0.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-0.09-0.66-0.16-1.32-0.16-2 0-0.68 0.07-1.35 0.16-2h4.68c0.09 0.65 0.16 1.32 0.16 2 0 0.68-0.07 1.34-0.16 2zm0.25 5.56c0.6-1.11 1.06-2.31 1.38-3.56h2.95c-0.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c0.08-0.66 0.14-1.32 0.14-2 0-0.68-0.06-1.34-0.14-2h3.38C19.9 10.64 20 11.31 20 12s-0.1 1.36-0.26 2h-3.38z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_welcome.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24.0\"\n\tandroid:viewportHeight=\"24.0\">\n\t<path\n\t\tandroid:fillColor=\"@android:color/white\"\n\t\tandroid:pathData=\"M12 2C13.1 2 14 2.9 14 4S13.1 6 12 6 10 5.1 10 4 10.9 2 12 2M15.9 8.1C15.5 7.7 14.8 7 13.5 7H11C8.2 7 6 4.8 6 2H4C4 5.2 6.1 7.8 9 8.7V22H11V16H13V22H15V10.1L19 14L20.4 12.6L15.9 8.1Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_zoom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M15.5,14L20.5,19L19,20.5L14,15.5V14.71L13.73,14.43C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.43,13.73L14.71,14H15.5M9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14M12,10H10V12H9V10H7V9H9V7H10V9H12V10Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_zoom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000000\"\n\t\tandroid:pathData=\"M15.5,14H14.71L14.43,13.73C15.41,12.59 16,11.11 16,9.5A6.5,6.5 0 0,0 9.5,3A6.5,6.5 0 0,0 3,9.5A6.5,6.5 0 0,0 9.5,16C11.11,16 12.59,15.41 13.73,14.43L14,14.71V15.5L19,20.5L20.5,19L15.5,14M9.5,14C7,14 5,12 5,9.5C5,7 7,5 9.5,5C12,5 14,7 14,9.5C14,12 12,14 9.5,14M7,9H12V10H7V9Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/list_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:color=\"@color/selector_overlay\">\n\t<item>\n\t\t<selector>\n\t\t\t<item\n\t\t\t\tandroid:bottom=\"2dp\"\n\t\t\t\tandroid:left=\"2dp\"\n\t\t\t\tandroid:right=\"2dp\"\n\t\t\t\tandroid:state_selected=\"true\"\n\t\t\t\tandroid:top=\"2dp\">\n\t\t\t\t<shape android:shape=\"rectangle\">\n\t\t\t\t\t<corners android:radius=\"@dimen/list_selector_corner\" />\n\t\t\t\t\t<solid android:color=\"@color/selector_overlay\" />\n\t\t\t\t</shape>\n\t\t\t</item>\n\t\t\t<item\n\t\t\t\tandroid:bottom=\"2dp\"\n\t\t\t\tandroid:left=\"2dp\"\n\t\t\t\tandroid:right=\"2dp\"\n\t\t\t\tandroid:state_activated=\"true\"\n\t\t\t\tandroid:top=\"2dp\">\n\t\t\t\t<shape android:shape=\"rectangle\">\n\t\t\t\t\t<corners android:radius=\"@dimen/list_selector_corner\" />\n\t\t\t\t\t<solid android:color=\"@color/selector_overlay\" />\n\t\t\t\t</shape>\n\t\t\t</item>\n\t\t\t<item\n\t\t\t\tandroid:bottom=\"2dp\"\n\t\t\t\tandroid:left=\"2dp\"\n\t\t\t\tandroid:right=\"2dp\"\n\t\t\t\tandroid:state_hovered=\"true\"\n\t\t\t\tandroid:top=\"2dp\">\n\t\t\t\t<shape android:shape=\"rectangle\">\n\t\t\t\t\t<corners android:radius=\"@dimen/list_selector_corner\" />\n\t\t\t\t\t<solid android:color=\"@color/selector_overlay\" />\n\t\t\t\t</shape>\n\t\t\t</item>\n\t\t</selector>\n\t</item>\n\t<item\n\t\tandroid:id=\"@android:id/mask\"\n\t\tandroid:bottom=\"2dp\"\n\t\tandroid:left=\"2dp\"\n\t\tandroid:right=\"2dp\"\n\t\tandroid:top=\"2dp\">\n\t\t<shape android:shape=\"rectangle\">\n\t\t\t<corners android:radius=\"@dimen/list_selector_corner\" />\n\t\t\t<solid android:color=\"@color/selector_overlay\" />\n\t\t</shape>\n\t</item>\n</ripple>\n"
  },
  {
    "path": "app/src/main/res/drawable/m3_popup_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item>\n\t\t<shape>\n\t\t\t<solid android:color=\"?attr/colorSurface\" />\n\t\t\t<corners android:radius=\"@dimen/m3_alert_dialog_corner_size\" />\n\t\t</shape>\n\t</item>\n\t<item>\n\t\t<shape>\n\t\t\t<solid android:color=\"@color/m3_popupmenu_overlay_color\" />\n\t\t\t<corners android:radius=\"@dimen/m3_alert_dialog_corner_size\" />\n\t\t</shape>\n\t</item>\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable/m3_spinner_popup_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<item>\n\t\t<shape>\n\t\t\t<solid android:color=\"?attr/colorSurfaceContainerHigh\" />\n\t\t\t<corners android:radius=\"4dp\" />\n\t\t</shape>\n\t</item>\n\t<item>\n\t\t<shape>\n\t\t\t<solid android:color=\"@color/m3_popupmenu_overlay_color\" />\n\t\t\t<corners android:radius=\"4dp\" />\n\t\t</shape>\n\t</item>\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable/search_bar_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:shape=\"rectangle\">\n\t<solid android:color=\"@color/colored_button\" />\n\t<corners android:radius=\"100dp\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/tabs_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<!-- Fills the entire area with the divider's color first... -->\n\t<item>\n\t\t<shape\n\t\t\tandroid:shape=\"rectangle\">\n\t\t\t<solid android:color=\"?attr/colorSurfaceVariant\"/>\n\t\t</shape>\n\t</item>\n\t<!-- ..., then draws a rectangle with the container color to cover the area not for the divider. -->\n\t<item\n\t\tandroid:bottom=\"1dp\">\n\t\t<shape\n\t\t\tandroid:shape=\"rectangle\">\n\t\t\t<solid android:color=\"?android:attr/colorBackground\"/>\n\t\t</shape>\n\t</item>\n</layer-list>\n"
  },
  {
    "path": "app/src/main/res/drawable-anydpi-v24/ic_bot.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"#FFFFFF\">\n  <group android:scaleX=\"0.92\"\n      android:scaleY=\"0.92\"\n      android:translateX=\"0.96\"\n      android:translateY=\"0.96\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,2A2,2 0,0 1,14 4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0,0 1,21 14H22A1,1 0,0 1,23 15V18A1,1 0,0 1,22 19H21V20A2,2 0,0 1,19 22H5A2,2 0,0 1,3 20V19H2A1,1 0,0 1,1 18V15A1,1 0,0 1,2 14H3A7,7 0,0 1,10 7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0,0 1,12 2M7.5,13A2.5,2.5 0,0 0,5 15.5A2.5,2.5 0,0 0,7.5 18A2.5,2.5 0,0 0,10 15.5A2.5,2.5 0,0 0,7.5 13M16.5,13A2.5,2.5 0,0 0,14 15.5A2.5,2.5 0,0 0,16.5 18A2.5,2.5 0,0 0,19 15.5A2.5,2.5 0,0 0,16.5 13Z\"/>\n  </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-anydpi-v24/ic_stat_auto_fix.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"#FFFFFF\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<group\n\t\tandroid:scaleX=\"0.92\"\n\t\tandroid:scaleY=\"0.92\"\n\t\tandroid:translateX=\"0.96\"\n\t\tandroid:translateY=\"0.96\">\n\t\t<path\n\t\t\tandroid:fillColor=\"#FF000000\"\n\t\t\tandroid:pathData=\"M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z\" />\n\t</group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-anydpi-v24/ic_stat_book_plus.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#FFFFFF\"\n\t\tandroid:pathData=\"M4 20H18V22H4C2.9 22 2 21.1 2 20V6H4V20M22 4V16C22 17.1 21.1 18 20 18H8C6.9 18 6 17.1 6 16V4C6 2.9 6.9 2 8 2H20C21.1 2 22 2.9 22 4M20 4H8V16H20V4M18 6H13V13L15.5 11.5L18 13V6Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-anydpi-v24/ic_stat_done.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"#FFFFFF\">\n  <group android:scaleX=\"0.92\"\n      android:scaleY=\"0.92\"\n      android:translateX=\"0.96\"\n      android:translateY=\"0.96\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M21,5L9,17L3.5,11.5L4.91,10.09L9,14.17L19.59,3.59L21,5M3,21V19H21V21H3Z\"/>\n  </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-anydpi-v24/ic_stat_paused.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"#FFFFFF\">\n  <group android:scaleX=\"1.3320464\"\n      android:scaleY=\"1.3320464\"\n      android:translateX=\"-3.984556\"\n      android:translateY=\"-3.984556\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z\"/>\n  </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-anydpi-v24/ic_stat_suggestion.xml",
    "content": "<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"#FFFFFF\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<group\n\t\tandroid:scaleX=\"1.0036364\"\n\t\tandroid:scaleY=\"1.0036364\"\n\t\tandroid:translateX=\"-0.043636363\"\n\t\tandroid:translateY=\"-0.043636363\">\n\t\t<path\n\t\t\tandroid:fillColor=\"#FF000000\"\n\t\t\tandroid:pathData=\"M8.6,1.535L6.711,4.715L3.1,5.525L3.439,9.207L1,11.994L3.439,14.773L3.1,18.465L6.711,19.283L8.6,22.465L12,20.994L15.4,22.457L17.289,19.273L20.9,18.457L20.561,14.773L23,11.994L20.561,9.217L20.9,5.535L17.289,4.715L15.4,1.535L12,2.996L8.6,1.535zM10.068,8.521L13.932,8.521C14.357,8.521 14.705,8.866 14.705,9.295L14.705,15.479L12,14.318L9.295,15.479L9.295,9.295A0.773,0.773 0,0 1,10.068 8.521z\" />\n\t</group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-night/avd_splash.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector xmlns:tools=\"http://schemas.android.com/tools\"\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:aapt=\"http://schemas.android.com/aapt\">\n\t<aapt:attr name=\"android:drawable\">\n\t\t<vector\n\t\t\tandroid:name=\"splash\"\n\t\t\tandroid:width=\"320dp\"\n\t\t\tandroid:height=\"320dp\"\n\t\t\tandroid:viewportWidth=\"320\"\n\t\t\tandroid:viewportHeight=\"320\">\n\t\t\t<group\n\t\t\t\tandroid:name=\"scaleme\"\n\t\t\t\tandroid:pivotX=\"160\"\n\t\t\t\tandroid:pivotY=\"160\"\n\t\t\t\tandroid:scaleX=\"1\"\n\t\t\t\tandroid:scaleY=\"1\">\n\t\t\t\t<group android:name=\"bg\">\n\t\t\t\t\t<path\n\t\t\t\t\t\tandroid:name=\"circle\"\n\t\t\t\t\t\tandroid:pathData=\"M 160 110 C 146.744 110 134.018 115.271 124.645 124.645 C 115.271 134.018 110 146.744 110 160 C 110 173.256 115.271 185.982 124.645 195.355 C 134.018 204.729 146.744 210 160 210 C 173.256 210 185.982 204.729 195.355 195.355 C 204.729 185.982 210 173.256 210 160 C 210 146.744 204.729 134.018 195.355 124.645 C 185.982 115.271 173.256 110 160 110 Z\"\n\t\t\t\t\t\tandroid:fillColor=\"@color/m3_sys_color_dynamic_dark_primary_container\"\n\t\t\t\t\t\tandroid:fillAlpha=\"0\"\n\t\t\t\t\t\tandroid:strokeWidth=\"1\" />\n\t\t\t\t</group>\n\t\t\t\t<group\n\t\t\t\t\tandroid:name=\"fg\"\n\t\t\t\t\tandroid:pivotX=\"160\"\n\t\t\t\t\tandroid:pivotY=\"160\"\n\t\t\t\t\tandroid:scaleX=\"0\"\n\t\t\t\t\tandroid:scaleY=\"0\">\n\t\t\t\t\t<clip-path\n\t\t\t\t\t\tandroid:name=\"mask\"\n\t\t\t\t\t\tandroid:pathData=\"M 160 60 C 133.489 60 108.036 70.543 89.289 89.289 C 70.543 108.036 60 133.489 60 160 C 60 186.511 70.543 211.964 89.289 230.711 C 108.036 249.457 133.489 260 160 260 C 186.511 260 211.964 249.457 230.711 230.711 C 249.457 211.964 260 186.511 260 160 C 260 133.489 249.457 108.036 230.711 89.289 C 211.964 70.543 186.511 60 160 60 Z\" />\n\t\t\t\t\t<group\n\t\t\t\t\t\tandroid:name=\"totoro_group\"\n\t\t\t\t\t\tandroid:scaleX=\"0.73903004444\"\n\t\t\t\t\t\tandroid:scaleY=\"0.73903004444\">\n\t\t\t\t\t\t<clip-path android:pathData=\"M0.81,0.07h432v432h-432z\" />\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tandroid:name=\"totoro\"\n\t\t\t\t\t\t\tandroid:pathData=\"M242.26,418.58c-44.77,0 -89.54,0 -134.31,0c-6.96,0 -7.12,-0.56 -6.31,-7.57c6.75,-58.64 31.14,-110.6 61.45,-160.07c6.71,-10.95 9.45,-22.06 11.05,-34.22c1.43,-10.89 5.32,-20.99 13.4,-28.93c5.67,-5.58 9.49,-5.72 14.67,0.29c6.42,7.45 12.41,15.28 18.2,23.23c2.01,2.76 3.88,4.35 7.39,4.3c9.86,-0.15 19.72,-0.13 29.58,-0.01c3.26,0.04 5.21,-1.26 7.14,-3.9c6.02,-8.18 12.19,-16.28 18.74,-24.04c4.56,-5.41 8.36,-5.29 13.65,-0.51c8.35,7.53 11.83,17.58 13.9,28.16c1.14,5.82 1.07,11.88 2.18,17.71c0.61,3.18 2.21,6.32 3.97,9.1c19.34,30.51 35.86,62.44 48.61,96.27c8.91,23.64 15.25,47.93 18.01,73.09c0.72,6.6 0.28,7.1 -6.53,7.1C332.12,418.59 287.19,418.58 242.26,418.58zM172.46,404.96c0.23,-0.04 1.58,0.09 2.4,-0.51c8.32,-6.01 16.3,-5.48 24.88,-0.06c3.28,2.07 7.64,0.92 10.26,-2.87c2.4,-3.47 2.93,-7.99 -0.37,-10.11c-6.96,-4.48 -14.69,-7.79 -22.16,-11.45c-0.71,-0.35 -1.97,0.02 -2.8,0.42c-6.1,2.95 -12.31,5.72 -18.16,9.11c-3.47,2.01 -4.86,5.62 -3.44,9.68C164.45,403.11 167.3,405.15 172.46,404.96zM218.13,370.48c-0.72,7.32 5.26,12.42 10.7,10.65c3.5,-1.14 6.64,-3.36 10.15,-4.45c2.19,-0.68 4.93,-0.73 7.1,-0.05c3.5,1.09 6.63,3.81 10.14,4.31c2.71,0.39 6.58,-0.8 8.42,-2.72c1.69,-1.77 2.38,-5.74 1.7,-8.26c-1.66,-6.13 -21.82,-15 -27.76,-12.78c-0.3,0.11 -0.6,0.25 -0.88,0.39c-4.76,2.37 -9.72,4.42 -14.2,7.23C220.99,366.38 219.28,369.23 218.13,370.48zM312.12,405.04c5.83,-0.02 8.75,-2.02 9.96,-6c1.29,-4.24 -0.29,-7.83 -4,-9.92c-5.46,-3.07 -11.09,-5.89 -16.87,-8.3c-1.97,-0.82 -4.88,-0.73 -6.87,0.1c-5.49,2.28 -10.81,5.01 -16.03,7.87c-4.63,2.54 -6.06,7.19 -3.97,11.49c2.04,4.18 6.73,5.95 11.55,4.09c2.69,-1.04 5.36,-2.29 7.8,-3.82c3.12,-1.96 5.81,-1.75 8.95,0.07C306.06,402.61 309.82,403.99 312.12,405.04zM186.6,262.09c0.05,8.66 5.72,15.1 14.55,16.52c7.21,1.16 15.06,-3.96 17.57,-11.47c2.13,-6.36 0.68,-9.3 -5.95,-10.15c-3.72,-0.47 -4.65,-1.86 -5.04,-5.35c-0.62,-5.66 -3.07,-7 -8.67,-5.72C191.46,247.66 186.55,254.03 186.6,262.09zM282.02,278.75c7.26,0.03 14.41,-5.46 16.13,-12.38c1.46,-5.89 0.26,-8.38 -5.73,-9.23c-4.07,-0.58 -5.45,-2.48 -5.77,-6.12c-0.33,-3.76 -2.15,-6.05 -6.28,-5.61c-8.6,0.91 -15.51,8.95 -14.97,17.39C265.96,271.4 273.59,278.72 282.02,278.75zM259.29,278.52c-0.09,-5.33 -3.97,-9.16 -8.9,-9.28c-5.17,-0.13 -10.35,0 -15.52,-0.04c-3.82,-0.03 -6.54,1.76 -8.08,5.12c-1.56,3.4 -0.95,6.7 1.57,9.44c2.62,2.85 5.33,5.66 8.23,8.22c3.42,3.02 7.46,3.55 11.05,0.59c3.59,-2.97 6.8,-6.44 9.9,-9.93C258.63,281.4 258.91,279.45 259.29,278.52z\"\n\t\t\t\t\t\t\tandroid:fillType=\"evenOdd\"\n\t\t\t\t\t\t\tandroid:fillColor=\"@color/m3_sys_color_dynamic_dark_primary\"\n\t\t\t\t\t\t\tandroid:fillAlpha=\"0\"\n\t\t\t\t\t\t\tandroid:strokeWidth=\"1\"\n\t\t\t\t\t\t\ttools:targetApi=\"s\" />\n\t\t\t\t\t</group>\n\t\t\t\t</group>\n\t\t\t</group>\n\t\t</vector>\n\t</aapt:attr>\n\t<target android:name=\"circle\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"pathData\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:valueFrom=\"M 160 110 C 146.744 110 134.018 115.271 124.645 124.645 C 115.271 134.018 110 146.744 110 160 C 110 173.256 115.271 185.982 124.645 195.355 C 134.018 204.729 146.744 210 160 210 C 173.256 210 185.982 204.729 195.355 195.355 C 204.729 185.982 210 173.256 210 160 C 210 146.744 204.729 134.018 195.355 124.645 C 185.982 115.271 173.256 110 160 110 Z\"\n\t\t\t\t\tandroid:valueTo=\"M 160 60 C 133.489 60 108.036 70.543 89.289 89.289 C 70.543 108.036 60 133.489 60 160 C 60 186.511 70.543 211.964 89.289 230.711 C 108.036 249.457 133.489 260 160 260 C 186.511 260 211.964 249.457 230.711 230.711 C 249.457 211.964 260 186.511 260 160 C 260 133.489 249.457 108.036 230.711 89.289 C 211.964 70.543 186.511 60 160 60 Z\"\n\t\t\t\t\tandroid:valueType=\"pathType\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/overshoot_interpolator\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"fillAlpha\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"totoro_group\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:propertyName=\"translateY\"\n\t\t\t\t\tandroid:valueFrom=\"160\"\n\t\t\t\t\tandroid:valueTo=\"0\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:valueFrom=\"0.5\"\n\t\t\t\t\tandroid:valueTo=\"0.73903004444\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"totoro\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"fillAlpha\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"100\"\n\t\t\t\t\tandroid:valueFrom=\"0\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:interpolator/fast_out_slow_in\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n\t<target android:name=\"fg\">\n\t\t<aapt:attr name=\"android:animation\">\n\t\t\t<set>\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"scaleX\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:valueFrom=\"0.5\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/overshoot_interpolator\" />\n\t\t\t\t<objectAnimator\n\t\t\t\t\tandroid:propertyName=\"scaleY\"\n\t\t\t\t\tandroid:startOffset=\"200\"\n\t\t\t\t\tandroid:duration=\"300\"\n\t\t\t\t\tandroid:valueFrom=\"0.5\"\n\t\t\t\t\tandroid:valueTo=\"1\"\n\t\t\t\t\tandroid:valueType=\"floatType\"\n\t\t\t\t\tandroid:interpolator=\"@android:anim/overshoot_interpolator\" />\n\t\t\t</set>\n\t\t</aapt:attr>\n\t</target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_alternatives.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.CollapsingToolbarLayout\n\t\t\tandroid:id=\"@+id/collapsingToolbarLayout\"\n\t\t\tstyle=\"?attr/collapsingToolbarLayoutMediumStyle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\"\n\t\t\tapp:toolbarId=\"@id/toolbar\">\n\n\t\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\t\tapp:layout_collapseMode=\"pin\"\n\t\t\t\ttools:title=\"@string/alternatives\" />\n\n\t\t</com.google.android.material.appbar.CollapsingToolbarLayout>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\ttools:listitem=\"@layout/item_manga_alternative\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_app_update.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"24dp\"\n\t\tandroid:drawablePadding=\"@dimen/screen_padding\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/app_update_available\"\n\t\tandroid:textAppearance=\"?textAppearanceHeadline5\"\n\t\tapp:drawableTint=\"?colorPrimary\"\n\t\tapp:drawableTopCompat=\"@drawable/ic_app_update\" />\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"@dimen/screen_padding\"\n\t\tandroid:max=\"100\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_error\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:textColor=\"?colorError\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:text=\"@string/error_corrupted_file\"\n\t\ttools:visibility=\"visible\" />\n\n\t<ScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginTop=\"@dimen/screen_padding\"\n\t\tandroid:layout_weight=\"1\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_content\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:paddingHorizontal=\"@dimen/screen_padding\"\n\t\t\tandroid:paddingBottom=\"@dimen/screen_padding\"\n\t\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t</ScrollView>\n\n\t<com.google.android.material.dockedtoolbar.DockedToolbarLayout\n\t\tandroid:id=\"@+id/docked_toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<FrameLayout\n\t\t\tandroid:id=\"@+id/docked_toolbar_child\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"@dimen/m3_comp_toolbar_docked_container_height\">\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_cancel\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|start\"\n\t\t\t\tandroid:text=\"@android:string/cancel\" />\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_update\"\n\t\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|end\"\n\t\t\t\tandroid:enabled=\"false\"\n\t\t\t\tandroid:text=\"@string/update\" />\n\n\t\t</FrameLayout>\n\t</com.google.android.material.dockedtoolbar.DockedToolbarLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_appwidget_recent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.MaterialToolbar\n\t\tandroid:id=\"@id/toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\tapp:layout_collapseMode=\"pin\"\n\t\ttools:title=\"@string/recent_manga\">\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"end\"\n\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\tandroid:text=\"@string/done\" />\n\n\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\tandroid:id=\"@+id/switch_background\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\t\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\t\tandroid:text=\"@string/background\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_scrollFlags=\"scroll\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_appwidget_shelf.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\tapp:layout_collapseMode=\"pin\"\n\t\t\ttools:title=\"@string/manga_shelf\">\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"end\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\t\tandroid:text=\"@string/done\" />\n\n\t\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\tandroid:id=\"@+id/switch_background\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\tandroid:checked=\"true\"\n\t\t\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\t\t\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\t\t\tandroid:text=\"@string/background\"\n\t\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\t\tapp:layout_scrollFlags=\"scroll\" />\n\n\t\t<TextView\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:gravity=\"center_vertical|start\"\n\t\t\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\t\t\tandroid:paddingTop=\"6dp\"\n\t\t\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:text=\"@string/favourites_categories\"\n\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:paddingVertical=\"@dimen/list_spacing_normal\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\ttools:listitem=\"@layout/item_checkable_single\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_browser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<WebView\n\t\tandroid:id=\"@+id/webView\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\" />\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\"\n\t\tapp:trackCornerRadius=\"0dp\"\n\t\ttools:visibility=\"visible\" />\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_categories.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.CollapsingToolbarLayout\n\t\t\tandroid:id=\"@+id/collapsingToolbarLayout\"\n\t\t\tstyle=\"?attr/collapsingToolbarLayoutMediumStyle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\"\n\t\t\tapp:toolbarId=\"@id/toolbar\">\n\n\t\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\t\tapp:layout_collapseMode=\"pin\">\n\n\t\t\t\t<Button\n\t\t\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_gravity=\"end\"\n\t\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\t\t\tandroid:text=\"@string/done\"\n\t\t\t\t\tandroid:visibility=\"gone\" />\n\n\t\t\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t\t</com.google.android.material.appbar.CollapsingToolbarLayout>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\ttools:listitem=\"@layout/item_category\" />\n\n\t<com.google.android.material.floatingactionbutton.FloatingActionButton\n\t\tandroid:id=\"@+id/fab_add\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_margin=\"16dp\"\n\t\tandroid:contentDescription=\"@string/add_new_category\"\n\t\tandroid:src=\"@drawable/ic_add\"\n\t\tapp:fabSize=\"normal\"\n\t\tapp:layout_anchor=\"@id/recyclerView\"\n\t\tapp:layout_anchorGravity=\"bottom|end\"\n\t\tapp:layout_behavior=\"com.google.android.material.behavior.HideViewOnScrollBehavior\"\n\t\tapp:layout_dodgeInsetEdges=\"bottom\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_category_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.MaterialToolbar\n\t\tandroid:id=\"@+id/toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"?attr/actionBarSize\">\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"end\"\n\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\tandroid:text=\"@string/save\" />\n\n\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t<ScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:overScrollMode=\"ifContentScrolls\">\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:padding=\"16dp\">\n\n\t\t\t<com.google.android.material.textfield.TextInputLayout\n\t\t\t\tandroid:id=\"@+id/layout_name\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\t\t\tandroid:id=\"@+id/edit_name\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:hint=\"@string/name\"\n\t\t\t\t\tandroid:imeOptions=\"actionDone\"\n\t\t\t\t\tandroid:inputType=\"textCapSentences\"\n\t\t\t\t\tandroid:maxLength=\"120\" />\n\n\t\t\t</com.google.android.material.textfield.TextInputLayout>\n\n\t\t\t<com.google.android.material.textfield.TextInputLayout\n\t\t\t\tandroid:id=\"@+id/layout_sort\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\tapp:endIconMode=\"dropdown_menu\">\n\n\t\t\t\t<AutoCompleteTextView\n\t\t\t\t\tandroid:id=\"@+id/edit_sort\"\n\t\t\t\t\tstyle=\"?editTextStyle\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:hint=\"@string/sort_order\"\n\t\t\t\t\tandroid:inputType=\"none\" />\n\n\t\t\t</com.google.android.material.textfield.TextInputLayout>\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_tracker\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\tandroid:text=\"@string/check_for_new_chapters\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_shelf\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\tandroid:text=\"@string/show_on_shelf\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_error\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\tandroid:textColor=\"?colorError\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:text=\"@tools:sample/lorem[4]\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t</LinearLayout>\n\n\t</ScrollView>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_color_filter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.MaterialToolbar\n\t\tandroid:id=\"@+id/toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\ttools:navigationIcon=\"@drawable/abc_ic_clear_material\"\n\t\ttools:title=\"@string/color_correction\">\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"end\"\n\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\tandroid:text=\"@string/done\" />\n\n\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t<ScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:overScrollMode=\"ifContentScrolls\">\n\n\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:padding=\"@dimen/margin_normal\">\n\n\t\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\t\tandroid:id=\"@+id/imageView_before\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"0dp\"\n\t\t\t\tandroid:padding=\"2dp\"\n\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\tapp:allowRgb565=\"false\"\n\t\t\t\tapp:decodeRegion=\"true\"\n\t\t\t\tapp:layout_constraintDimensionRatio=\"W,14:9\"\n\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_arrow\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\tapp:shapeAppearance=\"?shapeAppearanceCornerLarge\"\n\t\t\t\tapp:strokeColor=\"?colorOutline\"\n\t\t\t\tapp:strokeWidth=\"1dp\"\n\t\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\t\t\tandroid:id=\"@+id/progress_before\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:indeterminate=\"true\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_before\" />\n\n\t\t\t<ImageView\n\t\t\t\tandroid:id=\"@+id/imageView_arrow\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:contentDescription=\"@null\"\n\t\t\t\tandroid:padding=\"@dimen/margin_normal\"\n\t\t\t\tandroid:src=\"@drawable/ic_arrow_forward\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_before\" />\n\n\t\t\t<com.google.android.material.imageview.ShapeableImageView\n\t\t\t\tandroid:id=\"@+id/imageView_after\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"0dp\"\n\t\t\t\tandroid:padding=\"2dp\"\n\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\tapp:layout_constraintDimensionRatio=\"W,14:9\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_arrow\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\tapp:shapeAppearance=\"?shapeAppearanceCornerLarge\"\n\t\t\t\tapp:strokeColor=\"?colorOutline\"\n\t\t\t\tapp:strokeWidth=\"1dp\"\n\t\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\t\t\tandroid:id=\"@+id/progress_after\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:indeterminate=\"true\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_after\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_after\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"@id/imageView_after\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_after\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_invert\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\tandroid:text=\"@string/invert_colors\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/imageView_before\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_grayscale\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\tandroid:text=\"@string/grayscale\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/switch_invert\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_brightness\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/brightness\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/switch_grayscale\" />\n\n\t\t\t<com.google.android.material.slider.Slider\n\t\t\t\tandroid:id=\"@+id/slider_brightness\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:labelFor=\"@id/textView_brightness\"\n\t\t\t\tandroid:value=\"0.0\"\n\t\t\t\tandroid:valueFrom=\"-1.0\"\n\t\t\t\tandroid:valueTo=\"1.0\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_brightness\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_contrast\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:text=\"@string/contrast\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/slider_brightness\" />\n\n\t\t\t<com.google.android.material.slider.Slider\n\t\t\t\tandroid:id=\"@+id/slider_contrast\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:value=\"0.0\"\n\t\t\t\tandroid:valueFrom=\"-1.0\"\n\t\t\t\tandroid:valueTo=\"1.0\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_contrast\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_book\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\t\tandroid:text=\"@string/book_effect\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/slider_contrast\" />\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_reset\"\n\t\t\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\tandroid:text=\"@string/reset\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/switch_book\" />\n\n\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n\t</ScrollView>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_container.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.CollapsingToolbarLayout\n\t\t\tandroid:id=\"@+id/collapsingToolbarLayout\"\n\t\t\tstyle=\"?attr/collapsingToolbarLayoutMediumStyle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\"\n\t\t\tapp:toolbarId=\"@id/toolbar\">\n\n\t\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\t\tapp:layout_collapseMode=\"pin\"\n\t\t\t\ttools:title=\"Title\" />\n\n\t\t</com.google.android.material.appbar.CollapsingToolbarLayout>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:clipToPadding=\"false\"\n\ttools:context=\".details.ui.DetailsActivity\"\n\ttools:ignore=\"ViewBindingType\"\n\ttools:viewBindingType=\"android.view.ViewGroup\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:elevation=\"0dp\"\n\t\tandroid:fitsSystemWindows=\"true\"\n\t\tapp:elevation=\"0dp\"\n\t\tapp:liftOnScroll=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n\t\tandroid:id=\"@+id/swipeRefreshLayout\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\">\n\n\t\t<androidx.core.widget.NestedScrollView\n\t\t\tandroid:id=\"@+id/scrollView\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:scrollIndicators=\"top\"\n\t\t\tandroid:scrollbars=\"vertical\">\n\n\t\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\tandroid:paddingBottom=\"@dimen/margin_normal\">\n\n\t\t\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"0dp\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\t\tandroid:background=\"?colorSecondaryContainer\"\n\t\t\t\t\tandroid:clipToOutline=\"true\"\n\t\t\t\t\tandroid:foreground=\"?selectableItemBackground\"\n\t\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\t\tapp:allowRgb565=\"false\"\n\t\t\t\t\tapp:aspectRationHeight=\"0\"\n\t\t\t\t\tapp:aspectRationWidth=\"0\"\n\t\t\t\t\tapp:layout_constraintDimensionRatio=\"H,13:18\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintWidth_percent=\"0.3\"\n\t\t\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\t\t\t\tapp:useExistingDrawable=\"true\"\n\t\t\t\t\ttools:background=\"@tools:sample/backgrounds/scenic[5]\"\n\t\t\t\t\ttools:ignore=\"ContentDescription,UnusedAttribute\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_nsfw_16\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.TextView.Badge\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\t\t\t\tandroid:backgroundTint=\"@color/nsfw_16\"\n\t\t\t\t\tandroid:text=\"@string/nsfw_16\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_nsfw_18\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.TextView.Badge\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\t\t\t\tandroid:backgroundTint=\"@color/nsfw_18\"\n\t\t\t\t\tandroid:text=\"@string/nsfw\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_cover\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\t\tandroid:maxLines=\"@integer/details_title_lines\"\n\t\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceHeadlineSmall\"\n\t\t\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\t\tandroid:maxLines=\"3\"\n\t\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem[12]\" />\n\n\t\t\t\t<com.google.android.material.chip.Chip\n\t\t\t\t\tandroid:id=\"@+id/chip_favorite\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Dropdown\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\t\t\tapp:chipIcon=\"@drawable/ic_heart_outline\"\n\t\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_subtitle\"\n\t\t\t\t\ttools:text=\"@string/add_to_favourites\" />\n\n\t\t\t\t<androidx.constraintlayout.widget.Barrier\n\t\t\t\t\tandroid:id=\"@+id/barrier_header\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tapp:barrierDirection=\"bottom\"\n\t\t\t\t\tapp:barrierMargin=\"@dimen/margin_normal\"\n\t\t\t\t\tapp:constraint_referenced_ids=\"imageView_cover,chip_favorite\" />\n\n\t\t\t\t<include layout=\"@layout/layout_details_table\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_description_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:gravity=\"center_vertical|start\"\n\t\t\t\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:text=\"@string/description\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_description_more\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_progress_label\" />\n\n\t\t\t\t<Button\n\t\t\t\t\tandroid:id=\"@+id/button_description_more\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\t\t\tandroid:text=\"@string/more\"\n\t\t\t\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_description_title\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\t\tandroid:lineSpacingMultiplier=\"1.2\"\n\t\t\t\t\tandroid:maxLines=\"@integer/details_description_lines\"\n\t\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_description_title\"\n\t\t\t\t\ttools:ignore=\"UnusedAttribute\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\t\t\t\tandroid:id=\"@+id/chips_tags\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\t\tapp:chipSpacingHorizontal=\"6dp\"\n\t\t\t\t\tapp:chipSpacingVertical=\"6dp\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_description\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_scrobbling_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:gravity=\"center_vertical|start\"\n\t\t\t\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:text=\"@string/tracking\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_scrobbling_more\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/chips_tags\" />\n\n\t\t\t\t<Button\n\t\t\t\t\tandroid:id=\"@+id/button_scrobbling_more\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\t\t\tandroid:text=\"@string/manage\"\n\t\t\t\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_scrobbling_title\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n\t\t\t\t<androidx.recyclerview.widget.RecyclerView\n\t\t\t\t\tandroid:id=\"@+id/recyclerView_scrobbling\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\t\t\tandroid:layout_marginBottom=\"12dp\"\n\t\t\t\t\tandroid:nestedScrollingEnabled=\"false\"\n\t\t\t\t\tandroid:orientation=\"vertical\"\n\t\t\t\t\tandroid:overScrollMode=\"never\"\n\t\t\t\t\tandroid:scrollbars=\"none\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_scrobbling_title\"\n\t\t\t\t\ttools:itemCount=\"2\"\n\t\t\t\t\ttools:listitem=\"@layout/item_scrobbling_info\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t\t<androidx.constraintlayout.widget.Group\n\t\t\t\t\tandroid:id=\"@+id/group_scrobbling\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:constraint_referenced_ids=\"recyclerView_scrobbling,textView_scrobbling_title,button_scrobbling_more\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_related_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:gravity=\"center_vertical|start\"\n\t\t\t\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:text=\"@string/related_manga\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_related_more\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/recyclerView_scrobbling\" />\n\n\t\t\t\t<Button\n\t\t\t\t\tandroid:id=\"@+id/button_related_more\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\t\t\tandroid:text=\"@string/show_all\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_related_title\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n\t\t\t\t<androidx.recyclerview.widget.RecyclerView\n\t\t\t\t\tandroid:id=\"@+id/recyclerView_related\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginBottom=\"6dp\"\n\t\t\t\t\tandroid:clipToPadding=\"false\"\n\t\t\t\t\tandroid:nestedScrollingEnabled=\"false\"\n\t\t\t\t\tandroid:orientation=\"horizontal\"\n\t\t\t\t\tandroid:paddingStart=\"12dp\"\n\t\t\t\t\tandroid:paddingEnd=\"12dp\"\n\t\t\t\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_related_title\"\n\t\t\t\t\ttools:listitem=\"@layout/item_manga_grid\" />\n\n\t\t\t\t<androidx.constraintlayout.widget.Group\n\t\t\t\t\tandroid:id=\"@+id/group_related\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:constraint_referenced_ids=\"recyclerView_related,textView_related_title,button_related_more\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n\t\t</androidx.core.widget.NestedScrollView>\n\t</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/container_bottom_sheet\"\n\t\tandroid:name=\"org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet\"\n\t\tstyle=\"@style/Widget.Material3.BottomSheet\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:elevation=\"6dp\"\n\t\tandroid:nestedScrollingEnabled=\"false\"\n\t\tandroid:outlineProvider=\"background\"\n\t\tapp:behavior_fitToContents=\"false\"\n\t\tapp:behavior_hideable=\"false\"\n\t\tapp:behavior_peekHeight=\"@dimen/details_bs_peek_height\"\n\t\tapp:layout_behavior=\"com.google.android.material.bottomsheet.BottomSheetBehavior\"\n\t\tapp:paddingBottomSystemWindowInsets=\"false\"\n\t\ttools:layout=\"@layout/sheet_chapters_pages\" />\n\n\t<View\n\t\tandroid:id=\"@+id/navbarDim\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom\"\n\t\tandroid:background=\"?colorSurfaceContainerLow\"\n\t\tandroid:elevation=\"9dp\"\n\t\ttools:layout_height=\"10dp\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_downloads.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"true\">\n\n\t\t<com.google.android.material.appbar.CollapsingToolbarLayout\n\t\t\tandroid:id=\"@+id/collapsingToolbarLayout\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\"\n\t\t\tapp:toolbarId=\"@id/toolbar\">\n\n\t\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\t\tapp:layout_collapseMode=\"pin\" />\n\n\t\t</com.google.android.material.appbar.CollapsingToolbarLayout>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\ttools:listitem=\"@layout/item_download\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\n\t\tandroid:id=\"@+id/ssiv\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:restoreStrategy=\"deferred\"\n\t\ttools:background=\"@tools:sample/backgrounds/scenic\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/button_back\"\n\t\tandroid:layout_width=\"48dp\"\n\t\tandroid:layout_height=\"48dp\"\n\t\tandroid:layout_margin=\"@dimen/screen_padding\"\n\t\tandroid:background=\"@drawable/bg_circle_button\"\n\t\tandroid:contentDescription=\"@string/back\"\n\t\tandroid:elevation=\"@dimen/m3_sys_elevation_level1\"\n\t\tandroid:scaleType=\"center\"\n\t\tandroid:tooltipText=\"@string/back\"\n\t\tapp:srcCompat=\"?homeAsUpIndicator\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/button_menu\"\n\t\tandroid:layout_width=\"48dp\"\n\t\tandroid:layout_height=\"48dp\"\n\t\tandroid:layout_gravity=\"end\"\n\t\tandroid:layout_margin=\"@dimen/screen_padding\"\n\t\tandroid:background=\"@drawable/bg_circle_button\"\n\t\tandroid:contentDescription=\"@string/show_menu\"\n\t\tandroid:elevation=\"@dimen/m3_sys_elevation_level1\"\n\t\tandroid:scaleType=\"center\"\n\t\tandroid:tooltipText=\"@string/show_menu\"\n\t\tapp:srcCompat=\"@drawable/abc_ic_menu_overflow_material\" />\n\n\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:indeterminate=\"true\" />\n\n\t<ViewStub\n\t\tandroid:id=\"@+id/stub_error\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout=\"@layout/item_error_state\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_kitsu_auth.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:drawablePadding=\"@dimen/screen_padding\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/kitsu\"\n\t\tandroid:textAppearance=\"?textAppearanceHeadline5\"\n\t\tapp:drawableTint=\"?colorPrimary\"\n\t\tapp:drawableTopCompat=\"@drawable/ic_kitsu\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/email_password_enter_hint\"\n\t\tandroid:textAppearance=\"?textAppearanceSubtitle1\" />\n\n\t<com.google.android.material.textfield.TextInputLayout\n\t\tandroid:id=\"@+id/layout_email\"\n\t\tstyle=\"?textInputOutlinedStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"30dp\"\n\t\tapp:errorIconDrawable=\"@null\"\n\t\tapp:hintEnabled=\"false\">\n\n\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\tandroid:id=\"@+id/edit_email\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:autofillHints=\"username\"\n\t\t\tandroid:hint=\"@string/email\"\n\t\t\tandroid:imeOptions=\"actionNext\"\n\t\t\tandroid:inputType=\"textEmailAddress\"\n\t\t\tandroid:maxLength=\"512\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textSize=\"16sp\" />\n\n\t</com.google.android.material.textfield.TextInputLayout>\n\n\t<com.google.android.material.textfield.TextInputLayout\n\t\tandroid:id=\"@+id/layout_password\"\n\t\tstyle=\"?textInputOutlinedStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tapp:endIconMode=\"password_toggle\"\n\t\tapp:errorIconDrawable=\"@null\"\n\t\tapp:hintEnabled=\"false\">\n\n\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\tandroid:id=\"@+id/edit_password\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:autofillHints=\"password\"\n\t\t\tandroid:hint=\"@string/password\"\n\t\t\tandroid:imeOptions=\"actionDone\"\n\t\t\tandroid:inputType=\"textPassword\"\n\t\t\tandroid:maxLength=\"512\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textSize=\"16sp\" />\n\n\t</com.google.android.material.textfield.TextInputLayout>\n\n\t<View\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:visibility=\"invisible\" />\n\n\t<com.google.android.material.dockedtoolbar.DockedToolbarLayout\n\t\tandroid:id=\"@+id/docked_toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<FrameLayout\n\t\t\tandroid:id=\"@+id/docked_toolbar_child\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"@dimen/m3_comp_toolbar_docked_container_height\">\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_cancel\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|start\"\n\t\t\t\tandroid:text=\"@android:string/cancel\" />\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|end\"\n\t\t\t\tandroid:enabled=\"false\"\n\t\t\t\tandroid:text=\"@string/_continue\" />\n\n\t\t</FrameLayout>\n\t</com.google.android.material.dockedtoolbar.DockedToolbarLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\ttools:context=\".main.ui.MainActivity\">\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\ttools:layout=\"@layout/fragment_list\" />\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"@null\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:elevation=\"0dp\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tandroid:stateListAnimator=\"@null\"\n\t\tapp:elevation=\"0dp\"\n\t\tapp:liftOnScroll=\"false\"\n\t\tapp:liftOnScrollColor=\"@null\">\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.WindowInsetHolder\n\t\t\tandroid:id=\"@+id/insetsHolder\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"top\"\n\t\t\tandroid:fitsSystemWindows=\"true\"\n\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways|snap\" />\n\n\t\t<com.google.android.material.search.SearchBar\n\t\t\tandroid:id=\"@+id/search_bar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:hint=\"@string/search_manga\"\n\t\t\tapp:forceDefaultNavigationOnClickListener=\"true\"\n\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways|snap\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<com.google.android.material.search.SearchView\n\t\tandroid:id=\"@+id/search_view\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:hint=\"@string/search_hint\"\n\t\tapp:layout_anchor=\"@id/search_bar\">\n\n\t\t<androidx.recyclerview.widget.RecyclerView\n\t\t\tandroid:id=\"@+id/recyclerView_search\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:scrollbars=\"vertical\"\n\t\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n\t</com.google.android.material.search.SearchView>\n\n\t<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton\n\t\tandroid:id=\"@+id/fab\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"16dp\"\n\t\tandroid:text=\"@string/_continue\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:icon=\"@drawable/ic_read\"\n\t\tapp:layout_anchor=\"@id/bottomNav\"\n\t\tapp:layout_anchorGravity=\"top|end\"\n\t\tapp:layout_behavior=\"org.koitharu.kotatsu.main.ui.MainActionButtonBehavior\"\n\t\tapp:layout_dodgeInsetEdges=\"bottom\"\n\t\tapp:layout_insetEdge=\"bottom\"\n\t\ttools:ignore=\"InconsistentLayout\"\n\t\ttools:visibility=\"visible\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView\n\t\tandroid:id=\"@+id/bottomNav\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom\"\n\t\tandroid:fitsSystemWindows=\"false\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_manga_directories.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"true\">\n\n        <com.google.android.material.appbar.MaterialToolbar\n            android:id=\"@id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:layout_scrollFlags=\"noScroll\">\n\n        </com.google.android.material.appbar.MaterialToolbar>\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:orientation=\"vertical\"\n        android:paddingBottom=\"@dimen/list_spacing_large\"\n        android:scrollbars=\"vertical\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        app:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n        tools:listitem=\"@layout/item_storage_config2\" />\n\n    <com.google.android.material.progressindicator.LinearProgressIndicator\n        android:id=\"@+id/progressBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        android:visibility=\"gone\"\n        app:layout_anchor=\"@id/appbar\"\n        app:layout_anchorGravity=\"bottom\" />\n\n    <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton\n        android:id=\"@+id/fab_add\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:contentDescription=\"@string/pick_custom_directory\"\n        android:text=\"@string/add\"\n        app:fabSize=\"normal\"\n        app:icon=\"@drawable/ic_add\"\n        app:layout_anchor=\"@id/recyclerView\"\n        app:layout_anchorGravity=\"bottom|end\"\n        app:layout_behavior=\"org.koitharu.kotatsu.core.ui.util.ShrinkOnScrollBehavior\"\n        app:layout_dodgeInsetEdges=\"bottom\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_manga_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.CollapsingToolbarLayout\n\t\t\tandroid:id=\"@+id/collapsingToolbarLayout\"\n\t\t\tstyle=\"?attr/collapsingToolbarLayoutMediumStyle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways\"\n\t\t\tapp:toolbarId=\"@id/toolbar\">\n\n\t\t\t<LinearLayout\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\t\tandroid:gravity=\"bottom|end\"\n\t\t\t\tandroid:orientation=\"horizontal\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\t\tapp:layout_collapseMode=\"parallax\"\n\t\t\t\ttools:ignore=\"RtlSymmetry\">\n\n\t\t\t\t<com.google.android.material.button.MaterialButton\n\t\t\t\t\tandroid:id=\"@+id/button_order\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:visibility=\"invisible\"\n\t\t\t\t\tapp:icon=\"@drawable/ic_filter_menu\"\n\t\t\t\t\ttools:text=\"@string/newest\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t</LinearLayout>\n\n\t\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\t\tapp:layout_collapseMode=\"pin\"\n\t\t\t\ttools:title=\"Title\" />\n\n\t\t</com.google.android.material.appbar.CollapsingToolbarLayout>\n\n\t\t<androidx.fragment.app.FragmentContainerView\n\t\t\tandroid:id=\"@+id/container_filter_header\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:layout_scrollFlags=\"noScroll\"\n\t\t\ttools:layout=\"@layout/fragment_filter_header\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_override_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.MaterialToolbar\n\t\tandroid:id=\"@+id/toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"?attr/actionBarSize\">\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"end\"\n\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\tandroid:text=\"@string/save\" />\n\n\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t<ScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:overScrollMode=\"ifContentScrolls\">\n\n\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:paddingBottom=\"@dimen/screen_padding\">\n\n\t\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"0dp\"\n\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\tandroid:background=\"?colorSecondaryContainer\"\n\t\t\t\tandroid:clipToOutline=\"true\"\n\t\t\t\tandroid:foreground=\"?selectableItemBackground\"\n\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\tapp:layout_constraintDimensionRatio=\"H,13:18\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\tapp:layout_constraintWidth_percent=\"0.3\"\n\t\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\t\t\ttools:background=\"@tools:sample/backgrounds/scenic[5]\"\n\t\t\t\ttools:ignore=\"ContentDescription,UnusedAttribute\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_cover_title\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/margin_small\"\n\t\t\t\tandroid:text=\"@string/change_cover\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_cover\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n\t\t\t\tandroid:id=\"@+id/button_pick_file\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\tandroid:text=\"@string/pick_custom_file\"\n\t\t\t\tapp:drawableStartCompat=\"@drawable/ic_folder_file\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/textView_cover_title\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"@id/textView_cover_title\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_cover_title\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n\t\t\t\tandroid:id=\"@+id/button_pick_page\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:text=\"@string/pick_manga_page\"\n\t\t\t\tapp:drawableStartCompat=\"@drawable/ic_grid\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/textView_cover_title\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"@id/textView_cover_title\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/button_pick_file\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n\t\t\t\tandroid:id=\"@+id/button_reset_cover\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:text=\"@string/use_default_cover\"\n\t\t\t\tapp:drawableStartCompat=\"@drawable/ic_revert\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/textView_cover_title\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"@id/textView_cover_title\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/button_pick_page\" />\n\n\t\t\t<androidx.constraintlayout.widget.Barrier\n\t\t\t\tandroid:id=\"@+id/barrier_cover\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tapp:barrierDirection=\"bottom\"\n\t\t\t\tapp:constraint_referenced_ids=\"imageView_cover,button_reset_cover\" />\n\n\t\t\t<com.google.android.material.textfield.TextInputLayout\n\t\t\t\tandroid:id=\"@+id/layout_name\"\n\t\t\t\tstyle=\"?textInputOutlinedStyle\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tapp:endIconContentDescription=\"@string/reset\"\n\t\t\t\tapp:endIconDrawable=\"@drawable/ic_revert\"\n\t\t\t\tapp:endIconMode=\"custom\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/barrier_cover\">\n\n\t\t\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\t\t\tandroid:id=\"@+id/edit_name\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:hint=\"@string/name\"\n\t\t\t\t\tandroid:imeOptions=\"actionDone\"\n\t\t\t\t\tandroid:inputType=\"textCapSentences\"\n\t\t\t\t\tandroid:maxLength=\"120\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t\t\t</com.google.android.material.textfield.TextInputLayout>\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_tip\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/manga_override_hint\"\n\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/layout_name\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_error\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:textColor=\"?colorError\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_tip\"\n\t\t\t\ttools:text=\"@tools:sample/lorem[4]\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n\t</ScrollView>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\tapp:layout_collapseMode=\"pin\"\n\t\t\ttools:title=\"Title\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_protect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:padding=\"@dimen/screen_padding\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:drawablePadding=\"16dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/app_name\"\n\t\tandroid:textAppearance=\"?textAppearanceHeadline5\"\n\t\tapp:drawableTint=\"?colorPrimary\"\n\t\tapp:drawableTopCompat=\"@drawable/ic_lock\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/enter_password\"\n\t\tandroid:textAppearance=\"?textAppearanceSubtitle1\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\" />\n\n\t<com.google.android.material.textfield.TextInputLayout\n\t\tandroid:id=\"@+id/layout_password\"\n\t\tstyle=\"?textInputOutlinedStyle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"30dp\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_subtitle\"\n\t\tapp:passwordToggleEnabled=\"true\"\n\t\tapp:startIconDrawable=\"@android:color/transparent\">\n\n\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\tandroid:id=\"@+id/edit_password\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:gravity=\"center_horizontal\"\n\t\t\tandroid:imeOptions=\"actionDone\"\n\t\t\tandroid:importantForAutofill=\"no\"\n\t\t\tandroid:inputType=\"textPassword|textNoSuggestions\"\n\t\t\tandroid:maxLength=\"24\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAlignment=\"center\"\n\t\t\tandroid:textSize=\"16sp\"\n\t\t\ttools:text=\"1234\" />\n\n\t</com.google.android.material.textfield.TextInputLayout>\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_cancel\"\n\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@android:string/cancel\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_next\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:enabled=\"false\"\n\t\tandroid:text=\"@string/next\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_reader.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\ttools:background=\"@color/grey\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_timer\"\n\t\tstyle=\"?materialIconButtonOutlinedStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom|end\"\n\t\tandroid:layout_margin=\"16dp\"\n\t\tapp:backgroundTint=\"@color/bg_floating_button\"\n\t\tapp:icon=\"@drawable/ic_timelapse\"\n\t\tapp:layout_insetEdge=\"bottom\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.ZoomControl\n\t\tandroid:id=\"@+id/zoomControl\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom|end\"\n\t\tandroid:layout_margin=\"16dp\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:spacing=\"2dp\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_dodgeInsetEdges=\"bottom\"\n\t\ttools:visibility=\"visible\" />\n\n\t<org.koitharu.kotatsu.reader.ui.ReaderInfoBarView\n\t\tandroid:id=\"@+id/infoBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"top\"\n\t\tandroid:padding=\"6dp\"\n\t\tandroid:textSize=\"12sp\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<org.koitharu.kotatsu.reader.ui.ScrollTimerControlView\n\t\tandroid:id=\"@+id/timerControl\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom\"\n\t\tandroid:layout_margin=\"@dimen/screen_padding\"\n\t\tandroid:background=\"@drawable/bg_card\"\n\t\tandroid:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_dodgeInsetEdges=\"bottom\" />\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar_top\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\tapp:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\tapp:liftOnScroll=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\t\tapp:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\t\tapp:popupTheme=\"@style/ThemeOverlay.Kotatsu\"\n\t\t\ttools:menu=\"@menu/opt_reader\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<com.google.android.material.dockedtoolbar.DockedToolbarLayout\n\t\tandroid:id=\"@+id/toolbar_docked\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tapp:cardBackgroundColor=\"?colorSurfaceContainer\"\n\t\tapp:layout_insetEdge=\"bottom\">\n\n\t\t<org.koitharu.kotatsu.reader.ui.ReaderActionsView\n\t\t\tandroid:id=\"@+id/actionsView\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:minHeight=\"?actionBarSize\" />\n\n\t</com.google.android.material.dockedtoolbar.DockedToolbarLayout>\n\n\t<org.koitharu.kotatsu.reader.ui.ReaderToastView\n\t\tandroid:id=\"@+id/toastView\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom|center_horizontal\"\n\t\tandroid:layout_marginBottom=\"20dp\"\n\t\tandroid:background=\"@drawable/bg_reader_indicator\"\n\t\tandroid:drawablePadding=\"6dp\"\n\t\tandroid:elevation=\"1000dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tandroid:theme=\"@style/ThemeOverlay.Material3.Dark\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_dodgeInsetEdges=\"bottom\"\n\t\ttools:text=\"@string/loading_\"\n\t\ttools:visibility=\"visible\" />\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/layout_loading\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:background=\"@drawable/bg_card\"\n\t\tandroid:backgroundTint=\"?colorSurfaceContainer\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:outlineProvider=\"background\"\n\t\tandroid:padding=\"@dimen/screen_padding\">\n\n\t\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\t\tandroid:id=\"@+id/progressBar\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:indeterminate=\"true\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_loading\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:text=\"@string/loading_\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleMedium\" />\n\n\t</LinearLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_reader_tap_actions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.MaterialToolbar\n\t\tandroid:id=\"@id/toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"?attr/actionBarSize\" />\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<androidx.constraintlayout.widget.Guideline\n\t\t\tandroid:id=\"@+id/guideline_top\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tapp:layout_constraintGuide_percent=\"0.33\" />\n\n\t\t<androidx.constraintlayout.widget.Guideline\n\t\t\tandroid:id=\"@+id/guideline_bottom\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tapp:layout_constraintGuide_percent=\"0.67\" />\n\n\t\t<androidx.constraintlayout.widget.Guideline\n\t\t\tandroid:id=\"@+id/guideline_left\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tapp:layout_constraintGuide_percent=\"0.33\" />\n\n\t\t<androidx.constraintlayout.widget.Guideline\n\t\t\tandroid:id=\"@+id/guideline_right\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tapp:layout_constraintGuide_percent=\"0.67\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_top_left\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/guideline_top\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_top_center\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/guideline_top\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_top_right\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/guideline_top\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_center_left\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/textView_bottom_left\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_top_left\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_center\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/textView_bottom_center\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_top_center\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_center_right\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/textView_bottom_right\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_top_right\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_bottom_left\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/guideline_bottom\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_bottom_center\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/guideline_bottom\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_bottom_right\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?selectableItemBackground\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/guideline_bottom\" />\n\n\t\t<View\n\t\t\tandroid:layout_width=\"1dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?colorOutline\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"@id/guideline_right\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t\t<View\n\t\t\tandroid:layout_width=\"1dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?colorOutline\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"@id/guideline_left\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t\t<View\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"1dp\"\n\t\t\tandroid:background=\"?colorOutline\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/guideline_top\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/guideline_top\" />\n\n\t\t<View\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"1dp\"\n\t\t\tandroid:background=\"?colorOutline\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/guideline_bottom\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/guideline_bottom\" />\n\n\t\t<View\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"1dp\"\n\t\t\tandroid:background=\"?colorOutline\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_scrobbler_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.CollapsingToolbarLayout\n\t\t\tandroid:id=\"@+id/collapsingToolbarLayout\"\n\t\t\tstyle=\"?attr/collapsingToolbarLayoutMediumStyle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\"\n\t\t\tapp:toolbarId=\"@id/toolbar\">\n\n\t\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\t\tapp:layout_collapseMode=\"pin\">\n\n\t\t\t\t<org.koitharu.kotatsu.core.image.CoilImageView\n\t\t\t\t\tandroid:id=\"@+id/imageView_avatar\"\n\t\t\t\t\tandroid:layout_width=\"28dp\"\n\t\t\t\t\tandroid:layout_height=\"28dp\"\n\t\t\t\t\tandroid:layout_gravity=\"center_vertical|end\"\n\t\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\t\t\tandroid:padding=\"1dp\"\n\t\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\t\tapp:errorDrawable=\"@drawable/ic_shortcut_default\"\n\t\t\t\t\tapp:fallbackDrawable=\"@drawable/ic_shortcut_default\"\n\t\t\t\t\tapp:placeholderDrawable=\"@drawable/bg_badge_empty\"\n\t\t\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Circle\"\n\t\t\t\t\tapp:strokeColor=\"?colorOutline\"\n\t\t\t\t\tapp:strokeWidth=\"1dp\"\n\t\t\t\t\ttools:src=\"@tools:sample/avatars\" />\n\n\t\t\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t\t</com.google.android.material.appbar.CollapsingToolbarLayout>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\ttools:listitem=\"@layout/item_scrobbling_manga\" />\n\n\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_anchor=\"@id/recyclerView\"\n\t\tapp:layout_anchorGravity=\"center\"\n\t\ttools:visibility=\"visible\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tapp:elevation=\"0dp\"\n\t\tapp:liftOnScroll=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\t\tandroid:id=\"@id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:orientation=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tapp:liftOnScroll=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:layout_collapseMode=\"pin\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/container_search\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_setup_protect.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:padding=\"@dimen/screen_padding\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:drawablePadding=\"16dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/protect_application\"\n\t\tandroid:textAppearance=\"?textAppearanceHeadline5\"\n\t\tapp:drawableTint=\"?colorPrimary\"\n\t\tapp:drawableTopCompat=\"@drawable/ic_lock\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/protect_application_subtitle\"\n\t\tandroid:textAppearance=\"?textAppearanceSubtitle1\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\" />\n\n\t<com.google.android.material.textfield.TextInputLayout\n\t\tandroid:id=\"@+id/layout_password\"\n\t\tstyle=\"?textInputOutlinedStyle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"30dp\"\n\t\tapp:errorIconDrawable=\"@null\"\n\t\tapp:helperText=\"@string/password_length_hint\"\n\t\tapp:hintEnabled=\"false\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_subtitle\">\n\n\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\tandroid:id=\"@+id/edit_password\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:gravity=\"center_horizontal\"\n\t\t\tandroid:imeOptions=\"actionDone\"\n\t\t\tandroid:importantForAutofill=\"no\"\n\t\t\tandroid:inputType=\"textPassword|textNoSuggestions\"\n\t\t\tandroid:maxLength=\"24\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAlignment=\"center\"\n\t\t\tandroid:textSize=\"16sp\"\n\t\t\ttools:text=\"1234\" />\n\n\t</com.google.android.material.textfield.TextInputLayout>\n\n\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\tandroid:id=\"@+id/switch_biometric\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"16dp\"\n\t\tandroid:checked=\"true\"\n\t\tandroid:text=\"@string/use_fingerprint\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/layout_password\"\n\t\ttools:visibility=\"visible\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_cancel\"\n\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@android:string/cancel\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_next\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:enabled=\"false\"\n\t\tandroid:text=\"@string/next\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_sources_catalog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\ttools:context=\".settings.sources.catalog.SourcesCatalogActivity\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"true\"\n\t\tapp:liftOnScroll=\"false\"\n\t\tapp:liftOnScrollColor=\"@null\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways|snap\" />\n\n\t\t<HorizontalScrollView\n\t\t\tandroid:id=\"@+id/scrollView_chips\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:paddingHorizontal=\"@dimen/list_spacing_large\"\n\t\t\tandroid:scrollbars=\"none\">\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\t\t\tandroid:id=\"@+id/chips_filter\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:clipChildren=\"false\"\n\t\t\t\tandroid:clipToPadding=\"false\"\n\t\t\t\tandroid:paddingVertical=\"@dimen/margin_small\"\n\t\t\t\tapp:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\"\n\t\t\t\tapp:selectionRequired=\"false\"\n\t\t\t\tapp:singleLine=\"true\"\n\t\t\t\tapp:singleSelection=\"false\" />\n\n\t\t</HorizontalScrollView>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tapp:bubbleSize=\"normal\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_stats.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\tapp:layout_scrollFlags=\"noScroll\">\n\n\t\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:hideAnimationBehavior=\"outward\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/appbar\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\"\n\t\tapp:showAnimationBehavior=\"inward\"\n\t\tapp:trackCornerRadius=\"0dp\"\n\t\ttools:visibility=\"visible\" />\n\n\t<HorizontalScrollView\n\t\tandroid:id=\"@+id/scrollView_chips\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:paddingHorizontal=\"12dp\"\n\t\tandroid:scrollbars=\"none\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\">\n\n\t\t<com.google.android.material.chip.ChipGroup\n\t\t\tandroid:id=\"@+id/layout_chips\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t\t<com.google.android.material.chip.Chip\n\t\t\t\tandroid:id=\"@+id/chip_period\"\n\t\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Dropdown\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:text=\"@string/week\"\n\t\t\t\tapp:chipIcon=\"@drawable/ic_history\" />\n\n\t\t</com.google.android.material.chip.ChipGroup>\n\n\t</HorizontalScrollView>\n\n\t<org.koitharu.kotatsu.stats.ui.views.PieChartView\n\t\tandroid:id=\"@+id/chart\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_margin=\"24dp\"\n\t\tapp:layout_constraintDimensionRatio=\"1\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/scrollView_chips\" />\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginTop=\"24dp\"\n\t\tandroid:overScrollMode=\"ifContentScrolls\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/chart\"\n\t\ttools:itemCount=\"4\"\n\t\ttools:listitem=\"@layout/item_stats\" />\n\n\t<ViewStub\n\t\tandroid:id=\"@+id/stub_empty\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout=\"@layout/item_empty_state\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/scrollView_chips\"\n\t\ttools:visibility=\"visible\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_sync_auth.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"24dp\"\n\t\tandroid:drawablePadding=\"@dimen/screen_padding\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:text=\"@string/sync_title\"\n\t\tandroid:textAppearance=\"?textAppearanceHeadline5\"\n\t\tapp:drawableTint=\"?colorPrimary\"\n\t\tapp:drawableTopCompat=\"@drawable/ic_sync\" />\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"@dimen/screen_padding\"\n\t\tandroid:max=\"100\"\n\t\tandroid:visibility=\"invisible\" />\n\n\t<ScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_weight=\"1\">\n\n\t\t<LinearLayout\n\t\t\tandroid:id=\"@+id/layout_content\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:paddingHorizontal=\"@dimen/screen_padding\"\n\t\t\tandroid:paddingBottom=\"@dimen/screen_padding\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:text=\"@string/sync_auth_hint\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceBodySmall\" />\n\n\t\t\t<com.google.android.material.textfield.TextInputLayout\n\t\t\t\tandroid:id=\"@+id/layout_email\"\n\t\t\t\tstyle=\"?textInputOutlinedStyle\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"30dp\"\n\t\t\t\tapp:errorIconDrawable=\"@null\">\n\n\t\t\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\t\t\tandroid:id=\"@+id/edit_email\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:autofillHints=\"username\"\n\t\t\t\t\tandroid:hint=\"@string/email\"\n\t\t\t\t\tandroid:imeOptions=\"actionDone\"\n\t\t\t\t\tandroid:inputType=\"textEmailAddress\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:textSize=\"16sp\" />\n\n\t\t\t</com.google.android.material.textfield.TextInputLayout>\n\n\t\t\t<com.google.android.material.textfield.TextInputLayout\n\t\t\t\tandroid:id=\"@+id/layout_password\"\n\t\t\t\tstyle=\"?textInputOutlinedStyle\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"30dp\"\n\t\t\t\tapp:endIconMode=\"password_toggle\"\n\t\t\t\tapp:errorIconDrawable=\"@null\">\n\n\t\t\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\t\t\tandroid:id=\"@+id/edit_password\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:autofillHints=\"password\"\n\t\t\t\t\tandroid:hint=\"@string/password\"\n\t\t\t\t\tandroid:imeOptions=\"actionDone\"\n\t\t\t\t\tandroid:inputType=\"textPassword\"\n\t\t\t\t\tandroid:maxLength=\"24\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:textSize=\"16sp\" />\n\n\t\t\t</com.google.android.material.textfield.TextInputLayout>\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_settings\"\n\t\t\t\tstyle=\"?borderlessButtonStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\tandroid:text=\"@string/settings\" />\n\n\t\t</LinearLayout>\n\n\t</ScrollView>\n\n\t<com.google.android.material.dockedtoolbar.DockedToolbarLayout\n\t\tandroid:id=\"@+id/docked_toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<FrameLayout\n\t\t\tandroid:id=\"@+id/docked_toolbar_child\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"@dimen/m3_comp_toolbar_docked_container_height\">\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_cancel\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|start\"\n\t\t\t\tandroid:text=\"@android:string/cancel\" />\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_back\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|start\"\n\t\t\t\tandroid:text=\"@string/back\"\n\t\t\t\tandroid:visibility=\"gone\" />\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_next\"\n\t\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|end\"\n\t\t\t\tandroid:enabled=\"false\"\n\t\t\t\tandroid:text=\"@string/next\" />\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|end\"\n\t\t\t\tandroid:enabled=\"false\"\n\t\t\t\tandroid:text=\"@string/done\"\n\t\t\t\tandroid:visibility=\"gone\" />\n\n\t\t</FrameLayout>\n\t</com.google.android.material.dockedtoolbar.DockedToolbarLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_tracker_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"false\">\n\n\t\t<com.google.android.material.appbar.CollapsingToolbarLayout\n\t\t\tandroid:id=\"@+id/collapsingToolbarLayout\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/collapsingToolbarLayoutMediumSize\"\n\t\t\tapp:layout_scrollFlags=\"scroll|exitUntilCollapsed|snap\"\n\t\t\tapp:toolbarId=\"@id/toolbar\">\n\n\t\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\t\tapp:layout_collapseMode=\"pin\" />\n\n\t\t</com.google.android.material.appbar.CollapsingToolbarLayout>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\ttools:listitem=\"@layout/item_track_debug\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_checkbox.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\">\n\n\t<com.google.android.material.checkbox.MaterialCheckBox\n\t\tandroid:id=\"@+id/checkbox\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_directory_select.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/recyclerView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\"\n\tandroid:scrollIndicators=\"top|bottom\"\n\tandroid:scrollbars=\"vertical\"\n\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\ttools:ignore=\"UnusedAttribute\" />\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingBottom=\"?dialogPreferredPadding\">\n\n\t<ScrollView\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:scrollIndicators=\"top|bottom\"\n\t\ttools:ignore=\"UnusedAttribute\">\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_summary\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceBody2\"\n\t\t\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView\n\t\t\t\tandroid:id=\"@+id/option_whole_manga\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:checked=\"true\"\n\t\t\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\t\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\t\t\tapp:icon=\"?android:listChoiceIndicatorSingle\"\n\t\t\t\tapp:title=\"@string/download_option_whole_manga\"\n\t\t\t\ttools:subtitle=\"@string/no_chapters\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView\n\t\t\t\tandroid:id=\"@+id/option_whole_branch\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:button=\"?expandCollapseIndicator\"\n\t\t\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\t\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\tapp:icon=\"?android:listChoiceIndicatorSingle\"\n\t\t\t\ttools:subtitle=\"@string/no_chapters\"\n\t\t\t\ttools:title=\"@string/download_option_all_chapters\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView\n\t\t\t\tandroid:id=\"@+id/option_first_chapters\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:button=\"?expandCollapseIndicator\"\n\t\t\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\t\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\tapp:icon=\"?android:listChoiceIndicatorSingle\"\n\t\t\t\ttools:title=\"@string/download_option_first_n_chapters\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView\n\t\t\t\tandroid:id=\"@+id/option_unread_chapters\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:button=\"?expandCollapseIndicator\"\n\t\t\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\t\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\tapp:icon=\"?android:listChoiceIndicatorSingle\"\n\t\t\t\ttools:title=\"@string/download_option_next_unread_n_chapters\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\t\t\tandroid:id=\"@+id/progressBar\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_tip\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:drawablePadding=\"@dimen/margin_small\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:paddingVertical=\"@dimen/margin_small\"\n\t\t\t\tandroid:text=\"@string/chapter_selection_hint\"\n\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\t\tapp:drawableStartCompat=\"@drawable/ic_tap\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_start\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:checked=\"true\"\n\t\t\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\t\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/start_download\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceListItem\"\n\t\t\t\tandroid:textColor=\"?colorOnSurfaceVariant\" />\n\n\t\t\t<CheckedTextView\n\t\t\t\tandroid:id=\"@+id/textView_more\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:background=\"@drawable/list_selector\"\n\t\t\t\tandroid:checked=\"false\"\n\t\t\t\tandroid:drawableEnd=\"?expandCollapseIndicator\"\n\t\t\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\t\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/more_options\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceListItem\"\n\t\t\t\tandroid:textColor=\"?colorOnSurfaceVariant\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_destination\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/destination_directory\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<com.google.android.material.card.MaterialCardView\n\t\t\t\tandroid:id=\"@+id/card_destination\"\n\t\t\t\tstyle=\"?materialCardViewOutlinedStyle\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"16dp\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\">\n\n\t\t\t\t<Spinner\n\t\t\t\t\tandroid:id=\"@+id/spinner_destination\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:minHeight=\"@dimen/spinner_height\"\n\t\t\t\t\tandroid:paddingHorizontal=\"8dp\" />\n\n\t\t\t</com.google.android.material.card.MaterialCardView>\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_format\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/preferred_download_format\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<com.google.android.material.card.MaterialCardView\n\t\t\t\tandroid:id=\"@+id/card_format\"\n\t\t\t\tstyle=\"?materialCardViewOutlinedStyle\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"16dp\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\">\n\n\t\t\t\t<Spinner\n\t\t\t\t\tandroid:id=\"@+id/spinner_format\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:entries=\"@array/download_formats\"\n\t\t\t\t\tandroid:minHeight=\"@dimen/spinner_height\"\n\t\t\t\t\tandroid:paddingHorizontal=\"8dp\" />\n\n\t\t\t</com.google.android.material.card.MaterialCardView>\n\n\t\t</LinearLayout>\n\n\t</ScrollView>\n\n\t<LinearLayout\n\t\tstyle=\"?buttonBarStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"?dialogPreferredPadding\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:gravity=\"end\"\n\t\tandroid:orientation=\"horizontal\">\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_cancel\"\n\t\t\tstyle=\"?buttonBarButtonStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@android:string/cancel\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_confirm\"\n\t\t\tstyle=\"?buttonBarButtonStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@string/save\" />\n\n\t</LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_error_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:paddingHorizontal=\"?attr/dialogPreferredPadding\"\n\t\tandroid:paddingTop=\"@dimen/margin_normal\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_summary\"\n\t\t\tstyle=\"?textAppearanceBodyMedium\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\ttools:text=\"@tools:sample/lorem[14]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_browser\"\n\t\t\tstyle=\"?textAppearanceBodySmall\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\tandroid:text=\"@string/error_disclaimer_manga\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_browser\"\n\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"center_horizontal\"\n\t\t\tandroid:layout_marginBottom=\"2dp\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:text=\"@string/open_in_browser\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\tstyle=\"?textAppearanceBodySmall\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\ttools:text=\"@string/error_disclaimer_report\" />\n\n\t</LinearLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_favorite.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:paddingTop=\"@dimen/margin_normal\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverStackView\n\t\tandroid:id=\"@+id/coversStack\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"@dimen/category_covers_height\"\n\t\tandroid:layout_marginStart=\"@dimen/screen_padding\"\n\t\tapp:coverSize=\"3.4dp\"\n\t\tapp:hideEmptyViews=\"true\"\n\t\tapp:layout_constraintDimensionRatio=\"13:18\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:gravity=\"center_vertical|start\"\n\t\tandroid:maxLines=\"3\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/coversStack\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/coversStack\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\ttools:text=\"@tools:sample/lorem[22]\" />\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView_categories\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"4dp\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:paddingBottom=\"@dimen/list_spacing\"\n\t\tandroid:scrollIndicators=\"top\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_constrainedHeight=\"true\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/coversStack\"\n\t\ttools:ignore=\"UnusedAttribute\"\n\t\ttools:listitem=\"@layout/item_category_checkable\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_import.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingHorizontal=\"@dimen/grid_spacing_outer\"\n\tandroid:paddingTop=\"@dimen/margin_normal\">\n\n\t<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView\n\t\tandroid:id=\"@+id/button_file\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tapp:icon=\"@drawable/ic_file_zip\"\n\t\tapp:subtitle=\"@string/comics_archive_import_description\"\n\t\tapp:title=\"@string/comics_archive\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView\n\t\tandroid:id=\"@+id/button_dir\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tapp:icon=\"@drawable/ic_folder_file\"\n\t\tapp:subtitle=\"@string/folder_with_images_import_description\"\n\t\tapp:title=\"@string/folder_with_images\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_local_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingHorizontal=\"?dialogPreferredPadding\"\n\tandroid:paddingTop=\"?dialogPreferredPadding\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_path_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/location\"\n\t\tandroid:textAppearance=\"?textAppearanceLabelMedium\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\tandroid:id=\"@+id/textView_path\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"4dp\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tandroid:textIsSelectable=\"true\"\n\t\ttools:text=\"/storage/emulated/0/Manga/lorem.cbz\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView\n\t\tandroid:id=\"@+id/barView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"18dp\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:background=\"?colorSecondaryContainer\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/label_used\"\n\t\tstyle=\"@style/Widget.Kotatsu.TextView.Indicator\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:text=\"@string/this_manga\"\n\t\tapp:drawableStartCompat=\"@drawable/bg_rounded_square\"\n\t\ttools:drawableTint=\"?colorPrimary\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/label_available\"\n\t\tstyle=\"@style/Widget.Kotatsu.TextView.Indicator\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:text=\"@string/available\"\n\t\tapp:drawableStartCompat=\"@drawable/bg_rounded_square\"\n\t\tapp:drawableTint=\"?colorSecondaryContainer\" />\n\n\t<com.google.android.material.chip.Chip\n\t\tandroid:id=\"@+id/chip_cleanup\"\n\t\tstyle=\"@style/Widget.Kotatsu.Chip.Assist\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:text=\"@string/delete_read_chapters\"\n\t\tapp:chipIcon=\"@drawable/ic_delete\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_progress.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"?attr/dialogPreferredPadding\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tandroid:textColor=\"?android:textColorPrimary\"\n\t\ttools:text=\"Title\" />\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"6dp\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:max=\"100\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"4dp\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\tandroid:textColor=\"?android:textColorSecondary\"\n\t\ttools:text=\"Subtitle\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_restore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingVertical=\"?dialogPreferredPadding\">\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"?dialogPreferredPadding\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:max=\"100\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:scrollIndicators=\"top|bottom\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\ttools:itemCount=\"6\"\n\t\ttools:listitem=\"@layout/item_checkable_multiple\"\n\t\ttools:visibility=\"visible\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"?dialogPreferredPadding\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceLabelMedium\"\n\t\tandroid:textColor=\"?android:textColorSecondary\"\n\t\ttools:text=\"@tools:sample/lorem[10]\" />\n\n\t<LinearLayout\n\t\tstyle=\"?buttonBarStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"?dialogPreferredPadding\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:gravity=\"end\"\n\t\tandroid:orientation=\"horizontal\">\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_cancel\"\n\t\t\tstyle=\"?buttonBarButtonStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@android:string/cancel\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_restore\"\n\t\t\tstyle=\"?buttonBarButtonStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@string/restore\" />\n\n\t</LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_two_buttons.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"?dialogPreferredPadding\">\n\n\t<ImageView\n\t\tandroid:id=\"@android:id/icon\"\n\t\tandroid:layout_width=\"32dp\"\n\t\tandroid:layout_height=\"32dp\"\n\t\tandroid:layout_gravity=\"center_horizontal\"\n\t\tandroid:contentDescription=\"@null\"\n\t\tapp:tint=\"?colorPrimary\"\n\t\ttools:src=\"@drawable/ic_notification\" />\n\n\t<TextView\n\t\tandroid:id=\"@android:id/title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginVertical=\"18dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:textAlignment=\"center\"\n\t\tandroid:textAppearance=\"?textAppearanceLabelLarge\"\n\t\tandroid:textSize=\"18sp\"\n\t\ttools:text=\"@string/suggestions_summary\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@android:id/button1\"\n\t\tstyle=\"?materialButtonTonalStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:minHeight=\"62dp\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerMedium\"\n\t\ttools:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Material3.Corner.Top\"\n\t\ttools:text=\"Enable\"\n\t\ttools:visibility=\"visible\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@android:id/button2\"\n\t\tstyle=\"?materialButtonTonalStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:minHeight=\"62dp\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerMedium\"\n\t\ttools:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Material3.Corner.None\"\n\t\ttools:text=\"Ask every time\"\n\t\ttools:visibility=\"visible\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@android:id/button3\"\n\t\tstyle=\"?materialButtonTonalStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:minHeight=\"62dp\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerMedium\"\n\t\ttools:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Material3.Corner.Bottom\"\n\t\ttools:text=\"No thanks\"\n\t\ttools:visibility=\"visible\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fast_scroller.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2022 Randy Webster. All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:clipChildren=\"false\"\n\ttools:layout_gravity=\"end\"\n\ttools:layout_height=\"match_parent\"\n\ttools:layout_width=\"wrap_content\"\n\ttools:orientation=\"horizontal\"\n\ttools:parentTag=\"android.widget.LinearLayout\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/bubble\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"end\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:visibility=\"invisible\"\n\t\ttools:background=\"@drawable/fastscroll_bubble\"\n\t\ttools:backgroundTint=\"@color/blue_primary\"\n\t\ttools:text=\"A\"\n\t\ttools:textColor=\"#ffffff\"\n\t\ttools:visibility=\"visible\" />\n\n\t<FrameLayout\n\t\tandroid:id=\"@+id/scrollbar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:paddingStart=\"@dimen/fastscroll_scrollbar_padding_start\"\n\t\tandroid:paddingEnd=\"0dp\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\">\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/track\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:layout_gravity=\"center_horizontal\"\n\t\t\ttools:ignore=\"ContentDescription\"\n\t\t\ttools:src=\"@drawable/fastscroll_track\"\n\t\t\ttools:tint=\"@color/kotatsu_outline\" />\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/thumb\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"center_horizontal\"\n\t\t\ttools:ignore=\"ContentDescription\"\n\t\t\ttools:src=\"@drawable/fastscroll_handle\"\n\t\t\ttools:tint=\"@color/kotatsu_primary\" />\n\n\t</FrameLayout>\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_changelog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<androidx.core.widget.NestedScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\tandroid:id=\"@+id/textView_content\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:padding=\"@dimen/screen_padding\"\n\t\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t</androidx.core.widget.NestedScrollView>\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout_gravity=\"top\"\n\t\tandroid:indeterminate=\"true\"\n\t\tapp:hideAnimationBehavior=\"escape\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_chapters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<HorizontalScrollView\n\t\tandroid:id=\"@+id/scrollView_filter\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_alignParentStart=\"true\"\n\t\tandroid:layout_alignParentEnd=\"true\"\n\t\tandroid:clipChildren=\"false\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:paddingHorizontal=\"@dimen/list_spacing\"\n\t\tandroid:scrollbars=\"none\">\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\t\tandroid:id=\"@+id/chips_filter\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:clipChildren=\"false\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:paddingTop=\"2dp\"\n\t\t\tandroid:paddingBottom=\"6dp\"\n\t\t\tapp:chipIconVisible=\"false\"\n\t\t\tapp:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\"\n\t\t\tapp:singleLine=\"true\" />\n\n\t</HorizontalScrollView>\n\n\t<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\t\tandroid:id=\"@+id/recyclerView_chapters\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_alignWithParentIfMissing=\"true\"\n\t\tandroid:layout_below=\"@id/scrollView_filter\"\n\t\tandroid:layout_alignParentStart=\"true\"\n\t\tandroid:layout_alignParentEnd=\"true\"\n\t\tandroid:layout_alignParentBottom=\"true\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:scrollIndicators=\"top\"\n\t\tapp:bubbleSize=\"small\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\ttools:listitem=\"@layout/item_chapter\" />\n\n\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_centerInParent=\"true\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_holder\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_centerInParent=\"true\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginBottom=\"@dimen/margin_normal\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:text=\"@string/chapters_empty\"\n\t\tandroid:textAlignment=\"center\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_explore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/recyclerView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:padding=\"@dimen/list_spacing_normal\"\n\tandroid:orientation=\"vertical\"\n\tandroid:scrollbars=\"vertical\"\n\ttools:listitem=\"@layout/item_explore_source_list\" />\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_favourites_container.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.tabs.TabLayout\n\t\tandroid:id=\"@+id/tabs\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"@null\"\n\t\tapp:tabGravity=\"start\"\n\t\tapp:tabMode=\"scrollable\" />\n\n\t<androidx.viewpager2.widget.ViewPager2\n\t\tandroid:id=\"@+id/pager\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\ttools:visibility=\"gone\" />\n\n\t<ViewStub\n\t\tandroid:id=\"@+id/stub_empty\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout=\"@layout/item_empty_state\"\n\t\ttools:visibility=\"visible\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_filter_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<HorizontalScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:id=\"@+id/scrollView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:paddingHorizontal=\"12dp\"\n\tandroid:scrollbars=\"none\">\n\n\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\tandroid:id=\"@+id/chips_tags\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipChildren=\"false\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:paddingVertical=\"@dimen/margin_small\"\n\t\tapp:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\"\n\t\tapp:selectionRequired=\"false\"\n\t\tapp:singleLine=\"true\"\n\t\tapp:singleSelection=\"false\" />\n\n</HorizontalScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n\t\tandroid:id=\"@+id/swipeRefreshLayout\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\t\t\tandroid:id=\"@+id/recyclerView\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:clipChildren=\"false\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\t\ttools:layoutManager=\"org.koitharu.kotatsu.core.ui.list.FitHeightLinearLayoutManager\"\n\t\t\ttools:listitem=\"@layout/item_manga_list\" />\n\n\t</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_list_simple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/swipeRefreshLayout\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipChildren=\"false\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\ttools:listitem=\"@layout/item_feed\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_manga_bookmarks.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/recyclerView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:orientation=\"vertical\"\n\tandroid:scrollIndicators=\"top\"\n\tapp:bubbleSize=\"small\"\n\tapp:layoutManager=\"androidx.recyclerview.widget.GridLayoutManager\"\n\tapp:spanCount=\"3\"\n\ttools:listitem=\"@layout/item_bookmark_large\" />\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_pages.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:scrollIndicators=\"top\"\n\t\tapp:bubbleSize=\"small\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.GridLayoutManager\"\n\t\ttools:listitem=\"@layout/item_page_thumb\"\n\t\ttools:spanCount=\"3\" />\n\n\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_holder\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginBottom=\"@dimen/margin_normal\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:text=\"@string/chapters_empty\"\n\t\tandroid:textAlignment=\"center\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar_top\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"top\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:hideAnimationBehavior=\"outward\"\n\t\tapp:showAnimationBehavior=\"inward\"\n\t\tapp:trackCornerRadius=\"0dp\"\n\t\ttools:visibility=\"visible\" />\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar_bottom\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:hideAnimationBehavior=\"inward\"\n\t\tapp:showAnimationBehavior=\"outward\"\n\t\tapp:trackCornerRadius=\"0dp\"\n\t\ttools:visibility=\"visible\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/scrollView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\ttools:background=\"?colorBackgroundFloating\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:paddingBottom=\"?actionBarSize\">\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\tandroid:background=\"?colorSecondaryContainer\"\n\t\t\tandroid:clipToOutline=\"true\"\n\t\t\tandroid:foreground=\"?selectableItemBackground\"\n\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\tapp:layout_constraintDimensionRatio=\"H,13:18\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:layout_constraintWidth_percent=\"0.3\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\t\tapp:useExistingDrawable=\"true\"\n\t\t\ttools:background=\"@tools:sample/backgrounds/scenic[5]\"\n\t\t\ttools:ignore=\"ContentDescription,UnusedAttribute\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:maxLines=\"5\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceHeadlineSmall\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_open\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:layout_goneMarginEnd=\"16dp\"\n\t\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t\t<ImageButton\n\t\t\tandroid:id=\"@+id/button_close\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\tandroid:layout_marginEnd=\"4dp\"\n\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\tandroid:contentDescription=\"@string/close\"\n\t\t\tandroid:padding=\"12dp\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:srcCompat=\"?actionModeCloseDrawable\"\n\t\t\tapp:tint=\"?colorControlNormal\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:maxLines=\"3\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\t\ttools:text=\"@tools:sample/lorem[12]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_author\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\tandroid:background=\"@drawable/custom_selectable_item_background\"\n\t\t\tandroid:padding=\"2dp\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textColor=\"?attr/colorTertiary\"\n\t\t\tandroid:textStyle=\"bold\"\n\t\t\tapp:layout_constrainedWidth=\"true\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_subtitle\"\n\t\t\ttools:text=\"@tools:sample/full_names\" />\n\n\t\t<RatingBar\n\t\t\tandroid:id=\"@+id/rating_bar\"\n\t\t\tstyle=\"?ratingBarStyleSmall\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\tandroid:isIndicator=\"true\"\n\t\t\tandroid:max=\"1\"\n\t\t\tandroid:numStars=\"5\"\n\t\t\tandroid:stepSize=\"0.5\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintHorizontal_bias=\"0.0\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_author\"\n\t\t\ttools:rating=\"4\" />\n\n\t\t<androidx.constraintlayout.widget.Barrier\n\t\t\tandroid:id=\"@+id/barrier_header\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:barrierDirection=\"bottom\"\n\t\t\tapp:barrierMargin=\"8dp\"\n\t\t\tapp:constraint_referenced_ids=\"imageView_cover,rating_bar\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_read\"\n\t\t\tstyle=\"?materialButtonStyle\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_open\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/barrier_header\"\n\t\t\ttools:text=\"@string/read\" />\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_open\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\tandroid:contentDescription=\"@string/details\"\n\t\t\tapp:icon=\"@drawable/ic_expand\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/button_read\"\n\t\t\tapp:layout_constraintDimensionRatio=\"1\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/button_read\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\t\tandroid:id=\"@+id/chips_tags\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\tapp:chipSpacingHorizontal=\"6dp\"\n\t\t\tapp:chipSpacingVertical=\"6dp\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@+id/button_read\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\t\tandroid:lineSpacingMultiplier=\"1.2\"\n\t\t\tandroid:paddingBottom=\"@dimen/margin_normal\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/chips_tags\"\n\t\t\ttools:ignore=\"UnusedAttribute\"\n\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n</ScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_reader_double.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:id=\"@+id/recyclerView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:defaultFocusHighlightEnabled=\"false\"\n\tandroid:orientation=\"horizontal\"\n\tapp:layoutManager=\"org.koitharu.kotatsu.reader.ui.pager.doublepage.DoublePageLayoutManager\" />\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_reader_pager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.viewpager2.widget.ViewPager2\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:id=\"@+id/pager\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:defaultFocusHighlightEnabled=\"false\" />\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_reader_webtoon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonScalingFrame\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/frame\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:defaultFocusHighlightEnabled=\"false\"\n\tandroid:focusable=\"true\">\n\n\t<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonRecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:defaultFocusHighlightEnabled=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tapp:layoutManager=\"org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonLayoutManager\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/feedbackTop\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"top|center_horizontal\"\n\t\tandroid:layout_margin=\"@dimen/screen_padding\"\n\t\tandroid:alpha=\"0\"\n\t\tandroid:background=\"@drawable/bg_reader_indicator\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\tandroid:paddingVertical=\"@dimen/margin_small\"\n\t\tandroid:text=\"@string/pull_to_prev_chapter\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyLarge\"\n\t\tandroid:theme=\"@style/ThemeOverlay.Material3.Dark\"\n\t\ttools:alpha=\"1\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/feedbackBottom\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom|center_horizontal\"\n\t\tandroid:layout_margin=\"@dimen/screen_padding\"\n\t\tandroid:alpha=\"0\"\n\t\tandroid:background=\"@drawable/bg_reader_indicator\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\tandroid:paddingVertical=\"@dimen/margin_small\"\n\t\tandroid:text=\"@string/pull_to_next_chapter\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyLarge\"\n\t\tandroid:theme=\"@style/ThemeOverlay.Material3.Dark\"\n\t\ttools:alpha=\"1\" />\n\n</org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonScalingFrame>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_search_suggestion.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:background=\"@drawable/bg_search_suggestion\"\n\tandroid:clickable=\"true\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:orientation=\"vertical\"\n\tandroid:scrollbars=\"vertical\"\n\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\ttools:ignore=\"KeyboardInaccessibleWidget\" />\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_settings_sources.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/recyclerView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:background=\"?android:colorBackground\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:orientation=\"vertical\"\n\tandroid:scrollbars=\"vertical\"\n\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\ttools:listitem=\"@layout/item_source_config\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_bookmark_large.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"?materialCardViewOutlinedStyle\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tapp:cardBackgroundColor=\"?attr/colorSurfaceContainerHighest\"\n\ttools:layout_width=\"140dp\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_thumb\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\ttools:ignore=\"ContentDescription\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic[5]\" />\n\n\t<org.koitharu.kotatsu.history.ui.util.ReadingProgressView\n\t\tandroid:id=\"@+id/progressView\"\n\t\tandroid:layout_width=\"@dimen/card_indicator_size\"\n\t\tandroid:layout_height=\"@dimen/card_indicator_size\"\n\t\tandroid:layout_gravity=\"bottom|end\"\n\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\" />\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_button_footer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:gravity=\"center\"\n\tandroid:padding=\"4dp\">\n\n\t<Button\n\t\tandroid:id=\"@+id/button\"\n\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\ttools:text=\"@string/globally\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_categories_all.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:paddingVertical=\"4dp\"\n\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\ttools:ignore=\"RtlSymmetry\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverStackView\n\t\tandroid:id=\"@+id/coversView\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"@dimen/category_covers_height\"\n\t\tapp:coverSize=\"3.4dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintDimensionRatio=\"13:18\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"?listPreferredItemPaddingEnd\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/all_favourites\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tapp:layout_constraintBottom_toTopOf=\"@id/textView_subtitle\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_visible\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/coversView\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:layout_constraintVertical_chainStyle=\"packed\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginTop=\"4dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_visible\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/coversView\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\tapp:layout_constraintVertical_chainStyle=\"packed\"\n\t\ttools:text=\"@tools:sample/lorem[1]\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/imageView_visible\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/show_all\"\n\t\tandroid:padding=\"@dimen/margin_normal\"\n\t\tandroid:src=\"@drawable/ic_eye\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_category.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:paddingVertical=\"4dp\"\n\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\ttools:ignore=\"RtlSymmetry\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverStackView\n\t\tandroid:id=\"@+id/coversView\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"@dimen/category_covers_height\"\n\t\tapp:coverSize=\"3.4dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintDimensionRatio=\"13:18\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"?listPreferredItemPaddingEnd\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tapp:layout_constraintBottom_toTopOf=\"@id/textView_subtitle\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_edit\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/coversView\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:layout_constraintVertical_chainStyle=\"packed\"\n\t\ttools:text=\"@tools:sample/lorem[1]\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginTop=\"4dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_tracker\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintHorizontal_chainStyle=\"packed\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/coversView\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\tapp:layout_constraintVertical_chainStyle=\"packed\"\n\t\ttools:text=\"@tools:sample/lorem[1]\" />\n\n\t<ImageView\n\t\tandroid:id=\"@+id/imageView_tracker\"\n\t\tandroid:layout_width=\"16dp\"\n\t\tandroid:layout_height=\"16dp\"\n\t\tandroid:layout_marginEnd=\"4dp\"\n\t\tandroid:contentDescription=\"@string/check_for_new_chapters\"\n\t\tandroid:tooltipText=\"@string/check_for_new_chapters\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_subtitle\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_hidden\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/textView_subtitle\"\n\t\tapp:layout_constraintTop_toTopOf=\"@id/textView_subtitle\"\n\t\tapp:srcCompat=\"@drawable/ic_notification\" />\n\n\t<ImageView\n\t\tandroid:id=\"@+id/imageView_hidden\"\n\t\tandroid:layout_width=\"16dp\"\n\t\tandroid:layout_height=\"16dp\"\n\t\tandroid:contentDescription=\"@string/hide_from_main_screen\"\n\t\tandroid:tooltipText=\"@string/hide_from_main_screen\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_subtitle\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/textView_title\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_tracker\"\n\t\tapp:layout_constraintTop_toTopOf=\"@id/textView_subtitle\"\n\t\tapp:srcCompat=\"@drawable/ic_eye_off\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/imageView_edit\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/edit\"\n\t\tandroid:padding=\"@dimen/margin_normal\"\n\t\tandroid:src=\"@drawable/ic_edit\"\n\t\tandroid:tooltipText=\"@string/edit\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_handle\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<ImageView\n\t\tandroid:id=\"@+id/imageView_handle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/reorder\"\n\t\tandroid:padding=\"@dimen/margin_normal\"\n\t\tandroid:pointerIcon=\"grab\"\n\t\tandroid:src=\"@drawable/ic_reorder_handle\"\n\t\tandroid:tooltipText=\"@string/reorder\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_category_checkable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:minHeight=\"?listPreferredItemHeightSmall\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\">\n\n\t<com.google.android.material.checkbox.MaterialCheckBox\n\t\tandroid:id=\"@+id/checkBox\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clickable=\"false\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\ttools:checkedState=\"indeterminate\"\n\t\ttools:text=\"@tools:sample/cities\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_category_checkable_multiple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<CheckedTextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/checkedTextView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"?android:listPreferredItemHeightSmall\"\n\tandroid:background=\"?android:selectableItemBackground\"\n\tandroid:checkMark=\"?android:attr/listChoiceIndicatorMultiple\"\n\tandroid:gravity=\"start|center_vertical\"\n\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\ttools:checked=\"true\"\n\ttools:text=\"@tools:sample/lorem[4]\" />"
  },
  {
    "path": "app/src/main/res/layout/item_category_checkable_single.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<CheckedTextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/checkedTextView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"?android:listPreferredItemHeightSmall\"\n\tandroid:background=\"?android:selectableItemBackground\"\n\tandroid:checkMark=\"?android:attr/listChoiceIndicatorSingle\"\n\tandroid:gravity=\"start|center_vertical\"\n\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\ttools:checked=\"true\"\n\ttools:text=\"@tools:sample/lorem[4]\" />"
  },
  {
    "path": "app/src/main/res/layout/item_chapter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"?attr/listPreferredItemHeight\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:baselineAligned=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"@dimen/chapter_list_item_height\"\n\tandroid:orientation=\"horizontal\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginVertical=\"8dp\"\n\t\tandroid:layout_marginStart=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:layout_marginEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:drawablePadding=\"8dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\t\ttools:text=\"@tools:sample/lorem[15]\"\n\t\t\ttools:textColor=\"?android:textColorPrimary\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\ttools:text=\"05.10.2021 • Scanlator\"\n\t\t\ttools:textColor=\"?android:textColorTertiary\" />\n\t</LinearLayout>\n\n\t<ImageView\n\t\tandroid:id=\"@+id/imageView_bookmarked\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tandroid:contentDescription=\"@string/bookmarks\"\n\t\tapp:srcCompat=\"@drawable/ic_bookmark\" />\n\n\t<ImageView\n\t\tandroid:id=\"@+id/imageView_downloaded\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tandroid:contentDescription=\"@string/downloaded\"\n\t\tapp:srcCompat=\"@drawable/ic_storage\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_chapter_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:baselineAligned=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingVertical=\"4dp\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_number\"\n\t\tandroid:layout_width=\"24dp\"\n\t\tandroid:layout_height=\"24dp\"\n\t\tandroid:layout_marginStart=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:background=\"@drawable/bg_badge_default\"\n\t\tandroid:ellipsize=\"none\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAlignment=\"center\"\n\t\tandroid:textColor=\"?attr/colorOnPrimary\"\n\t\tandroid:textSize=\"12sp\"\n\t\ttools:text=\"13\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:layout_marginEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tandroid:drawablePadding=\"4dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tapp:drawableTint=\"?colorControlNormal\"\n\t\ttools:drawableEnd=\"@drawable/ic_check\"\n\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_chapter_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"?materialCardViewOutlinedStyle\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\ttools:layout_width=\"@dimen/chapter_grid_width\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAlignment=\"center\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleMedium\"\n\t\t\tapp:layout_constraintDimensionRatio=\"1:1\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\ttools:text=\"150\"\n\t\t\ttools:textColor=\"?android:textColorPrimary\" />\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_bookmarked\"\n\t\t\tandroid:layout_width=\"14dp\"\n\t\t\tandroid:layout_height=\"14dp\"\n\t\t\tandroid:layout_marginEnd=\"6dp\"\n\t\t\tandroid:layout_marginBottom=\"6dp\"\n\t\t\tandroid:contentDescription=\"@string/bookmarks\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_title\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/textView_title\"\n\t\t\tapp:srcCompat=\"@drawable/ic_bookmark\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_downloaded\"\n\t\t\tandroid:layout_width=\"14dp\"\n\t\t\tandroid:layout_height=\"14dp\"\n\t\t\tandroid:layout_marginEnd=\"6dp\"\n\t\t\tandroid:layout_marginBottom=\"6dp\"\n\t\t\tandroid:contentDescription=\"@string/downloaded\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_title\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_bookmarked\"\n\t\t\tapp:srcCompat=\"@drawable/ic_save_ok\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_new\"\n\t\t\tandroid:layout_width=\"8dp\"\n\t\t\tandroid:layout_height=\"8dp\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:layout_marginEnd=\"10dp\"\n\t\t\tandroid:contentDescription=\"@string/new_chapters\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/textView_title\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/textView_title\"\n\t\t\tapp:srcCompat=\"@drawable/ic_new\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_current\"\n\t\t\tandroid:layout_width=\"16dp\"\n\t\t\tandroid:layout_height=\"16dp\"\n\t\t\tandroid:layout_marginStart=\"6dp\"\n\t\t\tandroid:layout_marginTop=\"6dp\"\n\t\t\tandroid:contentDescription=\"@string/new_chapters\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"@id/textView_title\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/textView_title\"\n\t\t\tapp:srcCompat=\"@drawable/ic_current_chapter\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_checkable_multiple.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<CheckedTextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"?android:listPreferredItemHeightSmall\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\tapp:drawableStartCompat=\"?android:listChoiceIndicatorMultiple\"\n\ttools:text=\"@tools:sample/full_names\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_checkable_new.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:textAppearance=\"?attr/textAppearanceListItem\"\n\ttools:text=\"@tools:sample/full_names\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_checkable_single.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<CheckedTextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"?android:listPreferredItemHeightSmall\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\tapp:drawableStartCompat=\"?android:listChoiceIndicatorSingle\"\n\ttools:text=\"@tools:sample/full_names\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_color_scheme.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"wrap_content\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"6dp\"\n\ttools:theme=\"@style/ThemeOverlay.Kotatsu.Miku\">\n\n\t<com.google.android.material.card.MaterialCardView\n\t\tandroid:id=\"@+id/card\"\n\t\tstyle=\"?materialCardViewFilledStyle\"\n\t\tandroid:layout_width=\"@dimen/widget_cover_width\"\n\t\tandroid:layout_height=\"@dimen/widget_cover_height\"\n\t\tandroid:focusableInTouchMode=\"false\"\n\t\tapp:strokeColor=\"?colorOutline\">\n\n\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:padding=\"6dp\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\t\tandroid:text=\"Abc\"\n\t\t\t\tandroid:textSize=\"12sp\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\ttools:ignore=\"HardcodedText\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ShapeView\n\t\t\t\tandroid:id=\"@+id/shape_1\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"6dp\"\n\t\t\t\tandroid:layout_marginBottom=\"6dp\"\n\t\t\t\tandroid:background=\"?colorSecondary\"\n\t\t\t\tapp:cornerSize=\"4dp\"\n\t\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/shape_2\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintWidth_percent=\"0.4\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ShapeView\n\t\t\t\tandroid:id=\"@+id/shape_2\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"6dp\"\n\t\t\t\tandroid:background=\"?colorSecondary\"\n\t\t\t\tapp:cornerSize=\"4dp\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\tapp:layout_constraintVertical_bias=\"0.65\"\n\t\t\t\tapp:layout_constraintWidth_percent=\"0.7\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ShapeView\n\t\t\t\tandroid:layout_width=\"16dp\"\n\t\t\t\tandroid:layout_height=\"16dp\"\n\t\t\t\tandroid:background=\"?colorPrimary\"\n\t\t\t\tapp:cornerSize=\"6dp\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_check\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"top|end\"\n\t\t\tandroid:layout_margin=\"6dp\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:srcCompat=\"@drawable/ic_mtrl_checked_circle\"\n\t\t\tapp:tint=\"?colorPrimary\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t</com.google.android.material.card.MaterialCardView>\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:elegantTextHeight=\"false\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:paddingTop=\"4dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tandroid:textColor=\"?android:attr/textColorPrimary\"\n\t\ttools:text=\"@string/theme_name_miku\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"?materialCardViewFilledStyle\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:id=\"@+id/constraintLayout\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:paddingBottom=\"12dp\">\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"64dp\"\n\t\t\tandroid:layout_height=\"64dp\"\n\t\t\tandroid:layout_marginStart=\"12dp\"\n\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Medium\"\n\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_expand\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:layout_goneMarginEnd=\"12dp\"\n\t\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.CheckableImageButton\n\t\t\tandroid:id=\"@+id/button_expand\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\tandroid:contentDescription=\"@string/expand\"\n\t\t\tandroid:minWidth=\"?minTouchTargetSize\"\n\t\t\tandroid:minHeight=\"?minTouchTargetSize\"\n\t\t\tandroid:scaleType=\"center\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:srcCompat=\"?expandCollapseIndicator\"\n\t\t\tapp:tint=\"?colorControlActivated\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<androidx.constraintlayout.widget.Barrier\n\t\t\tandroid:id=\"@+id/barrier_top\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:barrierDirection=\"bottom\"\n\t\t\tapp:constraint_referenced_ids=\"imageView_cover,textView_status,button_expand,textView_details\" />\n\n\t\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\t\tandroid:id=\"@+id/progressBar\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"12dp\"\n\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/barrier_top\"\n\t\t\tapp:trackColor=\"?android:colorBackground\"\n\t\t\ttools:progress=\"25\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.NestedRecyclerView\n\t\t\tandroid:id=\"@+id/recyclerView_chapters\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"12dp\"\n\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:background=\"@drawable/bg_card\"\n\t\t\tandroid:clipToOutline=\"true\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:fadeScrollbars=\"false\"\n\t\t\tandroid:nestedScrollingEnabled=\"false\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:outlineProvider=\"background\"\n\t\t\tandroid:paddingVertical=\"8dp\"\n\t\t\tandroid:scrollbarStyle=\"insideOverlay\"\n\t\t\tandroid:scrollbars=\"vertical\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/progressBar\"\n\t\t\tapp:maxHeight=\"240dp\"\n\t\t\ttools:listitem=\"@layout/item_chapter_download\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_status\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/textView_percent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\t\ttools:text=\"@string/manga_downloading_\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_percent\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:layout_marginBottom=\"12dp\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/barrier_top\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\ttools:text=\"25%\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_details\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:maxLines=\"4\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/textView_percent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_status\"\n\t\t\ttools:text=\"@tools:sample/lorem[10]\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_pause\"\n\t\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@string/pause\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_resume\"\n\t\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@string/resume\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_skip\"\n\t\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@string/skip\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_skip_all\"\n\t\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@string/skip_all\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_cancel\"\n\t\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@android:string/cancel\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<androidx.constraintlayout.helper.widget.Flow\n\t\t\tandroid:id=\"@+id/flow_buttons\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginHorizontal=\"12dp\"\n\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\tapp:constraint_referenced_ids=\"button_pause,button_resume,button_skip,button_skip_all,button_cancel\"\n\t\t\tapp:flow_horizontalAlign=\"end\"\n\t\t\tapp:flow_horizontalBias=\"1\"\n\t\t\tapp:flow_horizontalGap=\"8dp\"\n\t\t\tapp:flow_horizontalStyle=\"packed\"\n\t\t\tapp:flow_wrapMode=\"chain\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/recyclerView_chapters\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_empty_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tapp:contentPadding=\"@dimen/margin_normal\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<org.koitharu.kotatsu.core.image.CoilImageView\n\t\t\tandroid:id=\"@+id/icon\"\n\t\t\tandroid:layout_width=\"120dp\"\n\t\t\tandroid:layout_height=\"120dp\"\n\t\t\tandroid:contentDescription=\"@null\"\n\t\t\tandroid:scaleType=\"fitCenter\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\ttools:src=\"@drawable/ic_empty_favourites\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textPrimary\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleMedium\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/icon\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textSecondary\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/icon\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textPrimary\"\n\t\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_retry\"\n\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textSecondary\"\n\t\t\ttools:text=\"@string/try_again\"\n\t\t\ttools:visibility=\"visible\" />\n\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_empty_hint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:padding=\"@dimen/margin_normal\">\n\n\t<org.koitharu.kotatsu.core.image.CoilImageView\n\t\tandroid:id=\"@+id/icon\"\n\t\tandroid:layout_width=\"120dp\"\n\t\tandroid:layout_height=\"120dp\"\n\t\tandroid:contentDescription=\"@null\"\n\t\tandroid:scaleType=\"fitCenter\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\ttools:src=\"@drawable/ic_empty_favourites\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textPrimary\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleMedium\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/icon\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textSecondary\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/icon\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textPrimary\"\n\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n\t<Button\n\t\tandroid:id=\"@+id/button_retry\"\n\t\tstyle=\"?materialButtonTonalStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textSecondary\"\n\t\ttools:text=\"@string/try_again\"\n\t\ttools:visibility=\"visible\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_empty_state.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:gravity=\"center\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingHorizontal=\"32dp\"\n\tandroid:paddingBottom=\"?actionBarSize\">\n\n\t<org.koitharu.kotatsu.core.image.CoilImageView\n\t\tandroid:id=\"@+id/icon\"\n\t\tandroid:layout_width=\"192dp\"\n\t\tandroid:layout_height=\"192dp\"\n\t\tandroid:contentDescription=\"@null\"\n\t\tandroid:scaleType=\"fitCenter\"\n\t\tapp:crossfadeEnabled=\"false\"\n\t\ttools:src=\"@drawable/ic_empty_favourites\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textPrimary\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleLarge\"\n\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textSecondary\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n\t<Button\n\t\tandroid:id=\"@+id/button_retry\"\n\t\tstyle=\"?materialButtonTonalStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:text=\"@string/try_again\"\n\t\ttools:visibility=\"visible\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_error_footer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:gravity=\"center\"\n\tandroid:minHeight=\"@dimen/list_footer_height_outer\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"4dp\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\ttools:text=\"@string/too_many_requests_message\" />\n\n\t<TextView\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"2dp\"\n\t\tandroid:text=\"@string/tap_to_try_again\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_error_state.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:gravity=\"center\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"16dp\"\n\tandroid:paddingBottom=\"?actionBarSize\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_error\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:drawablePadding=\"12dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:textAlignment=\"center\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\tapp:drawableTopCompat=\"@drawable/ic_error_large\"\n\t\ttools:text=\"@tools:sample/lorem[6]\" />\n\n\t<Button\n\t\tandroid:id=\"@+id/button_retry\"\n\t\tstyle=\"?materialButtonTonalStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"16dp\"\n\t\tandroid:text=\"@string/try_again\" />\n\n\t<Button\n\t\tandroid:id=\"@+id/button_secondary\"\n\t\tstyle=\"?borderlessButtonStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:text=\"@string/open_in_browser\"\n\t\ttools:visibility=\"visible\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_explore_buttons.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\">\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_local\"\n\t\tstyle=\"@style/Widget.Kotatsu.ExploreButton\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@string/local_storage\"\n\t\tapp:icon=\"@drawable/ic_storage\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_bookmarks\"\n\t\tstyle=\"@style/Widget.Kotatsu.ExploreButton\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@string/bookmarks\"\n\t\tapp:icon=\"@drawable/ic_bookmark\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_random\"\n\t\tstyle=\"@style/Widget.Kotatsu.ExploreButton\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@string/random\"\n\t\tapp:icon=\"@drawable/ic_dice\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_downloads\"\n\t\tstyle=\"@style/Widget.Kotatsu.ExploreButton\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@string/downloads\"\n\t\tapp:icon=\"@drawable/ic_download\" />\n\n\t<androidx.constraintlayout.helper.widget.Flow\n\t\tandroid:id=\"@+id/flow\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:constraint_referenced_ids=\"button_local,button_bookmarks,button_random,button_downloads\"\n\t\tapp:flow_horizontalGap=\"12dp\"\n\t\tapp:flow_maxElementsWrap=\"@integer/explore_buttons_columns\"\n\t\tapp:flow_verticalGap=\"8dp\"\n\t\tapp:flow_wrapMode=\"aligned\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_explore_source_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:clipChildren=\"false\"\n\tandroid:gravity=\"center_horizontal\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"@dimen/list_spacing\"\n\ttools:layout_width=\"120dp\">\n\n\t<org.koitharu.kotatsu.core.ui.image.FaviconView\n\t\tandroid:id=\"@+id/imageView_icon\"\n\t\tandroid:layout_width=\"72dp\"\n\t\tandroid:layout_height=\"72dp\"\n\t\tandroid:background=\"?colorControlHighlight\"\n\t\tandroid:labelFor=\"@id/textView_title\"\n\t\tandroid:padding=\"1dp\"\n\t\tandroid:scaleType=\"fitCenter\"\n\t\tapp:iconStyle=\"@style/FaviconDrawable.Large\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerMedium\"\n\t\tapp:strokeColor=\"?colorOutline\"\n\t\tapp:strokeWidth=\"1dp\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/list_spacing\"\n\t\tandroid:drawablePadding=\"1dp\"\n\t\tandroid:elegantTextHeight=\"false\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAlignment=\"center\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\ttools:drawableStart=\"@drawable/ic_pin_small\"\n\t\ttools:text=\"@tools:sample/lorem[0]\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_explore_source_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:baselineAligned=\"false\"\n\tandroid:clipChildren=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:orientation=\"horizontal\">\n\n\t<org.koitharu.kotatsu.core.ui.image.FaviconView\n\t\tandroid:id=\"@+id/imageView_icon\"\n\t\tandroid:layout_width=\"40dp\"\n\t\tandroid:layout_height=\"40dp\"\n\t\tandroid:layout_marginStart=\"8dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:layout_marginBottom=\"8dp\"\n\t\tandroid:background=\"?colorSurfaceContainer\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:iconStyle=\"@style/FaviconDrawable.Small\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"@dimen/margin_small\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:drawablePadding=\"2dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:drawableStart=\"@drawable/ic_pin_small\"\n\t\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t</LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_feed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"72dp\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:clipChildren=\"false\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover\"\n\t\tandroid:layout_width=\"40dp\"\n\t\tandroid:layout_height=\"40dp\"\n\t\tandroid:layout_marginStart=\"8dp\"\n\t\tandroid:layout_marginTop=\"16dp\"\n\t\tandroid:layout_marginBottom=\"16dp\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Medium\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:maxLines=\"1\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tapp:layout_constraintBottom_toTopOf=\"@+id/textView_summary\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toTopOf=\"@+id/imageView_cover\"\n\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_summary\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:drawablePadding=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:maxLines=\"1\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_title\"\n\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:baselineAligned=\"true\"\n\tandroid:orientation=\"horizontal\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:gravity=\"center_vertical|start\"\n\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_more\"\n\t\tstyle=\"@style/Widget.Kotatsu.Button.More.Small\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:visibility=\"invisible\"\n\t\ttools:text=\"@string/manage\"\n\t\ttools:visibility=\"visible\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:paddingTop=\"6dp\">\n\n\t<View\n\t\tandroid:id=\"@+id/view_line\"\n\t\tandroid:layout_width=\"4dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginVertical=\"4dp\"\n\t\tandroid:layout_marginStart=\"8dp\"\n\t\tandroid:background=\"@drawable/bg_chip\"\n\t\tandroid:backgroundTint=\"?colorPrimary\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"6dp\"\n\t\tandroid:drawablePadding=\"10dp\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/view_line\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\ttools:drawableStartCompat=\"@drawable/ic_app_update\"\n\t\ttools:text=\"Уведомления отключены\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_body\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:paddingBottom=\"6dp\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/textView_title\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\ttools:text=\"Включите их, чтобы ничего не пропускать. Откройте настройки системы и разрешите их отправку.\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_list_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:clipChildren=\"false\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingBottom=\"@dimen/grid_spacing_outer\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_alignWithParentIfMissing=\"true\"\n\t\tandroid:layout_alignParentStart=\"true\"\n\t\tandroid:layout_alignParentTop=\"true\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/grid_spacing\"\n\t\tandroid:layout_marginTop=\"@dimen/grid_spacing_outer\"\n\t\tandroid:layout_toStartOf=\"@id/button_more\"\n\t\tandroid:gravity=\"center_vertical|start\"\n\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t<Button\n\t\tandroid:id=\"@+id/button_more\"\n\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_alignBaseline=\"@id/textView_title\"\n\t\tandroid:layout_alignParentEnd=\"true\"\n\t\tandroid:layout_marginEnd=\"@dimen/grid_spacing\"\n\t\tandroid:text=\"@string/show_all\" />\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_below=\"@id/textView_title\"\n\t\tandroid:layout_alignParentStart=\"true\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:nestedScrollingEnabled=\"false\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tandroid:paddingHorizontal=\"@dimen/grid_spacing\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_error\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_below=\"@id/recyclerView\"\n\t\tandroid:layout_alignParentStart=\"true\"\n\t\tandroid:layout_alignParentEnd=\"true\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/grid_spacing\"\n\t\tandroid:drawablePadding=\"12dp\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:drawableStartCompat=\"@drawable/ic_error_small\"\n\t\ttools:text=\"@tools:sample/lorem[6]\"\n\t\ttools:visibility=\"visible\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_loading_footer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:minHeight=\"@dimen/list_footer_height_outer\">\n\n\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:indeterminate=\"true\"\n\t\tapp:indicatorSize=\"24dp\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_loading_state.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:paddingBottom=\"?actionBarSize\">\n\n\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:indeterminate=\"true\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_manga_alternative.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tapp:cardCornerRadius=\"16dp\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"98dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:background=\"?colorSurfaceContainer\"\n\t\t\tapp:layout_constraintDimensionRatio=\"13:18\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.IconsView\n\t\t\tandroid:id=\"@+id/iconsView\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:background=\"@drawable/bg_list_icons\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tandroid:padding=\"4dp\"\n\t\t\tapp:iconSize=\"14dp\"\n\t\t\tapp:iconSpacing=\"4dp\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_cover\" />\n\n\t\t<org.koitharu.kotatsu.history.ui.util.ReadingProgressView\n\t\t\tandroid:id=\"@+id/progressView\"\n\t\t\tandroid:layout_width=\"@dimen/card_indicator_size\"\n\t\t\tandroid:layout_height=\"@dimen/card_indicator_size\"\n\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_cover\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"12dp\"\n\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:maxLines=\"2\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleMedium\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"12dp\"\n\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\tandroid:maxLines=\"2\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_title\"\n\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t\t<com.google.android.material.chip.Chip\n\t\t\tandroid:id=\"@+id/chip_source\"\n\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Assist\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginHorizontal=\"12dp\"\n\t\t\tapp:layout_constrainedWidth=\"true\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_subtitle\"\n\t\t\ttools:text=\"Mangadex\" />\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_migrate\"\n\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:layout_marginBottom=\"12dp\"\n\t\t\tandroid:text=\"@string/migrate\"\n\t\t\tapp:icon=\"@drawable/ic_replace\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/chip_source\"\n\t\t\tapp:layout_constraintVertical_bias=\"1\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_manga_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_margin=\"2dp\"\n\tandroid:background=\"@drawable/custom_selectable_item_background\"\n\tandroid:clipChildren=\"false\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"6dp\"\n\ttools:layout_width=\"140dp\">\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:background=\"?colorSurfaceContainer\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tapp:allowRgb565=\"true\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\t\ttools:ignore=\"ContentDescription\"\n\t\t\ttools:src=\"@tools:sample/backgrounds/scenic[5]\" />\n\n\t\t<org.koitharu.kotatsu.history.ui.util.ReadingProgressView\n\t\t\tandroid:id=\"@+id/progressView\"\n\t\t\tandroid:layout_width=\"@dimen/card_indicator_size\"\n\t\t\tandroid:layout_height=\"@dimen/card_indicator_size\"\n\t\t\tandroid:layout_gravity=\"bottom|end\"\n\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.IconsView\n\t\t\tandroid:id=\"@+id/iconsView\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"top|start\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:background=\"@drawable/bg_list_icons\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tandroid:padding=\"4dp\"\n\t\t\tapp:iconSize=\"14dp\"\n\t\t\tapp:iconSpacing=\"4dp\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.BadgeView\n\t\t\tandroid:id=\"@+id/badge\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"top|end\"\n\t\t\tandroid:layout_margin=\"@dimen/margin_small\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:maxCharacterCount=\"@integer/manga_badge_max_character_count\"\n\t\t\ttools:number=\"8\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t</FrameLayout>\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:elegantTextHeight=\"false\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:lines=\"2\"\n\t\tandroid:paddingVertical=\"4dp\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tandroid:textColor=\"?android:attr/textColorPrimary\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@+id/thumbnail\"\n\t\ttools:text=\"@tools:sample/lorem\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_manga_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:clipChildren=\"false\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover\"\n\t\tandroid:layout_width=\"40dp\"\n\t\tandroid:layout_height=\"40dp\"\n\t\tandroid:layout_marginStart=\"8dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:layout_marginBottom=\"8dp\"\n\t\tandroid:background=\"?colorSurfaceContainer\"\n\t\tapp:aspectRationHeight=\"0\"\n\t\tapp:aspectRationWidth=\"0\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\tapp:trimImage=\"true\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:maxLines=\"1\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tapp:layout_constraintBottom_toTopOf=\"@+id/textView_subtitle\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toTopOf=\"@+id/imageView_cover\"\n\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:maxLines=\"1\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_title\"\n\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.BadgeView\n\t\tandroid:id=\"@+id/badge\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_margin=\"@dimen/margin_small\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:maxCharacterCount=\"@integer/manga_badge_max_character_count\"\n\t\ttools:number=\"8\"\n\t\ttools:visibility=\"visible\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_manga_list_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/custom_selectable_item_background\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"118dp\"\n\t\t\tandroid:background=\"?colorSurfaceContainer\"\n\t\t\tapp:aspectRationHeight=\"0\"\n\t\t\tapp:aspectRationWidth=\"0\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintDimensionRatio=\"2:3\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Medium\"\n\t\t\tapp:trimImage=\"true\"\n\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t<org.koitharu.kotatsu.history.ui.util.ReadingProgressView\n\t\t\tandroid:id=\"@+id/progressView\"\n\t\t\tandroid:layout_width=\"@dimen/card_indicator_size\"\n\t\t\tandroid:layout_height=\"@dimen/card_indicator_size\"\n\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_cover\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.IconsView\n\t\t\tandroid:id=\"@+id/iconsView\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:background=\"@drawable/bg_list_icons\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tandroid:padding=\"4dp\"\n\t\t\tapp:iconSize=\"14dp\"\n\t\t\tapp:iconSpacing=\"4dp\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_cover\" />\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginEnd=\"12dp\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\tandroid:maxLines=\"3\"\n\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\t\t\tapp:lineHeight=\"18dp\"\n\t\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_author\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\t\tandroid:maxLines=\"1\"\n\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_title\"\n\t\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_tags\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\t\tandroid:maxLines=\"2\"\n\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_author\"\n\t\t\t\tapp:lineHeight=\"14dp\"\n\t\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t\t</LinearLayout>\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.BadgeView\n\t\t\tandroid:id=\"@+id/badge\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_margin=\"@dimen/margin_small\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:maxCharacterCount=\"@integer/manga_badge_max_character_count\"\n\t\t\ttools:number=\"8\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_nav_available.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:drawablePadding=\"?listPreferredItemPaddingStart\"\n\tandroid:ellipsize=\"end\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?android:listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingVertical=\"@dimen/margin_small\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\tandroid:singleLine=\"true\"\n\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\ttools:drawableStartCompat=\"@drawable/ic_feed\"\n\ttools:text=\"@string/feed\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_nav_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?android:windowBackground\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingVertical=\"@dimen/margin_small\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:baselineAligned=\"false\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:orientation=\"horizontal\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_weight=\"1\"\n\t\t\tandroid:drawablePadding=\"?listPreferredItemPaddingStart\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\t\ttools:drawableStart=\"@drawable/ic_explore_selector\"\n\t\t\ttools:text=\"@string/explore\" />\n\n\t\t<ImageButton\n\t\t\tandroid:id=\"@+id/imageView_remove\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\tandroid:contentDescription=\"@string/remove\"\n\t\t\tandroid:padding=\"@dimen/margin_small\"\n\t\t\tandroid:scaleType=\"center\"\n\t\t\tandroid:src=\"@drawable/ic_delete\"\n\t\t\tandroid:tooltipText=\"@string/remove\" />\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_reorder\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\tandroid:contentDescription=\"@string/reorder\"\n\t\t\tandroid:padding=\"@dimen/margin_small\"\n\t\t\tandroid:pointerIcon=\"grab\"\n\t\t\tandroid:scaleType=\"center\"\n\t\t\tandroid:src=\"@drawable/ic_reorder_handle\"\n\t\t\tandroid:tooltipText=\"@string/reorder\" />\n\n\t</LinearLayout>\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_hint\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\ttools:text=\"@string/suggestions_unavailable_text\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView\n\t\tandroid:id=\"@+id/ssiv\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:defaultFocusHighlightEnabled=\"false\"\n\t\tandroid:focusable=\"true\"\n\t\tapp:doubleTapZoomStyle=\"center\"\n\t\tapp:restoreStrategy=\"deferred\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_number\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"end|bottom\"\n\t\tandroid:layout_margin=\"@dimen/margin_small\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\tandroid:textColor=\"?android:textColorTertiary\"\n\t\ttools:text=\"5\" />\n\n\t<include layout=\"@layout/layout_page_info\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_page_thumb.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"?materialCardViewOutlinedStyle\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tapp:cardBackgroundColor=\"?colorBackgroundFloating\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_thumb\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:decodeRegion=\"true\"\n\t\tapp:trimImage=\"true\"\n\t\ttools:src=\"@drawable/ic_placeholder\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_number\"\n\t\tandroid:layout_width=\"@dimen/card_indicator_size\"\n\t\tandroid:layout_height=\"@dimen/card_indicator_size\"\n\t\tandroid:layout_gravity=\"bottom|end\"\n\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\tandroid:ellipsize=\"none\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAlignment=\"center\"\n\t\tandroid:textColor=\"?attr/colorOnTertiary\"\n\t\tandroid:textSize=\"14sp\"\n\t\ttools:background=\"@drawable/bg_badge_default\"\n\t\ttools:text=\"2\" />\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_page_webtoon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonFrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:defaultFocusHighlightEnabled=\"false\">\n\n\t<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonImageView\n\t\tandroid:id=\"@+id/ssiv\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:defaultFocusHighlightEnabled=\"false\"\n\t\tandroid:minHeight=\"1dp\"\n\t\tapp:panEnabled=\"false\"\n\t\tapp:quickScaleEnabled=\"false\"\n\t\tapp:zoomEnabled=\"false\" />\n\n\t<include layout=\"@layout/layout_page_info\" />\n\n</org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonFrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_preference.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?android:attr/listPreferredItemHeight\"\n\tandroid:orientation=\"vertical\"\n\tandroid:padding=\"4dp\"\n\tandroid:paddingStart=\"?android:attr/listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?android:attr/listPreferredItemPaddingEnd\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:textAppearance=\"?android:attr/textAppearanceListItem\"\n\t\ttools:text=\"@string/too_many_requests_message\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_summary\"\n\t\tstyle=\"@style/PreferenceSummaryTextStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"2dp\"\n\t\ttools:text=\"@string/tap_to_try_again\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_quick_filter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<HorizontalScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:id=\"@+id/scrollView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:clipChildren=\"false\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:paddingHorizontal=\"@dimen/list_spacing\"\n\tandroid:scrollbars=\"none\">\n\n\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\tandroid:id=\"@+id/chips_tags\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipChildren=\"false\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:paddingTop=\"2dp\"\n\t\tandroid:paddingBottom=\"6dp\"\n\t\tapp:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\"\n\t\tapp:selectionRequired=\"false\"\n\t\tapp:singleLine=\"true\"\n\t\tapp:singleSelection=\"false\" />\n\n</HorizontalScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_recent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/imageView_cover\"\n\tandroid:layout_width=\"@dimen/widget_cover_width\"\n\tandroid:layout_height=\"@dimen/widget_cover_height\"\n\tandroid:scaleType=\"centerCrop\"\n\ttools:ignore=\"ContentDescription\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_recommendation.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\">\n\n\t<androidx.viewpager2.widget.ViewPager2\n\t\tandroid:id=\"@+id/pager\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"@dimen/recommendation_item_height\"\n\t\tandroid:nestedScrollingEnabled=\"false\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.DotsIndicator\n\t\tandroid:id=\"@+id/dots\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center_horizontal\"\n\t\tandroid:layout_marginVertical=\"@dimen/margin_small\"\n\t\tapp:dotAlpha=\"0.6\"\n\t\tapp:dotScale=\"0.4\"\n\t\tapp:dotSize=\"10dp\"\n\t\tapp:dotSpacing=\"4dp\"\n\t\ttools:max=\"6\"\n\t\ttools:progress=\"2\" />\n\n</LinearLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/item_recommendation_manga.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:background=\"@drawable/list_selector\"\n\ttools:layout_height=\"@dimen/recommendation_item_height\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover\"\n\t\tandroid:layout_width=\"40dp\"\n\t\tandroid:layout_height=\"40dp\"\n\t\tandroid:layout_marginStart=\"8dp\"\n\t\tandroid:layout_marginTop=\"16dp\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Medium\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toTopOf=\"@+id/imageView_cover\"\n\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.MultilineEllipsizeTextView\n\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:layout_marginBottom=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_title\"\n\t\tapp:layout_constraintVertical_bias=\"0\"\n\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_scrobbling_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"?materialCardViewOutlinedStyle\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.imageview.ShapeableImageView\n\t\t\tandroid:id=\"@+id/imageView_icon\"\n\t\t\tandroid:layout_width=\"40dp\"\n\t\t\tandroid:layout_height=\"40dp\"\n\t\t\tandroid:layout_marginVertical=\"20dp\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:background=\"?colorPrimary\"\n\t\t\tandroid:scaleType=\"center\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Circle\"\n\t\t\tapp:tint=\"?colorOnPrimary\"\n\t\t\ttools:src=\"@drawable/ic_shikimori\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginHorizontal=\"16dp\"\n\t\t\tandroid:layout_toEndOf=\"@id/imageView_cover\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\tapp:layout_constraintBottom_toTopOf=\"@id/textView_status\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_icon\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:layout_constraintVertical_chainStyle=\"packed\"\n\t\t\ttools:text=\"@string/shikimori\" />\n\n\t\t<RatingBar\n\t\t\tandroid:id=\"@+id/ratingBar\"\n\t\t\tstyle=\"?ratingBarStyleSmall\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"6dp\"\n\t\t\tandroid:isIndicator=\"true\"\n\t\t\tandroid:max=\"1\"\n\t\t\tandroid:numStars=\"5\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_status\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/textView_status\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"@id/textView_status\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_status\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?textAppearanceBody1\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_icon\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\t\ttools:text=\"Reading\" />\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintDimensionRatio=\"1:1\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\ttools:src=\"@tools:sample/backgrounds/scenic[7]\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_scrobbling_manga.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:clipChildren=\"false\"\n\tandroid:padding=\"@dimen/list_spacing\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover\"\n\t\tandroid:layout_width=\"42dp\"\n\t\tandroid:layout_height=\"42dp\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:maxLines=\"1\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\tapp:layout_constraintBottom_toTopOf=\"@+id/ratingBar\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toTopOf=\"@+id/imageView_cover\"\n\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t<RatingBar\n\t\tandroid:id=\"@+id/ratingBar\"\n\t\tstyle=\"?ratingBarStyleSmall\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginTop=\"2dp\"\n\t\tandroid:isIndicator=\"true\"\n\t\tandroid:max=\"1\"\n\t\tandroid:numStars=\"5\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_title\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_manga_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"?materialCardViewFilledStyle\"\n\tandroid:layout_width=\"wrap_content\"\n\tandroid:layout_height=\"match_parent\"\n\tapp:shapeAppearance=\"?shapeAppearanceCornerSmall\"\n\ttools:layout_height=\"@dimen/search_suggestions_manga_height\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:layout_weight=\"1\"\n\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:elegantTextHeight=\"false\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:lines=\"1\"\n\t\t\tandroid:padding=\"4dp\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceLabelSmall\"\n\t\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t</LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_manga_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:layout_width=\"match_parent\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:scrollbars=\"none\"\n\tandroid:clipChildren=\"false\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\ttools:listitem=\"@layout/item_search_suggestion_manga_grid\"\n\tandroid:layout_height=\"@dimen/search_suggestions_manga_height\" />"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_query.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:drawablePadding=\"12dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\t\tapp:drawableStartCompat=\"@drawable/ic_history\"\n\t\ttools:text=\"@tools:sample/lorem[6]\" />\n\n\t<com.google.android.material.divider.MaterialDivider\n\t\tandroid:layout_width=\"1dp\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout_marginVertical=\"6dp\"\n\t\tandroid:layout_marginHorizontal=\"4dp\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/button_complete\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:padding=\"8dp\"\n\t\tandroid:src=\"@drawable/abc_ic_commit_search_api_mtrl_alpha\"\n\t\tapp:tint=\"?colorControlNormal\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_query_hint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:drawablePadding=\"12dp\"\n\tandroid:ellipsize=\"end\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\tandroid:singleLine=\"true\"\n\tandroid:textAppearance=\"?attr/textAppearanceBodyLarge\"\n\tapp:drawableStartCompat=\"@drawable/ic_suggestion\"\n\ttools:text=\"@tools:sample/lorem[6]\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?attr/listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingVertical=\"@dimen/margin_small\">\n\n\t<org.koitharu.kotatsu.core.ui.image.FaviconView\n\t\tandroid:id=\"@+id/imageView_cover\"\n\t\tandroid:layout_width=\"32dp\"\n\t\tandroid:layout_height=\"32dp\"\n\t\tandroid:layout_marginStart=\"?listPreferredItemPaddingStart\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:iconStyle=\"@style/FaviconDrawable.Small\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerSmall\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"@dimen/margin_small\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t</LinearLayout>\n\n\t<View\n\t\tandroid:layout_width=\"1dp\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout_marginVertical=\"8dp\"\n\t\tandroid:background=\"?colorOutline\" />\n\n\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\tandroid:id=\"@+id/switch_local\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:paddingHorizontal=\"?listPreferredItemPaddingEnd\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_source_tip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?attr/listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingVertical=\"@dimen/margin_small\">\n\n\t<org.koitharu.kotatsu.core.ui.image.FaviconView\n\t\tandroid:id=\"@+id/imageView_cover\"\n\t\tandroid:layout_width=\"32dp\"\n\t\tandroid:layout_height=\"32dp\"\n\t\tandroid:layout_marginStart=\"?listPreferredItemPaddingStart\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:iconStyle=\"@style/FaviconDrawable.Small\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerSmall\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"?listPreferredItemPaddingEnd\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n\t</LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_tags.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<HorizontalScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:id=\"@+id/scrollView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\tandroid:scrollbars=\"none\">\n\n\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\tandroid:id=\"@+id/chips_genres\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipChildren=\"false\"\n\t\tandroid:paddingTop=\"6dp\"\n\t\tandroid:paddingBottom=\"12dp\"\n\t\tapp:singleLine=\"true\" />\n\n</HorizontalScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_search_suggestion_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:drawablePadding=\"@dimen/screen_padding\"\n\tandroid:paddingVertical=\"@dimen/margin_small\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\"\n\tandroid:textAppearance=\"?textAppearanceBodySmall\"\n\tandroid:textColor=\"?android:textColorSecondary\"\n\ttools:drawableStart=\"@drawable/ic_error_small\"\n\ttools:text=\"@string/error_corrupted_file\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_shelf.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:padding=\"4dp\"\n\tandroid:theme=\"@style/Theme.Kotatsu.AppWidgetContainer\"\n\ttools:layout_width=\"116dp\">\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/rootLayout\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center_horizontal\"\n\t\tandroid:background=\"@drawable/bg_appwidget_card\"\n\t\tandroid:clipToOutline=\"true\"\n\t\tandroid:foreground=\"?android:selectableItemBackground\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:outlineProvider=\"background\"\n\t\ttools:ignore=\"UnusedAttribute,UselessParent\">\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"@dimen/widget_cover_height\"\n\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\ttools:ignore=\"ContentDescription\"\n\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:elegantTextHeight=\"false\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:lines=\"2\"\n\t\t\tandroid:paddingHorizontal=\"6dp\"\n\t\t\tandroid:paddingTop=\"2dp\"\n\t\t\tandroid:paddingBottom=\"4dp\"\n\t\t\tandroid:textColor=\"?android:attr/textColorPrimary\"\n\t\t\tandroid:textSize=\"12sp\"\n\t\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t</LinearLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_source_catalog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\ttools:ignore=\"RtlSymmetry\">\n\n\t<org.koitharu.kotatsu.core.ui.image.FaviconView\n\t\tandroid:id=\"@+id/imageView_icon\"\n\t\tandroid:layout_width=\"32dp\"\n\t\tandroid:layout_height=\"32dp\"\n\t\tandroid:background=\"?colorControlHighlight\"\n\t\tandroid:labelFor=\"@id/textView_title\"\n\t\tandroid:scaleType=\"fitCenter\"\n\t\tapp:iconStyle=\"@style/FaviconDrawable.Small\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerSmall\"\n\t\ttools:src=\"@tools:sample/avatars\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:layout_marginEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\tandroid:drawablePadding=\"4dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\ttools:drawableStart=\"@drawable/ic_off_small\"\n\t\t\ttools:text=\"English\" />\n\n\t</LinearLayout>\n\n\t<View\n\t\tandroid:layout_width=\"1dp\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout_marginVertical=\"@dimen/margin_small\"\n\t\tandroid:background=\"?colorOutline\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/imageView_add\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/add\"\n\t\tandroid:padding=\"@dimen/margin_normal\"\n\t\tandroid:scaleType=\"center\"\n\t\tandroid:src=\"@drawable/ic_add\"\n\t\tandroid:tooltipText=\"@string/add\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_source_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?android:windowBackground\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingVertical=\"@dimen/margin_small\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\">\n\n\t<org.koitharu.kotatsu.core.ui.image.FaviconView\n\t\tandroid:id=\"@+id/imageView_icon\"\n\t\tandroid:layout_width=\"32dp\"\n\t\tandroid:layout_height=\"32dp\"\n\t\tandroid:background=\"?colorControlHighlight\"\n\t\tandroid:labelFor=\"@id/textView_title\"\n\t\tandroid:scaleType=\"fitCenter\"\n\t\tapp:iconStyle=\"@style/FaviconDrawable.Small\"\n\t\tapp:shapeAppearance=\"?shapeAppearanceCornerSmall\"\n\t\ttools:src=\"@tools:sample/avatars\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"?android:listPreferredItemPaddingStart\"\n\t\tandroid:layout_marginEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:drawablePadding=\"4dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:drawableStart=\"@drawable/ic_pin_small\"\n\t\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\ttools:text=\"English\" />\n\n\t</LinearLayout>\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/imageView_menu\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/more\"\n\t\tandroid:padding=\"@dimen/margin_small\"\n\t\tandroid:scaleType=\"center\"\n\t\tandroid:src=\"@drawable/abc_ic_menu_overflow_material\"\n\t\tandroid:tooltipText=\"@string/more\"\n\t\tapp:tint=\"?colorControlNormal\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/imageView_add\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/add\"\n\t\tandroid:padding=\"@dimen/margin_small\"\n\t\tandroid:scaleType=\"center\"\n\t\tandroid:src=\"@drawable/ic_add\"\n\t\tandroid:tooltipText=\"@string/add\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/imageView_remove\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/disable\"\n\t\tandroid:padding=\"@dimen/margin_small\"\n\t\tandroid:scaleType=\"center\"\n\t\tandroid:src=\"@drawable/ic_disable\"\n\t\tandroid:tooltipText=\"@string/disable\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_sources_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:padding=\"12dp\"\n\tandroid:text=\"@string/nothing_found\"\n\tandroid:textAppearance=\"?textAppearanceBody1\"\n\tandroid:textColor=\"?android:textColorSecondary\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_stats.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:clipChildren=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\">\n\n\t<ImageView\n\t\tandroid:id=\"@+id/imageView_badge\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:contentDescription=\"@null\"\n\t\tapp:srcCompat=\"@drawable/bg_rounded_square\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:maxLines=\"1\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_summary\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:maxLines=\"1\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t</LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_storage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?listPreferredItemHeight\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingVertical=\"12dp\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?listPreferredItemPaddingEnd\">\n\n\t<org.koitharu.kotatsu.core.ui.widgets.CheckableImageView\n\t\tandroid:id=\"@+id/imageView_indicator\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:src=\"?android:listChoiceIndicatorSingle\"\n\t\ttools:ignore=\"TouchTargetSizeCheck\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"?listPreferredItemPaddingStart\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"6dp\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\ttools:text=\"@tools:sample/lorem[20]\" />\n\n\t</LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_storage_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"?selectableItemBackground\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?listPreferredItemHeight\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingVertical=\"12dp\"\n\tandroid:paddingStart=\"?listPreferredItemPaddingStart\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:drawablePadding=\"6dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\t\ttools:drawableStart=\"@drawable/ic_alert_outline\"\n\t\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"6dp\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\ttools:text=\"@tools:sample/lorem[20]\" />\n\n\t</LinearLayout>\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/button_remove\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/remove\"\n\t\tandroid:padding=\"?listPreferredItemPaddingEnd\"\n\t\tandroid:tooltipText=\"@string/remove\"\n\t\tapp:srcCompat=\"@drawable/ic_delete\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_storage_config2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    tools:layout_margin=\"@dimen/screen_padding\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingBottom=\"@dimen/margin_small\">\n\n        <TextView\n            android:id=\"@+id/textView_title\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"@dimen/screen_padding\"\n            android:layout_marginTop=\"@dimen/screen_padding\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textAppearance=\"?attr/textAppearanceTitleMedium\"\n            tools:text=\"@tools:sample/lorem[3]\" />\n\n        <TextView\n            android:id=\"@+id/textView_subtitle\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"@dimen/screen_padding\"\n            android:layout_marginTop=\"@dimen/margin_small\"\n            android:textAppearance=\"?attr/textAppearanceBodyMedium\"\n            tools:text=\"@tools:sample/lorem[5]\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"@dimen/screen_padding\"\n            android:layout_marginTop=\"@dimen/screen_padding\"\n            android:baselineAligned=\"false\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/textView_size\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"@dimen/margin_small\"\n                android:layout_weight=\"1\"\n                android:singleLine=\"true\"\n                android:textAppearance=\"?attr/textAppearanceBodyMedium\"\n                android:textColor=\"?android:textColorSecondary\"\n                tools:text=\"250MB / 10GB\" />\n\n            <com.google.android.material.progressindicator.LinearProgressIndicator\n                android:id=\"@+id/indicator_size\"\n                android:layout_width=\"160dp\"\n                android:layout_height=\"wrap_content\"\n                app:trackCornerRadius=\"5dp\"\n                app:trackThickness=\"10dp\"\n                tools:progress=\"40\" />\n\n        </LinearLayout>\n\n        <TextView\n            android:id=\"@+id/textView_info\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"@dimen/screen_padding\"\n            android:layout_marginTop=\"@dimen/margin_small\"\n            android:textAppearance=\"?attr/textAppearanceBodySmall\"\n            android:textColor=\"?android:textColorSecondary\"\n            tools:text=\"Content will be removed within application\" />\n\n        <Button\n            android:id=\"@+id/button_remove\"\n            style=\"?buttonBarButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"end\"\n            android:layout_marginEnd=\"@dimen/margin_small\"\n            android:text=\"@string/remove\" />\n\n        <View\n            android:id=\"@+id/spacer\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"@dimen/margin_small\"\n            tools:visibility=\"gone\" />\n\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_tip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_margin=\"@dimen/margin_small\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\">\n\n\t\t<com.google.android.material.imageview.ShapeableImageView\n\t\t\tandroid:id=\"@+id/imageView_icon\"\n\t\t\tandroid:layout_width=\"56dp\"\n\t\t\tandroid:layout_height=\"56dp\"\n\t\t\tandroid:background=\"?colorSurfaceVariant\"\n\t\t\tandroid:scaleType=\"center\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Circle\"\n\t\t\ttools:src=\"@drawable/ic_tap_reorder\" />\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:paddingTop=\"@dimen/margin_normal\"\n\t\t\tandroid:paddingBottom=\"@dimen/margin_small\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\t\t\ttools:text=\"@string/sources_reorder_tip\" />\n\n\t\t\t<Button\n\t\t\t\tandroid:id=\"@+id/button_close\"\n\t\t\t\tstyle=\"?borderlessButtonStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"end\"\n\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\tandroid:text=\"@string/got_it\" />\n\t\t</LinearLayout>\n\n\t</LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/item_tip2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<org.koitharu.kotatsu.core.ui.widgets.TipView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_marginHorizontal=\"8dp\"\n\tandroid:layout_marginVertical=\"8dp\"\n\tapp:shapeAppearance=\"?shapeAppearanceCornerExtraLarge\"\n\ttools:icon=\"@drawable/ic_notification\"\n\ttools:primaryButtonText=\"@string/settings\"\n\ttools:secondaryButtonText=\"@string/close\"\n\ttools:text=\"Включите их, чтобы ничего не пропускать. Откройте настройки системы и разрешите их отправку.\"\n\ttools:title=\"Уведомления отключены\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_track_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@drawable/list_selector\"\n\tandroid:clipChildren=\"false\"\n\tandroid:minHeight=\"72dp\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover\"\n\t\tandroid:layout_width=\"40dp\"\n\t\tandroid:layout_height=\"40dp\"\n\t\tandroid:layout_marginStart=\"8dp\"\n\t\tandroid:layout_marginTop=\"16dp\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:drawablePadding=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:maxLines=\"1\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toTopOf=\"@+id/imageView_cover\"\n\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_summary\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginTop=\"2dp\"\n\t\tandroid:layout_marginEnd=\"8dp\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:paddingBottom=\"16dp\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@+id/imageView_cover\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_title\"\n\t\ttools:text=\"@tools:sample/lorem[2]\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_details_table.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\">\n\n\t<com.google.android.material.card.MaterialCardView\n\t\tandroid:id=\"@+id/card_details\"\n\t\tstyle=\"?materialCardViewFilledStyle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginBottom=\"-12dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_progress_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/barrier_header\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_source_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/source\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/barrier_header\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_source\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginEnd=\"12dp\"\n\t\tandroid:background=\"@drawable/custom_selectable_item_background\"\n\t\tandroid:drawablePadding=\"4dp\"\n\t\tandroid:padding=\"4dp\"\n\t\tandroid:pointerIcon=\"hand\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_source_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\ttools:text=\"MangaSource\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_author_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/author\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_source_label\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_author\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"16dp\"\n\t\tandroid:layout_marginEnd=\"16dp\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_author_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\ttools:text=\"Author name\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_translation_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/translation\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_author\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_translation\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\tandroid:drawablePadding=\"4dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_translation_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\ttools:text=\"English\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_rating_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/rating\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_translation_label\" />\n\n\t<RatingBar\n\t\tandroid:id=\"@+id/ratingBar_rating\"\n\t\tstyle=\"?ratingBarStyleSmall\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:isIndicator=\"true\"\n\t\tandroid:max=\"1\"\n\t\tandroid:numStars=\"5\"\n\t\tandroid:stepSize=\"0.5\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_rating_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\tapp:layout_constraintTop_toTopOf=\"@id/textView_rating_label\"\n\t\ttools:text=\"Author name\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_state_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/state\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_rating_label\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_state\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_state_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\ttools:text=\"Ongoing\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_chapters_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/chapters\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_state_label\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_chapters\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_chapters_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\ttools:text=\"10 of 50\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_local_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/on_device\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_chapters_label\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_local\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginEnd=\"12dp\"\n\t\tandroid:background=\"@drawable/custom_selectable_item_background\"\n\t\tandroid:padding=\"4dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constrainedWidth=\"true\"\n\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_local_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\ttools:text=\"25 Mb\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_progress_label\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/progress\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintStart_toStartOf=\"@id/card_details\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_local_label\" />\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progress\"\n\t\tstyle=\"?linearProgressIndicatorStyle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"12dp\"\n\t\tandroid:indeterminate=\"false\"\n\t\tandroid:max=\"100\"\n\t\tandroid:visibility=\"visible\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/textView_progress_label\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/textView_progress\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/barrier_table\"\n\t\tapp:layout_constraintTop_toTopOf=\"@id/textView_progress_label\"\n\t\tapp:trackColor=\"?android:colorBackground\"\n\t\ttools:progress=\"12\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_progress\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_progress_label\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/card_details\"\n\t\ttools:text=\"40%\" />\n\n\t<androidx.constraintlayout.widget.Barrier\n\t\tandroid:id=\"@+id/barrier_table\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:barrierDirection=\"end\"\n\t\tapp:constraint_referenced_ids=\"textView_source_label,textView_author_label,textView_rating_label,textView_state_label,textView_progress_label,textView_chapters_label,textView_local_label,textView_translation_label\" />\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_page_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:parentTag=\"android.widget.FrameLayout\">\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/layout_progress\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\">\n\n\t\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\t\tandroid:id=\"@+id/progressBar\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginBottom=\"8dp\"\n\t\t\tandroid:indeterminate=\"true\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_status\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:background=\"@drawable/bg_rounded_transparency\"\n\t\t\tandroid:gravity=\"center\"\n\t\t\tandroid:padding=\"4dp\"\n\t\t\tandroid:textAppearance=\"?textAppearanceBodyLarge\"\n\t\t\ttools:text=\"72%\" />\n\t</LinearLayout>\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/layout_error\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:layout_marginStart=\"56dp\"\n\t\tandroid:layout_marginEnd=\"56dp\"\n\t\tandroid:background=\"@drawable/bg_card\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:padding=\"@dimen/screen_padding\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"gone\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_error\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:drawablePadding=\"12dp\"\n\t\t\tandroid:gravity=\"center_horizontal\"\n\t\t\tandroid:textAlignment=\"center\"\n\t\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\t\tapp:drawableTopCompat=\"@drawable/ic_error_large\"\n\t\t\ttools:text=\"@tools:sample/lorem[6]\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_retry\"\n\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\tandroid:text=\"@string/try_again\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_error_details\"\n\t\t\tstyle=\"?borderlessButtonStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:text=\"@string/details\" />\n\n\t</LinearLayout>\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_reader_actions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:layout_height=\"wrap_content\"\n\ttools:layout_width=\"match_parent\"\n\ttools:orientation=\"horizontal\"\n\ttools:parentTag=\"android.widget.LinearLayout\"\n\ttools:style=\"?dockedToolbarStyle\">\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_prev\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/prev_chapter\"\n\t\t\tapp:icon=\"@drawable/ic_prev\" />\n\t</FrameLayout>\n\n\t<com.google.android.material.slider.Slider\n\t\tandroid:id=\"@+id/slider\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:stepSize=\"1.0\"\n\t\tandroid:valueFrom=\"0\"\n\t\tandroid:visibility=\"visible\"\n\t\tapp:labelBehavior=\"floating\"\n\t\ttools:value=\"6\"\n\t\ttools:valueTo=\"20\" />\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_next\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/next_chapter\"\n\t\t\tapp:icon=\"@drawable/ic_next\" />\n\t</FrameLayout>\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_save\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/save_page\"\n\t\t\tapp:icon=\"@drawable/ic_save\" />\n\t</FrameLayout>\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_timer\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/automatic_scroll\"\n\t\t\tapp:icon=\"@drawable/ic_timer\" />\n\t</FrameLayout>\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_screen_rotation\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/screen_orientation\"\n\t\t\tapp:icon=\"@drawable/ic_screen_rotation\" />\n\t</FrameLayout>\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_bookmark\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/bookmark_add\"\n\t\t\tapp:icon=\"@drawable/ic_bookmark\" />\n\t</FrameLayout>\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_pages_thumbs\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/pages\"\n\t\t\tapp:icon=\"@drawable/ic_grid\" />\n\t</FrameLayout>\n\n\t<FrameLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_options\"\n\t\t\tstyle=\"?materialIconButtonStyle\"\n\t\t\tandroid:layout_width=\"48dp\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_gravity=\"center\"\n\t\t\tandroid:contentDescription=\"@string/options\"\n\t\t\tapp:icon=\"@drawable/abc_ic_menu_overflow_material\" />\n\t</FrameLayout>\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_sheet_header_adaptive.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\ttools:orientation=\"vertical\"\n\ttools:parentTag=\"android.widget.LinearLayout\">\n\n\t<com.google.android.material.bottomsheet.BottomSheetDragHandleView\n\t\tandroid:id=\"@+id/sh_dragHandle\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/sh_layout_sidesheet\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:baselineAligned=\"false\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tandroid:paddingVertical=\"8dp\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:ignore=\"UseCompoundDrawables\"\n\t\ttools:visibility=\"visible\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/sh_textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_weight=\"1\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\tandroid:textAppearance=\"?textAppearanceBodyLarge\"\n\t\t\ttools:text=\"@string/filter\" />\n\n\t\t<ImageButton\n\t\t\tandroid:id=\"@+id/sh_button_close\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\tandroid:contentDescription=\"@string/close\"\n\t\t\tandroid:tooltipText=\"@string/close\"\n\t\t\tandroid:padding=\"16dp\"\n\t\t\tapp:srcCompat=\"?actionModeCloseDrawable\"\n\t\t\tapp:tint=\"?colorControlActivated\" />\n\n\t</LinearLayout>\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/navigation_rail_fab.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:id=\"@+id/cat_navigation_rail_efab_container\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_gravity=\"top|start\"\n\tandroid:clipChildren=\"false\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:orientation=\"vertical\">\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/button_expand\"\n\t\tandroid:layout_width=\"56dp\"\n\t\tandroid:layout_height=\"56dp\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/expand\"\n\t\tandroid:scaleType=\"center\"\n\t\tandroid:src=\"@drawable/ic_drawer_menu\" />\n\n\t<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton\n\t\tandroid:id=\"@+id/railFab\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:text=\"@string/_continue\"\n\t\tapp:icon=\"@drawable/ic_read\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_dialog_autocompletetextview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:layout_marginTop=\"48dp\"\n\tandroid:layout_marginBottom=\"48dp\"\n\tandroid:overScrollMode=\"ifContentScrolls\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@android:id/message\"\n\t\t\tstyle=\"?android:attr/textAppearanceSmall\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"24dp\"\n\t\t\tandroid:layout_marginLeft=\"24dp\"\n\t\t\tandroid:layout_marginEnd=\"24dp\"\n\t\t\tandroid:layout_marginRight=\"24dp\"\n\t\t\tandroid:layout_marginBottom=\"48dp\"\n\t\t\tandroid:labelFor=\"@android:id/edit\"\n\t\t\tandroid:textColor=\"?android:attr/textColorSecondary\"\n\t\t\ttools:ignore=\"LabelFor\" />\n\n\t\t<FrameLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"20dp\"\n\t\t\tandroid:layout_marginLeft=\"20dp\"\n\t\t\tandroid:layout_marginEnd=\"20dp\"\n\t\t\tandroid:layout_marginRight=\"20dp\">\n\n\t\t\t<AutoCompleteTextView\n\t\t\t\tandroid:id=\"@android:id/edit\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:importantForAutofill=\"no\"\n\t\t\t\tandroid:minHeight=\"48dp\" />\n\n\t\t\t<ImageButton\n\t\t\t\tandroid:id=\"@+id/dropdown\"\n\t\t\t\tandroid:layout_width=\"48dp\"\n\t\t\t\tandroid:layout_height=\"48dp\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|end\"\n\t\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\t\tandroid:paddingBottom=\"2dp\"\n\t\t\t\tandroid:scaleType=\"center\"\n\t\t\t\tandroid:src=\"@drawable/ic_expand_more\"\n\t\t\t\ttools:ignore=\"ContentDescription\" />\n\n\t\t</FrameLayout>\n\n\t</LinearLayout>\n</ScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_dialog_multiautocompletetextview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:layout_marginTop=\"48dp\"\n\tandroid:layout_marginBottom=\"48dp\"\n\tandroid:overScrollMode=\"ifContentScrolls\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@android:id/message\"\n\t\t\tstyle=\"?android:attr/textAppearanceSmall\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"24dp\"\n\t\t\tandroid:layout_marginLeft=\"24dp\"\n\t\t\tandroid:layout_marginEnd=\"24dp\"\n\t\t\tandroid:layout_marginRight=\"24dp\"\n\t\t\tandroid:layout_marginBottom=\"48dp\"\n\t\t\tandroid:labelFor=\"@android:id/edit\"\n\t\t\tandroid:textColor=\"?android:attr/textColorSecondary\"\n\t\t\ttools:ignore=\"LabelFor\" />\n\n\t\t<MultiAutoCompleteTextView\n\t\t\tandroid:id=\"@android:id/edit\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"20dp\"\n\t\t\tandroid:layout_marginLeft=\"20dp\"\n\t\t\tandroid:layout_marginEnd=\"20dp\"\n\t\t\tandroid:layout_marginRight=\"20dp\"\n\t\t\tandroid:importantForAutofill=\"no\"\n\t\t\tandroid:minHeight=\"48dp\" />\n\n\t</LinearLayout>\n</ScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_memory_usage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingBottom=\"4dp\"\n\ttools:ignore=\"RtlSymmetry\">\n\n\t<org.koitharu.kotatsu.core.ui.widgets.SegmentedBarView\n\t\tandroid:id=\"@+id/bar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"18dp\"\n\t\tandroid:layout_marginStart=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:background=\"?colorSecondaryContainer\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/label_storage\"\n\t\tstyle=\"@style/Widget.Kotatsu.TextView.Indicator\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:text=\"@string/saved_manga\"\n\t\tapp:drawableStartCompat=\"@drawable/bg_rounded_square\"\n\t\ttools:drawableTint=\"?colorPrimary\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/label_pages_cache\"\n\t\tstyle=\"@style/Widget.Kotatsu.TextView.Indicator\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:text=\"@string/pages_cache\"\n\t\tapp:drawableStartCompat=\"@drawable/bg_rounded_square\"\n\t\ttools:drawableTint=\"?colorSecondary\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/label_other_cache\"\n\t\tstyle=\"@style/Widget.Kotatsu.TextView.Indicator\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:text=\"@string/other_cache\"\n\t\tapp:drawableStartCompat=\"@drawable/bg_rounded_square\"\n\t\ttools:drawableTint=\"?colorTertiary\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/label_available\"\n\t\tstyle=\"@style/Widget.Kotatsu.TextView.Indicator\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:layout_marginEnd=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginBottom=\"@dimen/screen_padding\"\n\t\tandroid:text=\"@string/computing_\"\n\t\tapp:drawableStartCompat=\"@drawable/bg_rounded_square\"\n\t\tapp:drawableTint=\"?colorSecondaryContainer\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_slider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:baselineAligned=\"false\"\n\tandroid:clipChildren=\"false\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?android:attr/listPreferredItemHeightSmall\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingStart=\"?android:attr/listPreferredItemPaddingStart\"\n\tandroid:paddingEnd=\"?android:attr/listPreferredItemPaddingEnd\"\n\ttools:ignore=\"PrivateResource\">\n\n\t<include layout=\"@layout/image_frame\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:layout_marginBottom=\"8dp\"\n\t\tandroid:clipChildren=\"false\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tandroid:baselineAligned=\"true\"\n\t\t\tandroid:baselineAlignedChildIndex=\"0\"\n\t\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@android:id/title\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:ellipsize=\"marquee\"\n\t\t\t\tandroid:labelFor=\"@id/seekbar\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:textAppearance=\"?android:attr/textAppearanceListItem\"\n\t\t\t\ttools:ignore=\"LabelFor\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@android:id/summary\"\n\t\t\t\tstyle=\"@style/PreferenceSummaryTextStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:textAlignment=\"viewStart\"\n\t\t\t\tandroid:textColor=\"?android:attr/textColorSecondary\" />\n\n\t\t</LinearLayout>\n\n\t\t<com.google.android.material.slider.Slider\n\t\t\tandroid:id=\"@+id/slider\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:paddingStart=\"0dp\"\n\t\t\tandroid:paddingEnd=\"16dp\"\n\t\t\tapp:labelBehavior=\"gone\"\n\t\t\tapp:tickVisible=\"false\" />\n\t</LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_split_switch.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:baselineAligned=\"false\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingEnd=\"?android:attr/listPreferredItemPaddingEnd\"\n\ttools:ignore=\"RtlSymmetry\">\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/press_container\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:background=\"?selectableItemBackground\"\n\t\tandroid:baselineAligned=\"false\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:minHeight=\"?android:attr/listPreferredItemHeightSmall\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tandroid:paddingStart=\"?android:attr/listPreferredItemPaddingStart\">\n\n\t\t<include layout=\"@layout/image_frame\" />\n\n\t\t<RelativeLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:paddingTop=\"16dp\"\n\t\t\tandroid:paddingBottom=\"16dp\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@android:id/title\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:ellipsize=\"marquee\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:textAppearance=\"?android:attr/textAppearanceListItem\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@android:id/summary\"\n\t\t\t\tstyle=\"@style/PreferenceSummaryTextStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_below=\"@android:id/title\"\n\t\t\t\tandroid:layout_alignStart=\"@android:id/title\"\n\t\t\t\tandroid:layout_gravity=\"start\"\n\t\t\t\tandroid:maxLines=\"10\"\n\t\t\t\tandroid:textAlignment=\"viewStart\"\n\t\t\t\tandroid:textColor=\"?android:attr/textColorSecondary\" />\n\n\t\t</RelativeLayout>\n\t</LinearLayout>\n\n\t<View\n\t\tandroid:layout_width=\"1dp\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:layout_marginVertical=\"16dp\"\n\t\tandroid:background=\"?dividerVertical\" />\n\n\t<!-- Preference should place its actual preference widget here. -->\n\t<LinearLayout\n\t\tandroid:id=\"@android:id/widget_frame\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:gravity=\"end|center_vertical\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:paddingStart=\"16dp\"\n\t\tandroid:paddingEnd=\"0dp\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_theme.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:baselineAligned=\"false\"\n\tandroid:clipChildren=\"false\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:gravity=\"center_vertical\"\n\tandroid:minHeight=\"?android:attr/listPreferredItemHeight\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingVertical=\"8dp\"\n\ttools:ignore=\"PrivateResource\">\n\n\t<TextView\n\t\tandroid:id=\"@android:id/title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:ellipsize=\"marquee\"\n\t\tandroid:labelFor=\"@id/scrollView\"\n\t\tandroid:paddingStart=\"?android:attr/listPreferredItemPaddingStart\"\n\t\tandroid:paddingEnd=\"?android:attr/listPreferredItemPaddingEnd\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?android:attr/textAppearanceListItem\"\n\t\ttools:ignore=\"LabelFor\"\n\t\ttools:text=\"@string/color_theme\" />\n\n\t<HorizontalScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"4dp\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:descendantFocusability=\"blocksDescendants\"\n\t\tandroid:scrollbars=\"none\">\n\n\t\t<LinearLayout\n\t\t\tandroid:id=\"@+id/linear\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"horizontal\"\n\t\t\tandroid:paddingStart=\"?android:attr/listPreferredItemPaddingStart\"\n\t\t\tandroid:paddingEnd=\"?android:attr/listPreferredItemPaddingEnd\" />\n\n\t</HorizontalScrollView>\n\n\t<TextView\n\t\tandroid:id=\"@android:id/summary\"\n\t\tstyle=\"@style/PreferenceSummaryTextStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"2dp\"\n\t\tandroid:paddingStart=\"?android:attr/listPreferredItemPaddingStart\"\n\t\tandroid:paddingEnd=\"?android:attr/listPreferredItemPaddingEnd\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAlignment=\"viewStart\"\n\t\tandroid:textColor=\"?android:attr/textColorSecondary\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_toggle_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"?materialCardViewFilledStyle\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_marginHorizontal=\"16dp\"\n\tandroid:layout_marginVertical=\"8dp\"\n\tandroid:paddingBottom=\"8dp\"\n\tapp:cardBackgroundColor=\"?colorPrimaryContainer\"\n\tapp:cardCornerRadius=\"24dp\">\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:baselineAligned=\"false\"\n\t\tandroid:clipChildren=\"false\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:minHeight=\"?android:attr/listPreferredItemHeightSmall\"\n\t\tandroid:padding=\"16dp\">\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_weight=\"1\"\n\t\t\tandroid:orientation=\"vertical\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@android:id/title\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleLarge\"\n\t\t\t\tandroid:textSize=\"20sp\"\n\t\t\t\ttools:text=\"Title\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@android:id/summary\"\n\t\t\t\tstyle=\"@style/PreferenceSummaryTextStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"start\"\n\t\t\t\tandroid:maxLines=\"10\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\ttools:text=\"Subtitle\" />\n\n\t\t</LinearLayout>\n\n\t\t<LinearLayout\n\t\t\tandroid:id=\"@android:id/widget_frame\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:gravity=\"end|center_vertical\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:paddingStart=\"16dp\"\n\t\t\tandroid:paddingEnd=\"0dp\" />\n\n\t</LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_widget_material_switch.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.materialswitch.MaterialSwitch\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:id=\"@+id/switchWidget\"\n\tandroid:layout_width=\"wrap_content\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:background=\"@null\"\n\tandroid:clickable=\"false\"\n\tandroid:focusable=\"false\" />"
  },
  {
    "path": "app/src/main/res/layout/sheet_base.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\">\n\n\t<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n\t\tandroid:id=\"@+id/headerBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\" />\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:scrollIndicators=\"top\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\ttools:listitem=\"@layout/item_checkable_new\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_chapters_pages.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n        android:id=\"@+id/headerBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/chapters\" />\n\n    <com.google.android.material.appbar.MaterialToolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"8dp\"\n            android:baselineAligned=\"false\"\n            android:orientation=\"horizontal\">\n\n            <com.google.android.material.tabs.TabLayout\n                android:id=\"@+id/tabs\"\n                style=\"?tabSecondaryStyle\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"start|center_vertical\"\n                android:layout_weight=\"1\"\n                android:background=\"@null\"\n                app:tabGravity=\"start\"\n                app:tabIndicator=\"@drawable/bg_tab_pill\"\n                app:tabIndicatorAnimationMode=\"fade\"\n                app:tabIndicatorColor=\"?colorSurfaceDim\"\n                app:tabIndicatorFullWidth=\"true\"\n                app:tabIndicatorGravity=\"stretch\"\n                app:tabInlineLabel=\"true\"\n                app:tabMinWidth=\"0dp\"\n                app:tabMode=\"scrollable\"\n                app:tabUnboundedRipple=\"true\" />\n\n            <com.google.android.material.button.MaterialSplitButton\n                android:id=\"@+id/split_button_read\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"end|center_vertical\"\n                android:paddingTop=\"0dp\"\n                android:paddingBottom=\"0dp\">\n\n                <Button\n                    android:id=\"@+id/button_read\"\n                    style=\"?materialSplitButtonLeadingFilledStyle\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:minWidth=\"@dimen/read_button_min_width\"\n                    android:text=\"@string/read\" />\n\n                <Button\n                    android:id=\"@+id/button_read_menu\"\n                    style=\"?materialSplitButtonIconFilledStyle\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:contentDescription=\"@string/show_menu\"\n                    app:icon=\"?expandCollapseIndicator\"\n                    app:toggleCheckedStateOnClick=\"false\" />\n\n            </com.google.android.material.button.MaterialSplitButton>\n\n        </LinearLayout>\n\n    </com.google.android.material.appbar.MaterialToolbar>\n\n    <org.koitharu.kotatsu.core.ui.widgets.TouchBlockLayout\n        android:id=\"@+id/layout_touchBlock\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.viewpager2.widget.ViewPager2\n            android:id=\"@+id/pager\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n    </org.koitharu.kotatsu.core.ui.widgets.TouchBlockLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_filter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n        android:id=\"@+id/headerBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/filter\" />\n\n    <androidx.core.widget.NestedScrollView\n        android:id=\"@+id/scrollView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:clipToPadding=\"false\"\n        android:fillViewport=\"true\"\n        android:scrollIndicators=\"top|bottom\"\n        tools:ignore=\"UnusedAttribute\">\n\n        <LinearLayout\n            android:id=\"@+id/layout_body\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingHorizontal=\"@dimen/margin_small\"\n            android:paddingBottom=\"@dimen/margin_normal\">\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_order\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:title=\"@string/sort_order\">\n\n                <com.google.android.material.card.MaterialCardView\n                    android:id=\"@+id/card_order\"\n                    style=\"?materialCardViewOutlinedStyle\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\">\n\n                    <Spinner\n                        android:id=\"@+id/spinner_order\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"@dimen/spinner_height\"\n                        android:minHeight=\"?listPreferredItemHeightSmall\"\n                        android:paddingHorizontal=\"8dp\" />\n\n                </com.google.android.material.card.MaterialCardView>\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_saved_filters\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/saved_filters\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_saved_filters\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_locale\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/language\">\n\n                <com.google.android.material.card.MaterialCardView\n                    android:id=\"@+id/card_locale\"\n                    style=\"?materialCardViewOutlinedStyle\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\">\n\n                    <Spinner\n                        android:id=\"@+id/spinner_locale\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"@dimen/spinner_height\"\n                        android:minHeight=\"?listPreferredItemHeightSmall\"\n                        android:paddingHorizontal=\"8dp\"\n                        android:popupBackground=\"@drawable/m3_spinner_popup_background\" />\n\n                </com.google.android.material.card.MaterialCardView>\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_original_locale\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/original_language\">\n\n                <com.google.android.material.card.MaterialCardView\n                    android:id=\"@+id/card_original_locale\"\n                    style=\"?materialCardViewOutlinedStyle\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\">\n\n                    <Spinner\n                        android:id=\"@+id/spinner_original_locale\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"@dimen/spinner_height\"\n                        android:minHeight=\"?listPreferredItemHeightSmall\"\n                        android:paddingHorizontal=\"8dp\"\n                        android:popupBackground=\"@drawable/m3_spinner_popup_background\" />\n\n                </com.google.android.material.card.MaterialCardView>\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_genres\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:showMoreButton=\"true\"\n                app:title=\"@string/genres\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_genres\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_genresExclude\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:showMoreButton=\"true\"\n                app:title=\"@string/genres_exclude\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_genresExclude\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_author\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:showMoreButton=\"false\"\n                app:title=\"@string/author\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_author\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_types\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/type\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_types\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_state\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/state\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_state\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_contentRating\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/content_rating\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_contentRating\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_demographics\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/demographics\">\n\n                <org.koitharu.kotatsu.core.ui.widgets.ChipsView\n                    android:id=\"@+id/chips_demographics\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginHorizontal=\"@dimen/margin_small\"\n                    android:layout_marginTop=\"@dimen/margin_small\"\n                    app:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_year\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/year\">\n\n                <com.google.android.material.slider.Slider\n                    android:id=\"@+id/slider_year\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:stepSize=\"1\"\n                    app:labelBehavior=\"gone\"\n                    app:tickVisible=\"true\"\n                    tools:value=\"2020\"\n                    tools:valueFrom=\"1900\"\n                    tools:valueTo=\"2090\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n            <org.koitharu.kotatsu.filter.ui.FilterFieldLayout\n                android:id=\"@+id/layout_yearsRange\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                app:title=\"@string/years\">\n\n                <com.google.android.material.slider.RangeSlider\n                    android:id=\"@+id/slider_yearsRange\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"@dimen/margin_normal\"\n                    android:stepSize=\"1\"\n                    app:labelBehavior=\"gone\"\n                    app:tickVisible=\"true\"\n                    tools:valueFrom=\"1900\"\n                    tools:valueTo=\"2090\" />\n\n            </org.koitharu.kotatsu.filter.ui.FilterFieldLayout>\n\n        </LinearLayout>\n    </androidx.core.widget.NestedScrollView>\n\n    <com.google.android.material.dockedtoolbar.DockedToolbarLayout\n        android:id=\"@+id/docked_toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:fitsSystemWindows=\"false\">\n\n        <LinearLayout\n            android:id=\"@+id/layout_bottom\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"@dimen/m3_comp_toolbar_docked_container_height\"\n            android:gravity=\"center_vertical\">\n\n            <com.google.android.material.button.MaterialButton\n                android:id=\"@+id/button_save\"\n                style=\"?materialButtonOutlinedStyle\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"@dimen/margin_small\"\n                android:layout_weight=\"1\"\n                android:enabled=\"false\"\n                android:text=\"@string/save\"\n                tools:enabled=\"true\" />\n\n            <com.google.android.material.button.MaterialButton\n                android:id=\"@+id/button_done\"\n                style=\"?materialButtonTonalStyle\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/margin_small\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/done\" />\n\n        </LinearLayout>\n    </com.google.android.material.dockedtoolbar.DockedToolbarLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_list_mode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:animateLayoutChanges=\"true\"\n\tandroid:orientation=\"vertical\">\n\n\t<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n\t\tandroid:id=\"@+id/headerBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:title=\"@string/list_options\" />\n\n\t<androidx.core.widget.NestedScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:scrollIndicators=\"top\"\n\t\tandroid:scrollbars=\"vertical\">\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:paddingBottom=\"@dimen/margin_normal\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/list_mode\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\" />\n\n\t\t\t<com.google.android.material.button.MaterialButtonToggleGroup\n\t\t\t\tandroid:id=\"@+id/checkableGroup\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:baselineAligned=\"false\"\n\t\t\t\tandroid:orientation=\"horizontal\"\n\t\t\t\tapp:selectionRequired=\"true\"\n\t\t\t\tapp:singleSelection=\"true\">\n\n\t\t\t\t<com.google.android.material.button.MaterialButton\n\t\t\t\t\tandroid:id=\"@+id/button_list\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.ToggleButton.Vertical\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:text=\"@string/compact\"\n\t\t\t\t\tapp:icon=\"@drawable/ic_list\" />\n\n\t\t\t\t<com.google.android.material.button.MaterialButton\n\t\t\t\t\tandroid:id=\"@+id/button_list_detailed\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.ToggleButton.Vertical\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:text=\"@string/details\"\n\t\t\t\t\tapp:icon=\"@drawable/ic_list_detailed\" />\n\n\t\t\t\t<com.google.android.material.button.MaterialButton\n\t\t\t\t\tandroid:id=\"@+id/button_grid\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.ToggleButton.Vertical\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:text=\"@string/grid\"\n\t\t\t\t\tapp:icon=\"@drawable/ic_grid\" />\n\n\t\t\t</com.google.android.material.button.MaterialButtonToggleGroup>\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_grid_title\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/grid_size\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<com.google.android.material.slider.Slider\n\t\t\t\tandroid:id=\"@+id/slider_grid\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"16dp\"\n\t\t\t\tandroid:valueFrom=\"50\"\n\t\t\t\tandroid:valueTo=\"150\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\tapp:labelBehavior=\"floating\"\n\t\t\t\tapp:tickVisible=\"false\"\n\t\t\t\ttools:value=\"100\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_order_title\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/sort_order\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t<com.google.android.material.card.MaterialCardView\n\t\t\t\tandroid:id=\"@+id/card_order\"\n\t\t\t\tstyle=\"?materialCardViewOutlinedStyle\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"16dp\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\ttools:visibility=\"visible\">\n\n\t\t\t\t<Spinner\n\t\t\t\t\tandroid:id=\"@+id/spinner_order\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"56dp\"\n\t\t\t\t\tandroid:minHeight=\"?listPreferredItemHeightSmall\"\n\t\t\t\t\tandroid:paddingHorizontal=\"8dp\" />\n\n\t\t\t</com.google.android.material.card.MaterialCardView>\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_grouping\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"?android:listPreferredItemHeightSmall\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\tandroid:paddingStart=\"?android:listPreferredItemPaddingStart\"\n\t\t\t\tandroid:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/group\"\n\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceListItem\"\n\t\t\t\tandroid:textColor=\"?colorOnSurfaceVariant\"\n\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\tapp:drawableStartCompat=\"@drawable/ic_list_group\"\n\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t</LinearLayout>\n\t</androidx.core.widget.NestedScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_reader_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n        android:id=\"@+id/headerBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/options\" />\n\n    <androidx.core.widget.NestedScrollView\n        android:id=\"@+id/scrollView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:clipToPadding=\"false\"\n        android:scrollIndicators=\"top\">\n\n        <LinearLayout\n            android:id=\"@+id/layout_main\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"@dimen/margin_normal\">\n\n            <org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n                android:id=\"@+id/button_save_page\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/save_page\"\n                app:drawableStartCompat=\"@drawable/ic_save\" />\n\n            <org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n                android:id=\"@+id/button_bookmark\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/bookmark_add\"\n                app:drawableStartCompat=\"@drawable/ic_bookmark\" />\n\n            <org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n                android:id=\"@+id/button_image_server\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:drawableEnd=\"@drawable/ic_expand_more_22px\"\n                android:text=\"@string/image_server\"\n                android:visibility=\"gone\"\n                app:drawableStartCompat=\"@drawable/ic_images\"\n                tools:visibility=\"visible\" />\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginHorizontal=\"@dimen/margin_normal\"\n                android:layout_marginTop=\"@dimen/margin_normal\"\n                android:text=\"@string/read_mode\"\n                android:textAppearance=\"?textAppearanceTitleSmall\" />\n\n            <com.google.android.material.button.MaterialButtonToggleGroup\n                android:id=\"@+id/checkableGroup\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginHorizontal=\"@dimen/margin_normal\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                android:baselineAligned=\"false\"\n                android:orientation=\"horizontal\"\n                app:selectionRequired=\"true\"\n                app:singleSelection=\"true\">\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/button_standard\"\n                    style=\"@style/Widget.Kotatsu.ToggleButton.Vertical\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/standard\"\n                    app:icon=\"@drawable/ic_reader_ltr\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/button_reversed\"\n                    style=\"@style/Widget.Kotatsu.ToggleButton.Vertical\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/right_to_left\"\n                    app:icon=\"@drawable/ic_reader_rtl\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/button_vertical\"\n                    style=\"@style/Widget.Kotatsu.ToggleButton.Vertical\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/vertical\"\n                    app:icon=\"@drawable/ic_reader_vertical\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/button_webtoon\"\n                    style=\"@style/Widget.Kotatsu.ToggleButton.Vertical\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/webtoon\"\n                    app:icon=\"@drawable/ic_script\" />\n\n            </com.google.android.material.button.MaterialButtonToggleGroup>\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginHorizontal=\"@dimen/margin_normal\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                android:text=\"@string/reader_mode_hint\"\n                android:textAppearance=\"?attr/textAppearanceBodySmall\" />\n\n            <com.google.android.material.materialswitch.MaterialSwitch\n                android:id=\"@+id/switch_double_reader\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_normal\"\n                android:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n                android:minHeight=\"?android:listPreferredItemHeightSmall\"\n                android:paddingStart=\"?android:listPreferredItemPaddingStart\"\n                android:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n                android:text=\"@string/use_two_pages_landscape\"\n                android:textAppearance=\"?textAppearanceListItem\"\n                android:textColor=\"?colorOnSurfaceVariant\"\n                app:drawableStartCompat=\"@drawable/ic_split_horizontal\" />\n\n            <com.google.android.material.materialswitch.MaterialSwitch\n                android:id=\"@+id/switch_double_foldable\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                android:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n                android:minHeight=\"?android:listPreferredItemHeightSmall\"\n                android:paddingStart=\"?android:listPreferredItemPaddingStart\"\n                android:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n                android:text=\"@string/auto_double_foldable\"\n                android:textAppearance=\"@style/TextAppearance.Kotatsu.GridTitle\"\n                android:textColor=\"?colorOnSurfaceVariant\"\n                android:visibility=\"gone\"\n                tools:visibility=\"visible\" />\n\n            <TextView\n                android:id=\"@+id/text_double_sensitivity\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginHorizontal=\"@dimen/margin_normal\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                android:text=\"@string/two_page_scroll_sensitivity\"\n                android:textAppearance=\"@style/TextAppearance.Kotatsu.GridTitle\" />\n\n            <com.google.android.material.slider.Slider\n                android:id=\"@+id/slider_double_sensitivity\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginHorizontal=\"@dimen/margin_small\"\n                android:layout_marginTop=\"@dimen/margin_small\"\n                android:valueFrom=\"0\"\n                android:valueTo=\"100\"\n                app:labelBehavior=\"floating\"\n                tools:value=\"50\" />\n\n            <org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n                android:id=\"@+id/button_screen_rotate\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/rotate_screen\"\n                android:visibility=\"gone\"\n                app:drawableStartCompat=\"@drawable/ic_screen_rotation\"\n                tools:visibility=\"visible\" />\n\n            <com.google.android.material.materialswitch.MaterialSwitch\n                android:id=\"@+id/switch_screen_lock_rotation\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:drawablePadding=\"?android:listPreferredItemPaddingStart\"\n                android:ellipsize=\"end\"\n                android:minHeight=\"?android:listPreferredItemHeightSmall\"\n                android:paddingStart=\"?android:listPreferredItemPaddingStart\"\n                android:paddingEnd=\"?android:listPreferredItemPaddingEnd\"\n                android:singleLine=\"true\"\n                android:text=\"@string/lock_screen_rotation\"\n                android:textAppearance=\"?textAppearanceListItem\"\n                android:textColor=\"?colorOnSurfaceVariant\"\n                android:visibility=\"gone\"\n                app:drawableStartCompat=\"@drawable/ic_screen_rotation_lock\"\n                tools:visibility=\"visible\" />\n\n            <org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n                android:id=\"@+id/button_scroll_timer\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/automatic_scroll\"\n                app:drawableStartCompat=\"@drawable/ic_timer\" />\n\n            <org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n                android:id=\"@+id/button_color_filter\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/color_correction\"\n                app:drawableStartCompat=\"@drawable/ic_appearance\" />\n\n            <org.koitharu.kotatsu.core.ui.widgets.ListItemTextView\n                android:id=\"@+id/button_settings\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/settings\"\n                app:drawableStartCompat=\"@drawable/ic_settings\" />\n\n        </LinearLayout>\n\n    </androidx.core.widget.NestedScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_scrobbling.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:clipToPadding=\"false\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:paddingBottom=\"16dp\">\n\n\t\t<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n\t\t\tandroid:id=\"@+id/headerBar\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\tapp:title=\"@string/tracking\" />\n\n\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"0dp\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:clipToOutline=\"true\"\n\t\t\tandroid:foreground=\"?selectableItemBackground\"\n\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\tapp:layout_constraintDimensionRatio=\"H,13:18\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/barrier_rating\"\n\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/headerBar\"\n\t\t\tapp:layout_constraintWidth_max=\"146dp\"\n\t\t\tapp:layout_constraintWidth_min=\"40dp\"\n\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\t\ttools:background=\"@tools:sample/backgrounds/scenic\"\n\t\t\ttools:ignore=\"ContentDescription,UnusedAttribute\" />\n\n\t\t<androidx.constraintlayout.widget.Barrier\n\t\t\tandroid:id=\"@+id/barrier_rating\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:barrierDirection=\"left\"\n\t\t\tapp:constraint_referenced_ids=\"ratingBar\" />\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/imageView_logo\"\n\t\t\tandroid:layout_width=\"32dp\"\n\t\t\tandroid:layout_height=\"32dp\"\n\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\t\tandroid:background=\"@drawable/bg_badge_accent\"\n\t\t\tandroid:padding=\"4dp\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:tint=\"?attr/colorOnSecondary\"\n\t\t\ttools:ignore=\"ContentDescription\"\n\t\t\ttools:src=\"@drawable/ic_shikimori\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginEnd=\"6dp\"\n\t\t\tandroid:ellipsize=\"end\"\n\t\t\tandroid:maxLines=\"2\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceHeadlineSmall\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_menu\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/headerBar\"\n\t\t\ttools:text=\"@tools:sample/lorem[9]\" />\n\n\t\t<ImageButton\n\t\t\tandroid:id=\"@+id/button_menu\"\n\t\t\tstyle=\"?android:attr/actionOverflowButtonStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"-6dp\"\n\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\tandroid:contentDescription=\"@string/open_in_browser\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/headerBar\"\n\t\t\tapp:tint=\"?android:colorControlNormal\" />\n\n\t\t<RatingBar\n\t\t\tandroid:id=\"@+id/ratingBar\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"6dp\"\n\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\tandroid:numStars=\"5\"\n\t\t\tandroid:stepSize=\"0.5\"\n\t\t\tapp:layout_constrainedWidth=\"true\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\t\tapp:layout_constraintVertical_bias=\"0\"\n\t\t\ttools:rating=\"3.5\" />\n\n\t\t<Spinner\n\t\t\tandroid:id=\"@+id/spinner_status\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"6dp\"\n\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\tandroid:entries=\"@array/scrobbling_statuses\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/ratingBar\" />\n\n\t\t<androidx.constraintlayout.widget.Barrier\n\t\t\tandroid:id=\"@+id/barrier_header\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:barrierDirection=\"bottom\"\n\t\t\tapp:barrierMargin=\"8dp\"\n\t\t\tapp:constraint_referenced_ids=\"imageView_cover,spinner_status\" />\n\n\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\tandroid:lineSpacingMultiplier=\"1.2\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/barrier_header\"\n\t\t\ttools:ignore=\"UnusedAttribute\"\n\t\t\ttools:text=\"@tools:sample/lorem/random[250]\" />\n\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_scrobbling_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n\t\tandroid:id=\"@+id/headerBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:title=\"@string/tracking\" />\n\n\t<com.google.android.material.appbar.MaterialToolbar\n\t\tandroid:id=\"@+id/toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:contentInsetStart=\"0dp\"\n\t\ttools:menu=\"@menu/opt_search\">\n\n\t\t<RelativeLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t\t<com.google.android.material.tabs.TabLayout\n\t\t\t\tandroid:id=\"@+id/tabs\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_alignParentStart=\"true\"\n\t\t\t\tandroid:layout_centerVertical=\"true\"\n\t\t\t\tandroid:layout_toStartOf=\"@id/button_done\"\n\t\t\t\tandroid:background=\"@android:color/transparent\"\n\t\t\t\tandroid:clipToPadding=\"false\"\n\t\t\t\tandroid:paddingStart=\"@dimen/list_spacing\"\n\t\t\t\tandroid:scrollIndicators=\"start|end\"\n\t\t\t\tapp:tabGravity=\"start\"\n\t\t\t\tapp:tabMode=\"scrollable\"\n\t\t\t\ttools:ignore=\"RtlSymmetry,UnusedAttribute\" />\n\n\t\t\t<com.google.android.material.button.MaterialButton\n\t\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\t\tstyle=\"?materialIconButtonFilledStyle\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_alignParentEnd=\"true\"\n\t\t\t\tandroid:layout_centerVertical=\"true\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\t\tapp:icon=\"@drawable/ic_check\" />\n\n\t\t</RelativeLayout>\n\n\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:padding=\"@dimen/list_spacing_normal\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\ttools:listitem=\"@layout/item_manga_list\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_stats_manga.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\"\n\tandroid:paddingBottom=\"@dimen/screen_padding\">\n\n\t<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n\t\tandroid:id=\"@+id/headerBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:title=\"@string/reading_stats\" />\n\n\t<ScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:scrollIndicators=\"top\">\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\">\n\n\t\t\t<LinearLayout\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:gravity=\"center_vertical\"\n\t\t\t\tandroid:orientation=\"horizontal\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/screen_padding\">\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem[4]\" />\n\n\t\t\t\t<ImageButton\n\t\t\t\t\tandroid:id=\"@+id/button_open\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\t\t\t\tandroid:contentDescription=\"@string/details\"\n\t\t\t\t\tandroid:minWidth=\"?minTouchTargetSize\"\n\t\t\t\t\tandroid:minHeight=\"?minTouchTargetSize\"\n\t\t\t\t\tandroid:tooltipText=\"@string/details\"\n\t\t\t\t\tapp:srcCompat=\"@drawable/ic_open_external\" />\n\n\t\t\t</LinearLayout>\n\n\t\t\t<org.koitharu.kotatsu.stats.ui.views.BarChartView\n\t\t\t\tandroid:id=\"@+id/chartView\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"240dp\"\n\t\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/screen_padding\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_start\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/screen_padding\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceLabelSmall\"\n\t\t\t\ttools:text=\"Week ago\" />\n\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_pages\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"12dp\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/screen_padding\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\t\t\ttools:text=\"Total pages read: 250\" />\n\n\t\t</LinearLayout>\n\t</ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_tags.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n\t\tandroid:id=\"@+id/headerBar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:title=\"@string/filter\" />\n\n\t<com.google.android.material.textfield.TextInputLayout\n\t\tandroid:id=\"@+id/text_input_layout\"\n\t\tstyle=\"?textInputFilledDenseStyle\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"16dp\"\n\t\tandroid:layout_marginBottom=\"4dp\"\n\t\tandroid:hint=\"@string/genres_search_hint\"\n\t\tapp:endIconDrawable=\"@drawable/abc_ic_clear_material\"\n\t\tapp:endIconMode=\"clear_text\"\n\t\tapp:layout_constraintBottom_toTopOf=\"@id/recyclerView\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/headerBar\"\n\t\tapp:startIconDrawable=\"?android:actionModeWebSearchDrawable\">\n\n\t\t<com.google.android.material.textfield.TextInputEditText\n\t\t\tandroid:id=\"@+id/edit_search\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:imeOptions=\"actionSearch|flagNoFullscreen\"\n\t\t\tandroid:importantForAutofill=\"no\"\n\t\t\tandroid:inputType=\"text\" />\n\n\t</com.google.android.material.textfield.TextInputLayout>\n\n\t<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:orientation=\"vertical\"\n\t\tapp:bubbleSize=\"normal\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/text_input_layout\"\n\t\tapp:scrollerOffset=\"8dp\"\n\t\ttools:listitem=\"@layout/item_checkable_new\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/sheet_welcome.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:orientation=\"vertical\">\n\n\t<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar\n\t\tandroid:id=\"@+id/headerBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:title=\"@string/welcome\" />\n\n\t<androidx.core.widget.NestedScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:scrollIndicators=\"top\">\n\n\t\t<LinearLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:paddingBottom=\"@dimen/margin_normal\">\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_welcome_title\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:drawablePadding=\"16dp\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/welcome\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleLarge\"\n\t\t\t\tapp:drawableStartCompat=\"@drawable/ic_welcome\" />\n\n\t\t\t<HorizontalScrollView\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\t\tandroid:scrollbars=\"none\">\n\n\t\t\t\t<com.google.android.material.chip.ChipGroup\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\t\t\t\tapp:singleLine=\"true\">\n\n\t\t\t\t\t<com.google.android.material.chip.Chip\n\t\t\t\t\t\tandroid:id=\"@+id/chip_backup\"\n\t\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Assist\"\n\t\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\t\tandroid:text=\"@string/restore_backup\"\n\t\t\t\t\t\tapp:chipIcon=\"@drawable/ic_backup_restore\" />\n\n\t\t\t\t\t<com.google.android.material.chip.Chip\n\t\t\t\t\t\tandroid:id=\"@+id/chip_sync\"\n\t\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Assist\"\n\t\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\t\tandroid:text=\"@string/sync_auth\"\n\t\t\t\t\t\tapp:chipIcon=\"@drawable/ic_sync\" />\n\n\t\t\t\t\t<com.google.android.material.chip.Chip\n\t\t\t\t\t\tandroid:id=\"@+id/chip_directories\"\n\t\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Assist\"\n\t\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\t\tandroid:text=\"@string/local_manga_directories\"\n\t\t\t\t\t\tapp:chipIcon=\"@drawable/ic_storage\" />\n\n\t\t\t\t</com.google.android.material.chip.ChipGroup>\n\n\t\t\t</HorizontalScrollView>\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_hint\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/welcome_text\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_locales_title\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/languages\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\t\t\tandroid:id=\"@+id/chips_locales\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tapp:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_type_title\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\tandroid:text=\"@string/type\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\" />\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\t\t\tandroid:id=\"@+id/chips_type\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tapp:chipStyle=\"@style/Widget.Kotatsu.Chip.Filter\" />\n\n\t\t</LinearLayout>\n\t</androidx.core.widget.NestedScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/view_cover_stack.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:parentTag=\"org.koitharu.kotatsu.core.ui.widgets.StackLayout\">\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover3\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"64dp\"\n\t\tandroid:layout_marginStart=\"24dp\"\n\t\tandroid:layout_marginBottom=\"12dp\"\n\t\tandroid:background=\"?attr/colorSecondaryContainer\"\n\t\tandroid:backgroundTintMode=\"src_atop\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:fallbackDrawable=\"@android:color/transparent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\tapp:tintMode=\"src_atop\"\n\t\ttools:backgroundTint=\"#99FFFFFF\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\"\n\t\ttools:tint=\"#99FFFFFF\" />\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover2\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"64dp\"\n\t\tandroid:layout_marginStart=\"12dp\"\n\t\tandroid:background=\"?attr/colorSecondaryContainer\"\n\t\tandroid:backgroundTintMode=\"src_atop\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:fallbackDrawable=\"@android:color/transparent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\tapp:tintMode=\"src_atop\"\n\t\ttools:backgroundTint=\"#4DFFFFFF\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\"\n\t\ttools:tint=\"#4DFFFFFF\" />\n\n\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\tandroid:id=\"@+id/imageView_cover1\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"64dp\"\n\t\tandroid:layout_marginTop=\"12dp\"\n\t\tandroid:background=\"?attr/colorSecondaryContainer\"\n\t\tandroid:backgroundTintMode=\"src_atop\"\n\t\tandroid:scaleType=\"centerCrop\"\n\t\tapp:fallbackDrawable=\"@android:color/transparent\"\n\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small\"\n\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/view_dialog_autocomplete.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingHorizontal=\"@dimen/screen_padding\"\n    android:paddingTop=\"@dimen/margin_small\">\n\n    <AutoCompleteTextView\n        android:id=\"@+id/autoCompleteTextView\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_toStartOf=\"@id/dropdown\"\n        tools:ignore=\"LabelFor\" />\n\n    <ImageButton\n        android:id=\"@+id/dropdown\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_gravity=\"center_vertical|end\"\n        android:background=\"?selectableItemBackgroundBorderless\"\n        android:paddingBottom=\"2dp\"\n        android:scaleType=\"center\"\n        android:src=\"@drawable/ic_expand_more\"\n        tools:ignore=\"ContentDescription\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/view_filter_field.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:parentTag=\"android.widget.RelativeLayout\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_alignWithParentIfMissing=\"true\"\n\t\tandroid:layout_alignParentStart=\"true\"\n\t\tandroid:layout_alignParentTop=\"true\"\n\t\tandroid:layout_toStartOf=\"@id/button_more\"\n\t\tandroid:gravity=\"center_vertical|start\"\n\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\ttools:text=\"@string/genres\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_more\"\n\t\tstyle=\"@style/Widget.Kotatsu.Button.More.Small\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_alignBaseline=\"@id/textView_title\"\n\t\tandroid:layout_alignParentEnd=\"true\"\n\t\tandroid:text=\"@string/more\"\n\t\tandroid:visibility=\"invisible\"\n\t\ttools:visibility=\"visible\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_value\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_alignBaseline=\"@id/textView_title\"\n\t\tandroid:layout_alignParentEnd=\"true\"\n\t\tandroid:paddingEnd=\"@dimen/margin_small\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/view_scroll_timer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:background=\"@drawable/bg_card\"\n\ttools:layout_height=\"wrap_content\"\n\ttools:layout_margin=\"@dimen/screen_padding\"\n\ttools:layout_width=\"match_parent\"\n\ttools:paddingBottom=\"@dimen/margin_normal\"\n\ttools:parentTag=\"androidx.constraintlayout.widget.ConstraintLayout\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:minHeight=\"?minTouchTargetSize\"\n\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\tandroid:text=\"@string/automatic_scroll\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_close\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<ImageButton\n\t\tandroid:id=\"@+id/button_close\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:contentDescription=\"@string/close\"\n\t\tandroid:minWidth=\"?minTouchTargetSize\"\n\t\tandroid:minHeight=\"?minTouchTargetSize\"\n\t\tandroid:src=\"?actionModeCloseDrawable\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\tandroid:id=\"@+id/switch_scroll_timer\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\tandroid:ellipsize=\"end\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/enable\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/label_timer\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\tandroid:text=\"@string/speed\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/slider_timer\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"@id/slider_timer\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.CubicSlider\n\t\tandroid:id=\"@+id/slider_timer\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\tandroid:contentDescription=\"@string/speed\"\n\t\tandroid:labelFor=\"@id/switch_scroll_timer\"\n\t\tandroid:valueFrom=\"0.000001\"\n\t\tandroid:valueTo=\"0.97\"\n\t\tapp:labelBehavior=\"floating\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/label_timer\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/switch_scroll_timer\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_description\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginHorizontal=\"@dimen/margin_normal\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:textAppearance=\"?textAppearanceBodySmall\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/slider_timer\"\n\t\ttools:text=\"@string/page_switch_timer\" />\n\n\t<CheckedTextView\n\t\tandroid:id=\"@+id/button_fab\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\tandroid:background=\"?selectableItemBackground\"\n\t\tandroid:drawableEnd=\"?android:listChoiceIndicatorMultiple\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:paddingHorizontal=\"@dimen/margin_normal\"\n\t\tandroid:paddingVertical=\"4dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:text=\"@string/show_floating_control_button\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceTitleSmall\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_description\" />\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/view_tip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:padding=\"@dimen/margin_normal\"\n\ttools:background=\"@drawable/bg_appwidget_card\"\n\ttools:backgroundTint=\"?colorBackgroundFloating\"\n\ttools:layout_height=\"wrap_content\"\n\ttools:layout_margin=\"16dp\"\n\ttools:layout_width=\"match_parent\"\n\ttools:orientation=\"vertical\"\n\ttools:parentTag=\"android.widget.LinearLayout\">\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:drawablePadding=\"10dp\"\n\t\tandroid:gravity=\"center_vertical\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\ttools:drawableStartCompat=\"@drawable/ic_app_update\"\n\t\ttools:text=\"Уведомления отключены\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_body\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:textAppearance=\"?textAppearanceBodyMedium\"\n\t\ttools:text=\"Включите их, чтобы ничего не пропускать. Откройте настройки системы и разрешите их отправку.\" />\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/layout_buttons\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginTop=\"8dp\"\n\t\tandroid:layout_weight=\"2\"\n\t\tandroid:divider=\"@drawable/divider_transparent\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tandroid:showDividers=\"middle\">\n\n\t\t<com.google.android.material.button.MaterialButton\n\t\t\tandroid:id=\"@+id/button_primary\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_weight=\"1\"\n\t\t\tandroid:paddingHorizontal=\"8dp\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\ttools:text=\"Открыть настройки\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_secondary\"\n\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\tandroid:layout_width=\"0dp\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_weight=\"1\"\n\t\t\tandroid:paddingHorizontal=\"8dp\"\n\t\t\tandroid:singleLine=\"true\"\n\t\t\ttools:text=\"Отмена\" />\n\n\t</LinearLayout>\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/view_two_lines_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:layout_height=\"wrap_content\"\n\ttools:orientation=\"horizontal\"\n\ttools:parentTag=\"android.widget.LinearLayout\">\n\n\t<org.koitharu.kotatsu.core.ui.widgets.CheckableImageView\n\t\tandroid:id=\"@+id/icon\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center_vertical\"\n\t\ttools:src=\"@drawable/ic_folder_file\" />\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/layout_text\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center_vertical\"\n\t\tandroid:layout_weight=\"1\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:paddingVertical=\"6dp\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\ttools:text=\"@tools:sample/cities\"\n\t\t\ttools:textAppearance=\"?textAppearanceListItem\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/subtitle\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"2dp\"\n\t\t\ttools:text=\"@tools:sample/lorem[12]\"\n\t\t\ttools:textAppearance=\"?textAppearanceBodySmall\" />\n\n\t</LinearLayout>\n\n\t<ImageView\n\t\tandroid:id=\"@+id/button\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center_vertical\"\n\t\tandroid:background=\"?selectableItemBackgroundBorderless\"\n\t\tandroid:minWidth=\"?minTouchTargetSize\"\n\t\tandroid:minHeight=\"?minTouchTargetSize\"\n\t\tandroid:scaleType=\"center\"\n\t\ttools:src=\"?expandCollapseIndicator\" />\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/view_zoom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\ttools:orientation=\"vertical\"\n\ttools:parentTag=\"com.google.android.material.button.MaterialButtonGroup\">\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_zoom_in\"\n\t\tstyle=\"?materialIconButtonOutlinedStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:contentDescription=\"@string/zoom_in\"\n\t\tandroid:tooltipText=\"@string/zoom_in\"\n\t\tapp:backgroundTint=\"@color/bg_floating_button\"\n\t\tapp:icon=\"@drawable/ic_zoom_in\" />\n\n\t<com.google.android.material.button.MaterialButton\n\t\tandroid:id=\"@+id/button_zoom_out\"\n\t\tstyle=\"?materialIconButtonOutlinedStyle\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:contentDescription=\"@string/zoom_out\"\n\t\tandroid:tooltipText=\"@string/zoom_out\"\n\t\tapp:backgroundTint=\"@color/bg_floating_button\"\n\t\tapp:icon=\"@drawable/ic_zoom_out\" />\n\n</merge>\n"
  },
  {
    "path": "app/src/main/res/layout/widget_recent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:clipToOutline=\"true\"\n\tandroid:outlineProvider=\"background\"\n\tandroid:padding=\"4dp\"\n\tandroid:theme=\"@style/Theme.Kotatsu.AppWidgetContainer\"\n\ttools:background=\"@drawable/bg_appwidget_root\">\n\n\t<StackView\n\t\tandroid:id=\"@+id/stackView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\ttools:listitem=\"@layout/item_recent\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_holder\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:text=\"@string/history_is_empty\"\n\t\tandroid:textColor=\"?android:textColorPrimary\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/widget_shelf.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/widget_root\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:clipToOutline=\"true\"\n\tandroid:outlineProvider=\"background\"\n\tandroid:theme=\"@style/Theme.Kotatsu.AppWidgetContainer\"\n\ttools:background=\"?android:attr/colorBackground\">\n\n\t<GridView\n\t\tandroid:id=\"@+id/gridView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:columnWidth=\"92dp\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:numColumns=\"auto_fit\"\n\t\tandroid:orientation=\"horizontal\"\n\t\tandroid:padding=\"4dp\"\n\t\tandroid:scrollbarStyle=\"insideOverlay\"\n\t\ttools:listitem=\"@layout/item_shelf\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_holder\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:gravity=\"center\"\n\t\tandroid:text=\"@string/you_have_not_favourites_yet\"\n\t\tandroid:textColor=\"?android:attr/textColorPrimary\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-land/item_empty_state.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:gravity=\"center\"\n\tandroid:orientation=\"horizontal\"\n\tandroid:paddingHorizontal=\"32dp\">\n\n\t<org.koitharu.kotatsu.core.image.CoilImageView\n\t\tandroid:id=\"@+id/icon\"\n\t\tandroid:layout_width=\"192dp\"\n\t\tandroid:layout_height=\"192dp\"\n\t\tandroid:contentDescription=\"@null\"\n\t\tandroid:scaleType=\"fitCenter\"\n\t\ttools:src=\"@drawable/ic_empty_favourites\" />\n\n\t<LinearLayout\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:orientation=\"vertical\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textPrimary\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleLarge\"\n\t\t\ttools:text=\"@tools:sample/lorem[3]\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textSecondary\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\tandroid:maxWidth=\"320dp\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\ttools:text=\"@tools:sample/lorem[15]\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_retry\"\n\t\t\tstyle=\"?materialButtonTonalStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\tandroid:visibility=\"gone\"\n\t\t\ttools:text=\"@string/try_again\"\n\t\t\ttools:visibility=\"visible\" />\n\n\t</LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_color_filter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.MaterialToolbar\n\t\tandroid:id=\"@+id/toolbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\ttools:navigationIcon=\"@drawable/abc_ic_clear_material\"\n\t\ttools:title=\"@string/color_correction\">\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_done\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"end\"\n\t\t\tandroid:layout_marginEnd=\"@dimen/toolbar_button_margin\"\n\t\t\tandroid:text=\"@string/done\" />\n\n\t\t<Button\n\t\t\tandroid:id=\"@+id/button_reset\"\n\t\t\tstyle=\"?materialButtonOutlinedStyle\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"end\"\n\t\t\tandroid:layout_marginHorizontal=\"@dimen/toolbar_button_margin\"\n\t\t\tandroid:text=\"@string/reset\" />\n\n\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t<ScrollView\n\t\tandroid:id=\"@+id/scrollView\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:overScrollMode=\"ifContentScrolls\">\n\n\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:orientation=\"vertical\"\n\t\t\tandroid:padding=\"@dimen/margin_normal\">\n\n\t\t\t<androidx.constraintlayout.widget.Guideline\n\t\t\t\tandroid:id=\"@+id/guideline_vertical\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:orientation=\"vertical\"\n\t\t\t\tapp:layout_constraintGuide_percent=\"0.5\" />\n\n\t\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\t\tandroid:id=\"@+id/imageView_before\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"0dp\"\n\t\t\t\tandroid:padding=\"2dp\"\n\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\tapp:allowRgb565=\"false\"\n\t\t\t\tapp:decodeRegion=\"true\"\n\t\t\t\tapp:layout_constraintDimensionRatio=\"W,14:9\"\n\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/imageView_arrow\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\tapp:shapeAppearance=\"?shapeAppearanceCornerLarge\"\n\t\t\t\tapp:strokeColor=\"?colorOutline\"\n\t\t\t\tapp:strokeWidth=\"1dp\"\n\t\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\t\t\tandroid:id=\"@+id/progress_before\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:indeterminate=\"true\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_before\" />\n\n\t\t\t<ImageView\n\t\t\t\tandroid:id=\"@+id/imageView_arrow\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:contentDescription=\"@null\"\n\t\t\t\tandroid:padding=\"@dimen/margin_normal\"\n\t\t\t\tandroid:src=\"@drawable/ic_arrow_forward\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_before\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_before\" />\n\n\t\t\t<com.google.android.material.imageview.ShapeableImageView\n\t\t\t\tandroid:id=\"@+id/imageView_after\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"0dp\"\n\t\t\t\tandroid:padding=\"2dp\"\n\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\tapp:layout_constraintDimensionRatio=\"W,14:9\"\n\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_arrow\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\tapp:shapeAppearance=\"?shapeAppearanceCornerLarge\"\n\t\t\t\tapp:strokeColor=\"?colorOutline\"\n\t\t\t\tapp:strokeWidth=\"1dp\"\n\t\t\t\ttools:src=\"@tools:sample/backgrounds/scenic\" />\n\n\t\t\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\t\t\tandroid:id=\"@+id/progress_after\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:indeterminate=\"true\"\n\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_after\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_after\"\n\t\t\t\tapp:layout_constraintStart_toStartOf=\"@id/imageView_after\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"@id/imageView_after\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_invert\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/invert_colors\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_grayscale\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\tandroid:text=\"@string/grayscale\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/switch_invert\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_brightness\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\tandroid:text=\"@string/brightness\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/switch_grayscale\" />\n\n\t\t\t<com.google.android.material.slider.Slider\n\t\t\t\tandroid:id=\"@+id/slider_brightness\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:labelFor=\"@id/textView_brightness\"\n\t\t\t\tandroid:value=\"0.0\"\n\t\t\t\tandroid:valueFrom=\"-1.0\"\n\t\t\t\tandroid:valueTo=\"1.0\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_brightness\" />\n\n\t\t\t<TextView\n\t\t\t\tandroid:id=\"@+id/textView_contrast\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:text=\"@string/contrast\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/slider_brightness\" />\n\n\t\t\t<com.google.android.material.slider.Slider\n\t\t\t\tandroid:id=\"@+id/slider_contrast\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\tandroid:value=\"0.0\"\n\t\t\t\tandroid:valueFrom=\"-1.0\"\n\t\t\t\tandroid:valueTo=\"1.0\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_contrast\" />\n\n\t\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\t\tandroid:id=\"@+id/switch_book\"\n\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\t\tandroid:text=\"@string/book_effect\"\n\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleMedium\"\n\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_vertical\"\n\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/slider_contrast\" />\n\n\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n\t</ScrollView>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_container.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"true\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\ttools:title=\"Title\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\ttools:context=\".details.ui.DetailsActivity\"\n\ttools:ignore=\"ViewBindingType\"\n\ttools:viewBindingType=\"android.view.ViewGroup\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:elevation=\"0dp\"\n\t\tandroid:fitsSystemWindows=\"true\"\n\t\tapp:elevation=\"0dp\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:layout_constraintWidth_max=\"420dp\"\n\t\tapp:layout_constraintWidth_percent=\"0.5\"\n\t\tapp:liftOnScroll=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n\t\tandroid:id=\"@+id/swipeRefreshLayout\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/appbar\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\">\n\n\t\t<androidx.core.widget.NestedScrollView\n\t\t\tandroid:id=\"@+id/scrollView\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:scrollIndicators=\"top\"\n\t\t\tandroid:scrollbars=\"vertical\">\n\n\t\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\tandroid:paddingBottom=\"@dimen/margin_normal\">\n\n\t\t\t\t<org.koitharu.kotatsu.image.ui.CoverImageView\n\t\t\t\t\tandroid:id=\"@+id/imageView_cover\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"0dp\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\t\tandroid:background=\"?colorSecondaryContainer\"\n\t\t\t\t\tandroid:clipToOutline=\"true\"\n\t\t\t\t\tandroid:foreground=\"?selectableItemBackground\"\n\t\t\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\t\t\tapp:allowRgb565=\"false\"\n\t\t\t\t\tapp:aspectRationHeight=\"0\"\n\t\t\t\t\tapp:aspectRationWidth=\"0\"\n\t\t\t\t\tapp:layout_constraintDimensionRatio=\"H,13:18\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintWidth_percent=\"0.3\"\n\t\t\t\t\tapp:shapeAppearanceOverlay=\"@style/ShapeAppearanceOverlay.Kotatsu.Cover\"\n\t\t\t\t\tapp:useExistingDrawable=\"true\"\n\t\t\t\t\ttools:background=\"@tools:sample/backgrounds/scenic[5]\"\n\t\t\t\t\ttools:ignore=\"ContentDescription,UnusedAttribute\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_nsfw_16\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.TextView.Badge\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\t\t\t\tandroid:backgroundTint=\"@color/nsfw_16\"\n\t\t\t\t\tandroid:text=\"@string/nsfw_16\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_nsfw_18\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.TextView.Badge\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_margin=\"@dimen/card_indicator_offset\"\n\t\t\t\t\tandroid:backgroundTint=\"@color/nsfw_18\"\n\t\t\t\t\tandroid:text=\"@string/nsfw\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"@id/imageView_cover\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"16dp\"\n\t\t\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\t\tandroid:maxLines=\"5\"\n\t\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceHeadlineSmall\"\n\t\t\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\t\t\tandroid:id=\"@+id/textView_subtitle\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"4dp\"\n\t\t\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\t\tandroid:maxLines=\"3\"\n\t\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_title\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem[12]\" />\n\n\t\t\t\t<com.google.android.material.chip.Chip\n\t\t\t\t\tandroid:id=\"@+id/chip_favorite\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Dropdown\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginTop=\"8dp\"\n\t\t\t\t\tapp:chipIcon=\"@drawable/ic_heart_outline\"\n\t\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/imageView_cover\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_subtitle\"\n\t\t\t\t\ttools:text=\"@string/add_to_favourites\" />\n\n\t\t\t\t<androidx.constraintlayout.widget.Barrier\n\t\t\t\t\tandroid:id=\"@+id/barrier_header\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tapp:barrierDirection=\"bottom\"\n\t\t\t\t\tapp:barrierMargin=\"@dimen/margin_normal\"\n\t\t\t\t\tapp:constraint_referenced_ids=\"imageView_cover,chip_favorite\" />\n\n\t\t\t\t<include layout=\"@layout/layout_details_table\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_description_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:gravity=\"center_vertical|start\"\n\t\t\t\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:text=\"@string/description\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_description_more\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_progress_label\" />\n\n\t\t\t\t<Button\n\t\t\t\t\tandroid:id=\"@+id/button_description_more\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\t\t\tandroid:text=\"@string/more\"\n\t\t\t\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_description_title\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.SelectableTextView\n\t\t\t\t\tandroid:id=\"@+id/textView_description\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginEnd=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:ellipsize=\"end\"\n\t\t\t\t\tandroid:lineSpacingMultiplier=\"1.2\"\n\t\t\t\t\tandroid:maxLines=\"@integer/details_description_lines\"\n\t\t\t\t\tandroid:textAppearance=\"?attr/textAppearanceBodyMedium\"\n\t\t\t\t\tandroid:textIsSelectable=\"true\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_description_title\"\n\t\t\t\t\ttools:ignore=\"UnusedAttribute\"\n\t\t\t\t\ttools:text=\"@tools:sample/lorem/random\" />\n\n\t\t\t\t<org.koitharu.kotatsu.core.ui.widgets.ChipsView\n\t\t\t\t\tandroid:id=\"@+id/chips_tags\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginHorizontal=\"@dimen/screen_padding\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\t\tapp:chipSpacingHorizontal=\"6dp\"\n\t\t\t\t\tapp:chipSpacingVertical=\"6dp\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@+id/textView_description\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_scrobbling_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_normal\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:gravity=\"center_vertical|start\"\n\t\t\t\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:text=\"@string/tracking\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_scrobbling_more\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/chips_tags\" />\n\n\t\t\t\t<Button\n\t\t\t\t\tandroid:id=\"@+id/button_scrobbling_more\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\t\t\tandroid:text=\"@string/manage\"\n\t\t\t\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_scrobbling_title\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n\t\t\t\t<androidx.recyclerview.widget.RecyclerView\n\t\t\t\t\tandroid:id=\"@+id/recyclerView_scrobbling\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"16dp\"\n\t\t\t\t\tandroid:layout_marginEnd=\"16dp\"\n\t\t\t\t\tandroid:layout_marginBottom=\"12dp\"\n\t\t\t\t\tandroid:nestedScrollingEnabled=\"false\"\n\t\t\t\t\tandroid:orientation=\"vertical\"\n\t\t\t\t\tandroid:overScrollMode=\"never\"\n\t\t\t\t\tandroid:scrollbars=\"none\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_scrobbling_title\"\n\t\t\t\t\ttools:itemCount=\"2\"\n\t\t\t\t\ttools:listitem=\"@layout/item_scrobbling_info\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\t\t\t\tandroid:id=\"@+id/progressBar\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:indeterminate=\"true\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:hideAnimationBehavior=\"outward\"\n\t\t\t\t\tapp:layout_constraintBottom_toTopOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\t\tapp:showAnimationBehavior=\"inward\"\n\t\t\t\t\tapp:trackCornerRadius=\"0dp\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t\t<androidx.constraintlayout.widget.Group\n\t\t\t\t\tandroid:id=\"@+id/group_scrobbling\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:constraint_referenced_ids=\"recyclerView_scrobbling,textView_scrobbling_title,button_scrobbling_more\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_related_title\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginStart=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_marginTop=\"@dimen/margin_small\"\n\t\t\t\t\tandroid:layout_weight=\"1\"\n\t\t\t\t\tandroid:gravity=\"center_vertical|start\"\n\t\t\t\t\tandroid:padding=\"@dimen/grid_spacing\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tandroid:text=\"@string/related_manga\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/button_related_more\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/recyclerView_scrobbling\" />\n\n\t\t\t\t<Button\n\t\t\t\t\tandroid:id=\"@+id/button_related_more\"\n\t\t\t\t\tstyle=\"@style/Widget.Kotatsu.Button.More\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\t\t\tandroid:text=\"@string/show_all\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:layout_constraintBaseline_toBaselineOf=\"@id/textView_related_title\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\n\t\t\t\t<androidx.recyclerview.widget.RecyclerView\n\t\t\t\t\tandroid:id=\"@+id/recyclerView_related\"\n\t\t\t\t\tandroid:layout_width=\"0dp\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_marginBottom=\"6dp\"\n\t\t\t\t\tandroid:clipToPadding=\"false\"\n\t\t\t\t\tandroid:nestedScrollingEnabled=\"false\"\n\t\t\t\t\tandroid:orientation=\"horizontal\"\n\t\t\t\t\tandroid:paddingStart=\"12dp\"\n\t\t\t\t\tandroid:paddingEnd=\"12dp\"\n\t\t\t\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_related_title\"\n\t\t\t\t\ttools:listitem=\"@layout/item_manga_grid\" />\n\n\t\t\t\t<androidx.constraintlayout.widget.Group\n\t\t\t\t\tandroid:id=\"@+id/group_related\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:visibility=\"gone\"\n\t\t\t\t\tapp:constraint_referenced_ids=\"recyclerView_related,textView_related_title,button_related_more\"\n\t\t\t\t\ttools:visibility=\"visible\" />\n\n\t\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\n\t\t</androidx.core.widget.NestedScrollView>\n\n\t</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n\t<com.google.android.material.card.MaterialCardView\n\t\tandroid:id=\"@+id/card_chapters\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginTop=\"@dimen/grid_spacing_outer\"\n\t\tandroid:layout_marginEnd=\"@dimen/side_card_offset\"\n\t\tandroid:layout_marginBottom=\"@dimen/side_card_offset\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/appbar\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t<androidx.fragment.app.FragmentContainerView\n\t\t\tandroid:id=\"@+id/container_side\"\n\t\t\tandroid:name=\"org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\" />\n\n\t</com.google.android.material.card.MaterialCardView>\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"horizontal\"\n\ttools:context=\".main.ui.MainActivity\">\n\n\t<com.google.android.material.navigationrail.NavigationRailView\n\t\tandroid:id=\"@+id/navRail\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tapp:elevation=\"1dp\"\n\t\tapp:headerLayout=\"@layout/navigation_rail_fab\"\n\t\tapp:labelVisibilityMode=\"labeled\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:paddingBottomSystemWindowInsets=\"false\"\n\t\tapp:paddingStartSystemWindowInsets=\"false\"\n\t\tapp:paddingTopSystemWindowInsets=\"false\"\n\t\tapp:scrollingEnabled=\"true\" />\n\n\t<androidx.coordinatorlayout.widget.CoordinatorLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<androidx.fragment.app.FragmentContainerView\n\t\t\tandroid:id=\"@id/container\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\t\ttools:layout=\"@layout/fragment_list\" />\n\n\t\t<com.google.android.material.appbar.AppBarLayout\n\t\t\tandroid:id=\"@+id/appbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:background=\"@null\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:elevation=\"0dp\"\n\t\t\tandroid:fitsSystemWindows=\"false\"\n\t\t\tandroid:stateListAnimator=\"@null\"\n\t\t\tapp:elevation=\"0dp\"\n\t\t\tapp:liftOnScroll=\"false\"\n\t\t\tapp:liftOnScrollColor=\"@null\">\n\n\t\t\t<org.koitharu.kotatsu.core.ui.widgets.WindowInsetHolder\n\t\t\t\tandroid:id=\"@+id/insetsHolder\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"top\"\n\t\t\t\tandroid:fitsSystemWindows=\"true\"\n\t\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways|snap\" />\n\n\t\t\t<FrameLayout\n\t\t\t\tandroid:id=\"@+id/layout_search\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tapp:layout_scrollFlags=\"scroll|enterAlways|snap\">\n\n\t\t\t\t<TextView\n\t\t\t\t\tandroid:id=\"@+id/textView_title\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_gravity=\"center_vertical|start\"\n\t\t\t\t\tandroid:paddingStart=\"@dimen/screen_padding\"\n\t\t\t\t\tandroid:textAppearance=\"?textAppearanceTitleLarge\"\n\t\t\t\t\ttools:ignore=\"RtlSymmetry\"\n\t\t\t\t\ttools:text=\"@string/history\" />\n\n\t\t\t\t<com.google.android.material.search.SearchBar\n\t\t\t\t\tandroid:id=\"@+id/search_bar\"\n\t\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\t\tandroid:layout_gravity=\"center_vertical|end\"\n\t\t\t\t\tandroid:hint=\"@string/search_manga\"\n\t\t\t\t\tapp:adaptiveMaxWidthEnabled=\"true\"\n\t\t\t\t\tapp:forceDefaultNavigationOnClickListener=\"true\" />\n\n\t\t\t</FrameLayout>\n\n\t\t</com.google.android.material.appbar.AppBarLayout>\n\n\t\t<com.google.android.material.search.SearchView\n\t\t\tandroid:id=\"@+id/search_view\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:layout_gravity=\"end\"\n\t\t\tandroid:hint=\"@string/search_hint\"\n\t\t\tapp:layout_anchor=\"@id/search_bar\">\n\n\t\t\t<androidx.recyclerview.widget.RecyclerView\n\t\t\t\tandroid:id=\"@+id/recyclerView_search\"\n\t\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\tandroid:clipToPadding=\"false\"\n\t\t\t\tandroid:orientation=\"vertical\"\n\t\t\t\tandroid:scrollbars=\"vertical\"\n\t\t\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n\t\t</com.google.android.material.search.SearchView>\n\n\t</androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_manga_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/container\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\ttools:title=\"Title\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/card_side\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\"\n\t\ttools:layout=\"@layout/fragment_list\" />\n\n\t<com.google.android.material.card.MaterialCardView\n\t\tandroid:id=\"@+id/card_side\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_marginStart=\"@dimen/side_card_offset\"\n\t\tandroid:layout_marginTop=\"@dimen/grid_spacing_outer_double\"\n\t\tandroid:layout_marginEnd=\"@dimen/side_card_offset\"\n\t\tandroid:layout_marginBottom=\"@dimen/side_card_offset\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/container\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:layout_constraintWidth_max=\"600dp\"\n\t\tapp:layout_constraintWidth_min=\"320dp\"\n\t\tapp:layout_constraintWidth_percent=\"0.4\">\n\n\t\t<androidx.fragment.app.FragmentContainerView\n\t\t\tandroid:id=\"@+id/container_side\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\ttools:layout=\"@layout/sheet_tags\" />\n\n\t</com.google.android.material.card.MaterialCardView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_reader.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/container\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\ttools:background=\"@color/grey\" />\n\n\t<org.koitharu.kotatsu.core.ui.widgets.ZoomControl\n\t\tandroid:id=\"@+id/zoomControl\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom|end\"\n\t\tandroid:layout_margin=\"16dp\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:spacing=\"2dp\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_dodgeInsetEdges=\"bottom\"\n\t\ttools:visibility=\"visible\" />\n\n\t<org.koitharu.kotatsu.reader.ui.ReaderInfoBarView\n\t\tandroid:id=\"@+id/infoBar\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"top\"\n\t\tandroid:padding=\"6dp\"\n\t\tandroid:textSize=\"12sp\"\n\t\tandroid:visibility=\"gone\"\n\t\ttools:visibility=\"visible\" />\n\n\t<org.koitharu.kotatsu.reader.ui.ScrollTimerControlView\n\t\tandroid:id=\"@+id/timerControl\"\n\t\tandroid:layout_width=\"320dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_margin=\"@dimen/screen_padding\"\n\t\tandroid:background=\"@drawable/bg_card\"\n\t\tandroid:elevation=\"4dp\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_anchor=\"@id/appbar_top\"\n\t\tapp:layout_anchorGravity=\"bottom|end\"\n\t\ttools:visibility=\"visible\" />\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar_top\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\tapp:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\tapp:liftOnScroll=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\t\tapp:elevation=\"@dimen/m3_card_elevated_elevation\"\n\t\t\ttools:menu=\"@menu/opt_reader\">\n\n\t\t\t<org.koitharu.kotatsu.reader.ui.ReaderActionsView\n\t\t\t\tandroid:id=\"@+id/actionsView\"\n\t\t\t\tandroid:layout_width=\"400dp\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:layout_gravity=\"center_vertical|end\" />\n\n\t\t</com.google.android.material.appbar.MaterialToolbar>\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<org.koitharu.kotatsu.reader.ui.ReaderToastView\n\t\tandroid:id=\"@+id/toastView\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"bottom|center_horizontal\"\n\t\tandroid:layout_marginBottom=\"20dp\"\n\t\tandroid:background=\"@drawable/bg_reader_indicator\"\n\t\tandroid:drawablePadding=\"6dp\"\n\t\tandroid:elevation=\"1000dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?attr/textAppearanceBodySmall\"\n\t\tandroid:theme=\"@style/ThemeOverlay.Material3.Dark\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_dodgeInsetEdges=\"bottom\"\n\t\ttools:text=\"@string/loading_\"\n\t\ttools:visibility=\"visible\" />\n\n\t<LinearLayout\n\t\tandroid:id=\"@+id/layout_loading\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_gravity=\"center\"\n\t\tandroid:background=\"@drawable/bg_card\"\n\t\tandroid:backgroundTint=\"?colorSurfaceContainer\"\n\t\tandroid:gravity=\"center_horizontal\"\n\t\tandroid:orientation=\"vertical\"\n\t\tandroid:outlineProvider=\"background\"\n\t\tandroid:padding=\"@dimen/screen_padding\">\n\n\t\t<com.google.android.material.progressindicator.CircularProgressIndicator\n\t\t\tandroid:id=\"@+id/progressBar\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:indeterminate=\"true\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@+id/textView_loading\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginTop=\"10dp\"\n\t\t\tandroid:text=\"@string/loading_\"\n\t\t\tandroid:textAppearance=\"?attr/textAppearanceTitleMedium\" />\n\n\t</LinearLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:elevation=\"0dp\"\n\t\tandroid:fitsSystemWindows=\"false\"\n\t\tapp:elevation=\"0dp\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/container_master\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\tapp:liftOnScroll=\"false\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/actionBarSize\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/container_master\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/container\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\"\n\t\tapp:layout_constraintWidth_max=\"400dp\"\n\t\tapp:layout_constraintWidth_min=\"320dp\"\n\t\tapp:layout_constraintWidth_percent=\"0.4\" />\n\n\t<TextView\n\t\tandroid:id=\"@+id/textView_header\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_marginStart=\"?listPreferredItemPaddingStart\"\n\t\tandroid:layout_marginTop=\"@dimen/screen_padding\"\n\t\tandroid:layout_marginEnd=\"?listPreferredItemPaddingEnd\"\n\t\tandroid:gravity=\"center_vertical|start\"\n\t\tandroid:padding=\"8dp\"\n\t\tandroid:singleLine=\"true\"\n\t\tandroid:textAppearance=\"?textAppearanceTitleSmall\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/container_master\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\ttools:text=\"@string/appearance\" />\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@id/container\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/container_master\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/textView_header\"\n\t\ttools:layout=\"@layout/fragment_settings_sources\" />\n\n\t<View\n\t\tandroid:layout_width=\"1dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:background=\"?colorSurfaceDim\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"@id/container_master\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/container_master\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/container_search\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:layout_behavior=\"com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/activity_stats.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:orientation=\"vertical\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appbar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:fitsSystemWindows=\"true\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_center\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t<com.google.android.material.appbar.MaterialToolbar\n\t\t\tandroid:id=\"@id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"?attr/actionBarSize\"\n\t\t\tapp:layout_scrollFlags=\"noScroll\"\n\t\t\ttools:title=\"@string/reading_stats\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<HorizontalScrollView\n\t\tandroid:id=\"@+id/scrollView_chips\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:clipToPadding=\"false\"\n\t\tandroid:paddingHorizontal=\"12dp\"\n\t\tandroid:scrollIndicators=\"start\"\n\t\tandroid:scrollbars=\"none\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/appbar\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_center\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t<com.google.android.material.chip.ChipGroup\n\t\t\tandroid:id=\"@+id/layout_chips\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_gravity=\"center_vertical\">\n\n\t\t\t<com.google.android.material.chip.Chip\n\t\t\t\tandroid:id=\"@+id/chip_period\"\n\t\t\t\tstyle=\"@style/Widget.Kotatsu.Chip.Dropdown\"\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\t\tandroid:text=\"@string/week\"\n\t\t\t\tapp:chipIcon=\"@drawable/ic_history\" />\n\n\t\t</com.google.android.material.chip.ChipGroup>\n\n\t</HorizontalScrollView>\n\n\t<com.google.android.material.progressindicator.LinearProgressIndicator\n\t\tandroid:id=\"@+id/progressBar\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:indeterminate=\"true\"\n\t\tandroid:visibility=\"gone\"\n\t\tapp:hideAnimationBehavior=\"outward\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"@id/appbar\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\"\n\t\tapp:showAnimationBehavior=\"inward\"\n\t\tapp:trackCornerRadius=\"0dp\"\n\t\ttools:visibility=\"visible\" />\n\n\t<org.koitharu.kotatsu.stats.ui.views.PieChartView\n\t\tandroid:id=\"@+id/chart\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout_margin=\"24dp\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintDimensionRatio=\"1\"\n\t\tapp:layout_constraintEnd_toStartOf=\"@id/guideline_center\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\" />\n\n\t<androidx.recyclerview.widget.RecyclerView\n\t\tandroid:id=\"@+id/recyclerView\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:overScrollMode=\"ifContentScrolls\"\n\t\tandroid:scrollbars=\"vertical\"\n\t\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toEndOf=\"@id/guideline_center\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\"\n\t\ttools:itemCount=\"4\"\n\t\ttools:listitem=\"@layout/item_stats\" />\n\n\t<ViewStub\n\t\tandroid:id=\"@+id/stub_empty\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tandroid:layout=\"@layout/item_empty_state\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appbar\"\n\t\ttools:visibility=\"visible\" />\n\n\t<androidx.constraintlayout.widget.Guideline\n\t\tandroid:id=\"@+id/guideline_center\"\n\t\tandroid:layout_width=\"wrap_content\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:orientation=\"vertical\"\n\t\tapp:layout_constraintGuide_percent=\"0.5\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-w600dp-land/fragment_settings_sources.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/recyclerView\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:background=\"?android:colorBackground\"\n\tandroid:clipToPadding=\"false\"\n\tandroid:orientation=\"vertical\"\n\tandroid:scrollbars=\"vertical\"\n\tapp:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n\ttools:listitem=\"@layout/item_source_config\" />\n"
  },
  {
    "path": "app/src/main/res/menu/mode_bookmarks.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/remove\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_save\"\n\t\tandroid:title=\"@string/save\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_category.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_show\"\n\t\tandroid:icon=\"@drawable/ic_eye\"\n\t\tandroid:title=\"@string/show\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_hide\"\n\t\tandroid:icon=\"@drawable/ic_eye_off\"\n\t\tandroid:title=\"@string/hide\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/remove\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_chapters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_save\"\n\t\tandroid:title=\"@string/save\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_delete\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/delete\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_mark_current\"\n\t\tandroid:icon=\"@drawable/ic_eye_check\"\n\t\tandroid:title=\"@string/mark_as_current\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_select_range\"\n\t\tandroid:icon=\"@drawable/ic_select_range\"\n\t\tandroid:title=\"@string/select_range\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_select_all\"\n\t\tandroid:icon=\"?actionModeSelectAllDrawable\"\n\t\tandroid:title=\"@android:string/selectAll\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_downloads.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_resume\"\n\t\tandroid:icon=\"@drawable/ic_action_resume\"\n\t\tandroid:title=\"@string/resume\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_pause\"\n\t\tandroid:icon=\"@drawable/ic_action_pause\"\n\t\tandroid:title=\"@string/pause\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_cancel\"\n\t\tandroid:icon=\"@drawable/ic_cancel_multiple\"\n\t\tandroid:title=\"@android:string/cancel\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/remove\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_select_all\"\n\t\tandroid:icon=\"?actionModeSelectAllDrawable\"\n\t\tandroid:title=\"@android:string/selectAll\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_favourites.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_share\"\n\t\tandroid:icon=\"?actionModeShareDrawable\"\n\t\tandroid:title=\"@string/share\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/remove\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_save\"\n\t\tandroid:title=\"@string/save\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_fix\"\n\t\tandroid:icon=\"@drawable/ic_auto_fix\"\n\t\tandroid:title=\"@string/fix\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_favourite\"\n\t\tandroid:icon=\"@drawable/ic_heart\"\n\t\tandroid:title=\"@string/categories\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_edit_override\"\n\t\tandroid:icon=\"@drawable/ic_edit\"\n\t\tandroid:title=\"@string/edit\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_mark_current\"\n\t\tandroid:icon=\"@drawable/ic_eye_check\"\n\t\tandroid:title=\"@string/mark_as_completed\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_select_all\"\n\t\tandroid:icon=\"?actionModeSelectAllDrawable\"\n\t\tandroid:title=\"@android:string/selectAll\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_share\"\n\t\tandroid:icon=\"?actionModeShareDrawable\"\n\t\tandroid:title=\"@string/share\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/remove\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_save\"\n\t\tandroid:title=\"@string/save\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_favourite\"\n\t\tandroid:icon=\"@drawable/ic_heart_outline\"\n\t\tandroid:title=\"@string/add_to_favourites\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_fix\"\n\t\tandroid:icon=\"@drawable/ic_auto_fix\"\n\t\tandroid:title=\"@string/fix\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_edit_override\"\n\t\tandroid:icon=\"@drawable/ic_edit\"\n\t\tandroid:title=\"@string/edit\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_mark_current\"\n\t\tandroid:icon=\"@drawable/ic_eye_check\"\n\t\tandroid:title=\"@string/mark_as_completed\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_share\"\n\t\tandroid:icon=\"?actionModeShareDrawable\"\n\t\tandroid:title=\"@string/share\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/delete\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_edit_override\"\n\t\tandroid:icon=\"@drawable/ic_edit\"\n\t\tandroid:title=\"@string/edit\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_select_all\"\n\t\tandroid:icon=\"?actionModeSelectAllDrawable\"\n\t\tandroid:title=\"@android:string/selectAll\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_pages.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_save\"\n\t\tandroid:title=\"@string/save\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_remote.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_share\"\n\t\tandroid:icon=\"?actionModeShareDrawable\"\n\t\tandroid:title=\"@string/share\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_favourite\"\n\t\tandroid:icon=\"@drawable/ic_heart_outline\"\n\t\tandroid:title=\"@string/add_to_favourites\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_save\"\n\t\tandroid:title=\"@string/save\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/mode_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_disable\"\n\t\tandroid:icon=\"@drawable/ic_eye_off\"\n\t\tandroid:title=\"@string/disable\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_delete\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/delete\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_pin\"\n\t\tandroid:icon=\"@drawable/ic_pin\"\n\t\tandroid:title=\"@string/pin\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_unpin\"\n\t\tandroid:icon=\"@drawable/ic_unpin\"\n\t\tandroid:title=\"@string/unpin\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_shortcut\"\n\t\tandroid:icon=\"@drawable/ic_shortcut\"\n\t\tandroid:title=\"@string/create_shortcut\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_settings\"\n\t\tandroid:icon=\"@drawable/ic_settings\"\n\t\tandroid:title=\"@string/settings\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/mode_updates.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_share\"\n\t\tandroid:icon=\"?actionModeShareDrawable\"\n\t\tandroid:title=\"@string/share\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/delete\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_save\"\n\t\tandroid:title=\"@string/save\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_select_all\"\n\t\tandroid:icon=\"?actionModeSelectAllDrawable\"\n\t\tandroid:title=\"@android:string/selectAll\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_browser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_browser\"\n\t\tandroid:icon=\"@drawable/ic_open_external\"\n\t\tandroid:title=\"@string/open_in_browser\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/opt_captcha.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_retry\"\n\t\tandroid:title=\"@string/try_again\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_chapters.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_search\"\n\t\tandroid:icon=\"?actionModeWebSearchDrawable\"\n\t\tandroid:orderInCategory=\"10\"\n\t\tandroid:title=\"@string/search_chapters\"\n\t\tapp:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n\t\tapp:showAsAction=\"always|collapseActionView\"\n\t\ttools:ignore=\"AlwaysShowAction\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_downloaded\"\n\t\tandroid:checkable=\"true\"\n\t\tandroid:orderInCategory=\"18\"\n\t\tandroid:title=\"@string/on_device\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_reversed\"\n\t\tandroid:checkable=\"true\"\n\t\tandroid:orderInCategory=\"20\"\n\t\tandroid:title=\"@string/reverse\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_grid_view\"\n\t\tandroid:checkable=\"true\"\n\t\tandroid:orderInCategory=\"30\"\n\t\tandroid:title=\"@string/chapters_grid_view\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_details.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_share\"\n\t\tandroid:icon=\"?actionModeShareDrawable\"\n\t\tandroid:orderInCategory=\"15\"\n\t\tandroid:title=\"@string/share\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:icon=\"@drawable/ic_download\"\n\t\tandroid:orderInCategory=\"40\"\n\t\tandroid:title=\"@string/download\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_delete\"\n\t\tandroid:orderInCategory=\"40\"\n\t\tandroid:title=\"@string/delete\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_edit_override\"\n\t\tandroid:orderInCategory=\"40\"\n\t\tandroid:title=\"@string/edit\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_scrobbling\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/tracking\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_stats\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/statistics\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_related\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/find_similar\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_alternatives\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/alternatives\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_online\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/online_variant\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_browser\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/open_in_browser\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_shortcut\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/create_shortcut\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_downloads.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_pause\"\n\t\tandroid:icon=\"@drawable/ic_action_pause\"\n\t\tandroid:title=\"@string/pause\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_resume\"\n\t\tandroid:icon=\"@drawable/ic_action_resume\"\n\t\tandroid:title=\"@string/resume\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"ifRoom|withText\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_cancel_all\"\n\t\tandroid:title=\"@string/cancel_all\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_remove_completed\"\n\t\tandroid:title=\"@string/remove_completed\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_settings\"\n\t\tandroid:title=\"@string/settings\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_explore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_manage\"\n\t\tandroid:title=\"@string/manage_sources\"\n\t\tandroid:titleCondensed=\"@string/manage\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_favourites_container.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_manage\"\n\t\tandroid:orderInCategory=\"48\"\n\t\tandroid:title=\"@string/favourites_categories\"\n\t\tandroid:titleCondensed=\"@string/categories\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_feed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_update\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/update\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_show_updated\"\n\t\tandroid:checkable=\"true\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/show_updated\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_clear_feed\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/clear_feed\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_clear_history\"\n\t\tandroid:orderInCategory=\"10\"\n\t\tandroid:title=\"@string/clear_history\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_stats\"\n\t\tandroid:orderInCategory=\"40\"\n\t\tandroid:title=\"@string/statistics\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_save\"\n\t\tandroid:title=\"@string/save\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_list_mode\"\n\t\tandroid:orderInCategory=\"40\"\n\t\tandroid:title=\"@string/list_options\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_list_remote.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_random\"\n\t\tandroid:icon=\"@drawable/ic_dice\"\n\t\tandroid:orderInCategory=\"10\"\n\t\tandroid:title=\"@string/random\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_filter\"\n\t\tandroid:orderInCategory=\"30\"\n\t\tandroid:title=\"@string/filter\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_filter_reset\"\n\t\tandroid:orderInCategory=\"30\"\n\t\tandroid:title=\"@string/reset_filter\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"never\"\n\t\ttools:visible=\"true\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_source_settings\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/settings\"\n\t\tapp:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_import\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/_import\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_filter\"\n\t\tandroid:orderInCategory=\"30\"\n\t\tandroid:title=\"@string/filter\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_directories\"\n\t\tandroid:orderInCategory=\"80\"\n\t\tandroid:title=\"@string/directories\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_app_update\"\n\t\tandroid:icon=\"@drawable/ic_app_update\"\n\t\tandroid:orderInCategory=\"1\"\n\t\tandroid:title=\"@string/app_update_available\"\n\t\tandroid:titleCondensed=\"@string/update\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_incognito\"\n\t\tandroid:checkable=\"true\"\n\t\tandroid:orderInCategory=\"90\"\n\t\tandroid:title=\"@string/incognito_mode\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_settings\"\n\t\tandroid:orderInCategory=\"90\"\n\t\tandroid:title=\"@string/settings\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_pages.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_grid_size\"\n\t\tandroid:icon=\"@drawable/ic_size_large\"\n\t\tandroid:orderInCategory=\"10\"\n\t\tandroid:title=\"@string/grid_size\"\n\t\tapp:actionViewClass=\"com.google.android.material.slider.Slider\"\n\t\tapp:showAsAction=\"ifRoom|collapseActionView\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_reader.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_info\"\n\t\tandroid:icon=\"@drawable/ic_info_outline\"\n\t\tandroid:title=\"@string/details\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"always\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_scrobbling.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_browser\"\n\t\tandroid:title=\"@string/open_in_browser\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_edit\"\n\t\tandroid:title=\"@string/edit\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_unregister\"\n\t\tandroid:title=\"@string/remove\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/opt_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_search\"\n\t\tandroid:icon=\"?actionModeWebSearchDrawable\"\n\t\tandroid:orderInCategory=\"0\"\n\t\tandroid:title=\"@string/search\"\n\t\tapp:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n\t\tapp:showAsAction=\"ifRoom|collapseActionView\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_search_kind.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_search_kind\"\n\t\tandroid:icon=\"@drawable/ic_filter_menu\"\n\t\tandroid:orderInCategory=\"0\"\n\t\tandroid:title=\"@string/type\"\n\t\tapp:showAsAction=\"ifRoom\">\n\n\t\t<menu>\n\n\t\t\t<group\n\t\t\t\tandroid:id=\"@+id/group_search_kind\"\n\t\t\t\tandroid:checkableBehavior=\"single\">\n\n\t\t\t\t<item\n\t\t\t\t\tandroid:id=\"@+id/action_kind_simple\"\n\t\t\t\t\tandroid:title=\"@string/simple\" />\n\n\t\t\t\t<item\n\t\t\t\t\tandroid:id=\"@+id/action_kind_title\"\n\t\t\t\t\tandroid:title=\"@string/name\" />\n\n\t\t\t\t<item\n\t\t\t\t\tandroid:id=\"@+id/action_kind_author\"\n\t\t\t\t\tandroid:title=\"@string/author\" />\n\n\t\t\t\t<item\n\t\t\t\t\tandroid:id=\"@+id/action_kind_tag\"\n\t\t\t\t\tandroid:title=\"@string/genre\" />\n\n\t\t\t</group>\n\n\t\t\t<group android:id=\"@+id/group_search_filters\">\n\n\t\t\t\t<item\n\t\t\t\t\tandroid:id=\"@+id/action_filter_pinned_only\"\n\t\t\t\t\tandroid:checkable=\"true\"\n\t\t\t\t\tandroid:title=\"@string/pinned_sources_only\" />\n\n\t\t\t\t<item\n\t\t\t\t\tandroid:id=\"@+id/action_filter_hide_empty\"\n\t\t\t\t\tandroid:checkable=\"true\"\n\t\t\t\t\tandroid:title=\"@string/hide_empty_sources\" />\n\n\t\t\t</group>\n\t\t</menu>\n\n\t</item>\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_search_suggestion.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_voice_search\"\n\t\tandroid:icon=\"@drawable/ic_voice_input\"\n\t\tandroid:orderInCategory=\"0\"\n\t\tandroid:title=\"@string/voice_search\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_clear\"\n\t\tandroid:title=\"@string/clear_search_history\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_shiki_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_search\"\n\t\tandroid:icon=\"?actionModeWebSearchDrawable\"\n\t\tandroid:title=\"@string/search\"\n\t\tapp:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n\t\tapp:showAsAction=\"ifRoom|collapseActionView\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/opt_sources.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_catalog\"\n\t\tandroid:icon=\"@drawable/ic_add\"\n\t\tandroid:title=\"@string/sources_catalog\"\n\t\tapp:showAsAction=\"ifRoom\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_search\"\n\t\tandroid:icon=\"?actionModeWebSearchDrawable\"\n\t\tandroid:title=\"@string/search\"\n\t\tapp:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n\t\tapp:showAsAction=\"ifRoom|collapseActionView\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_no_nsfw\"\n\t\tandroid:checkable=\"true\"\n\t\tandroid:title=\"@string/disable_nsfw\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_disable_all\"\n\t\tandroid:title=\"@string/disable_all\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_sources_catalog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_search\"\n\t\tandroid:icon=\"?actionModeWebSearchDrawable\"\n\t\tandroid:title=\"@string/search\"\n\t\tapp:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n\t\tapp:showAsAction=\"ifRoom|collapseActionView\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_stats.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_clear\"\n\t\tandroid:title=\"@string/clear_stats\"\n\t\tandroid:titleCondensed=\"@string/clear\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_suggestions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_update\"\n\t\tandroid:orderInCategory=\"50\"\n\t\tandroid:title=\"@string/update\"\n\t\tapp:showAsAction=\"never\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_settings_suggestions\"\n\t\tandroid:orderInCategory=\"90\"\n\t\tandroid:title=\"@string/settings\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/opt_tap_grid_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_reset\"\n\t\tandroid:title=\"@string/reset\"\n\t\tapp:showAsAction=\"never\" />\n\t\t\n\t<item\n\t\tandroid:id=\"@+id/action_disable_all\"\n\t\tandroid:title=\"@string/disable_all\"\n\t\tapp:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/popup_fav_tab.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_edit\"\n\t\tandroid:title=\"@string/edit_category\"\n\t\tandroid:titleCondensed=\"@string/edit\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_delete\"\n\t\tandroid:title=\"@string/delete\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_hide\"\n\t\tandroid:title=\"@string/hide\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/popup_fav_tab_all.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_hide\"\n\t\tandroid:title=\"@string/hide\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_manage\"\n\t\tandroid:title=\"@string/manage\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/popup_read.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_incognito\"\n\t\tandroid:icon=\"@drawable/ic_incognito\"\n\t\tandroid:title=\"@string/incognito_mode\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_forget\"\n\t\tandroid:icon=\"@drawable/ic_delete\"\n\t\tandroid:title=\"@string/remove_from_history\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_download\"\n\t\tandroid:icon=\"@drawable/ic_download\"\n\t\tandroid:title=\"@string/download\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/popup_saved_filter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/action_rename\"\n        android:title=\"@string/rename\" />\n\n    <item\n        android:id=\"@+id/action_delete\"\n        android:title=\"@string/delete\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/popup_source_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<item\n\t\tandroid:id=\"@+id/action_lift\"\n\t\tandroid:title=\"@string/to_top\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_pin\"\n\t\tandroid:checkable=\"true\"\n\t\tandroid:title=\"@string/pin\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_shortcut\"\n\t\tandroid:title=\"@string/create_shortcut\" />\n\n\t<item\n\t\tandroid:id=\"@+id/action_settings\"\n\t\tandroid:title=\"@string/settings\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<background android:drawable=\"@color/launcher_background\" />\n\t<foreground android:drawable=\"@mipmap/ic_launcher_foreground\" />\n\t<monochrome android:drawable=\"@mipmap/ic_launcher_foreground\" />\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<background android:drawable=\"@color/launcher_background\" />\n\t<foreground android:drawable=\"@mipmap/ic_launcher_foreground\" />\n\t<monochrome android:drawable=\"@mipmap/ic_launcher_foreground\" />\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/raw/keep.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\"\n\ttools:keep=\"@drawable/ic_denied_large,@drawable/ic_alert_outline,@drawable/ic_error_large\" />"
  },
  {
    "path": "app/src/main/res/raw/tags_warnlist",
    "content": "amputation\namputee\nanal birth\nanal torture\nbdsm\nbeast\nbeastiality\nbestiality\nbirth\nblackmail\nblood\nbody horror\nbondage\nboys' love\nbrother\nbukkake\ncannibalism\ncbt\nchoking\ncoprophagia\ndegradation\ndiapers\ndrugs\negg laying\nelectrical play\nelectro\nelectro play\nenema\nextreme\nfather\nfemdom\nforce\nfull censorship\nfurry\nfutanari\ngang rape\ngangbang\ngangbang rape\ngender bender\ngirls' love\nguro\ngore\nhuman pet\nhumiliation\nhypno\nincest\ninflation\ninsect\ninseki\nknife play\nloli\nlolicon\nmachine\nmind break\nmindbreak\nmolestation\nmosaic\nmother\nmutilation\nnecrophila\nnecrophilia\nnetorase\nnetorare\nnipple torture\nnon-consensual\nntr\norgasm denial\nparasite\npiercing\nprolapse\nprostitution\npublic use\npuke\npuppy play\nrape\nryona\nscar\nscat\nsexual violence\nshemale\nshota\nshotacon\nsister\nslave\nslavery\nsnuff\ntentacles\ntoddlercon\ntorture\ntrans\ntransgender\ntrap\ntraps\nunbirth\nurination\nvaginal birth\nviolent\nvomit\nvore\nwatersports\nyaoi\nyuri\nгуро\nинцест\nкопро\nтентакли\nтрап\nфутанари\nюри\nяой\n"
  },
  {
    "path": "app/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string-array name=\"themes\" translatable=\"false\">\n\t\t<item>@string/follow_system</item>\n\t\t<item>@string/light</item>\n\t\t<item>@string/dark</item>\n\t</string-array>\n\t<string-array name=\"zoom_modes\" translatable=\"false\">\n\t\t<item>@string/zoom_mode_fit_center</item>\n\t\t<item>@string/zoom_mode_fit_height</item>\n\t\t<item>@string/zoom_mode_fit_width</item>\n\t\t<item>@string/zoom_mode_keep_start</item>\n\t</string-array>\n\t<string-array name=\"track_sources\" translatable=\"false\">\n\t\t<item>@string/favourites</item>\n\t\t<item>@string/history</item>\n\t</string-array>\n\t<string-array name=\"list_modes\" translatable=\"false\">\n\t\t<item>@string/list</item>\n\t\t<item>@string/detailed_list</item>\n\t\t<item>@string/grid</item>\n\t</string-array>\n\t<string-array name=\"screenshots_policy\" translatable=\"false\">\n\t\t<item>@string/screenshots_allow</item>\n\t\t<item>@string/screenshots_block_nsfw</item>\n\t\t<item>@string/screenshots_block_incognito</item>\n\t\t<item>@string/screenshots_block_all</item>\n\t</string-array>\n\t<string-array name=\"network_policy\" translatable=\"false\">\n\t\t<item>@string/always</item>\n\t\t<item>@string/only_using_wifi</item>\n\t\t<item>@string/never</item>\n\t</string-array>\n\t<string-array name=\"doh_providers\" translatable=\"false\">\n\t\t<item>@string/disabled</item>\n\t\t<item>Google</item>\n\t\t<item>CloudFlare</item>\n\t\t<item>AdGuard</item>\n\t\t<item>0ms</item>\n\t</string-array>\n\t<string-array name=\"image_proxies\" translatable=\"false\">\n\t\t<item>@string/none</item>\n\t\t<item>wsrv.nl</item>\n\t\t<item>0ms.dev</item>\n\t</string-array>\n\t<string-array name=\"reader_modes\" translatable=\"false\">\n\t\t<item>@string/standard</item>\n\t\t<item>@string/right_to_left</item>\n\t\t<item>@string/vertical</item>\n\t\t<item>@string/webtoon</item>\n\t</string-array>\n\t<string-array name=\"scrobbling_statuses\" translatable=\"false\">\n\t\t<item>@string/status_planned</item>\n\t\t<item>@string/status_reading</item>\n\t\t<item>@string/status_re_reading</item>\n\t\t<item>@string/status_completed</item>\n\t\t<item>@string/status_on_hold</item>\n\t\t<item>@string/status_dropped</item>\n\t</string-array>\n\t<string-array name=\"proxy_types\" translatable=\"false\">\n\t\t<item>@string/disabled</item>\n\t\t<item>HTTP</item>\n\t\t<item>SOCKS (v4/v5)</item>\n\t</string-array>\n\t<string-array name=\"reader_backgrounds\" translatable=\"false\">\n\t\t<item>@string/system_default</item>\n\t\t<item>@string/color_light</item>\n\t\t<item>@string/color_dark</item>\n\t\t<item>@string/color_white</item>\n\t\t<item>@string/color_black</item>\n\t</string-array>\n\t<string-array name=\"reader_animation\" translatable=\"false\">\n\t\t<item>@string/disabled</item>\n\t\t<item>@string/system_default</item>\n\t\t<item>@string/advanced</item>\n\t</string-array>\n\t<string-array name=\"backup_frequency\" translatable=\"false\">\n\t\t<item>@string/frequency_every_6_hours</item>\n\t\t<item>@string/frequency_every_day</item>\n\t\t<item>@string/frequency_every_2_days</item>\n\t\t<item>@string/frequency_once_per_week</item>\n\t\t<item>@string/frequency_twice_per_month</item>\n\t\t<item>@string/frequency_once_per_month</item>\n\t</string-array>\n\t<string-array name=\"details_tabs\" translatable=\"false\">\n\t\t<item>@string/last_used</item>\n\t\t<item>@string/chapters</item>\n\t\t<item>@string/pages</item>\n\t\t<item>@string/bookmarks</item>\n\t</string-array>\n\t<string-array name=\"download_formats\" translatable=\"false\">\n\t\t<item>@string/automatic</item>\n\t\t<item>@string/single_cbz_file</item>\n\t\t<item>@string/multiple_cbz_files</item>\n\t</string-array>\n\t<string-array name=\"tracker_frequency\" translatable=\"false\">\n\t\t<item>@string/manual</item>\n\t\t<item>@string/less_frequently</item>\n\t\t<item>@string/system_default</item>\n\t\t<item>@string/more_frequently</item>\n\t</string-array>\n\t<string-array name=\"reader_crop\" translatable=\"false\">\n\t\t<item>@string/pages</item>\n\t\t<item>@string/webtoon</item>\n\t</string-array>\n\t<string-array name=\"progress_indicators\" translatable=\"false\">\n\t\t<item>@string/disabled</item>\n\t\t<item>@string/percent_read</item>\n\t\t<item>@string/percent_left</item>\n\t\t<item>@string/chapters_read</item>\n\t\t<item>@string/chapters_left</item>\n\t</string-array>\n\t<string-array name=\"tracker_download_strategies\" translatable=\"false\">\n\t\t<item>@string/never</item>\n\t\t<item>@string/manga_with_downloaded_chapters</item>\n\t</string-array>\n\t<string-array name=\"metered_network_options\" translatable=\"false\">\n\t\t<item>@string/allow_always</item>\n\t\t<item>@string/ask_every_time</item>\n\t\t<item>@string/dont_allow</item>\n\t</string-array>\n\t<string-array name=\"screen_orientations\" translatable=\"false\">\n\t\t<item>@string/system_default</item>\n\t\t<item>@string/automatic</item>\n\t\t<item>@string/portrait</item>\n\t\t<item>@string/landscape</item>\n\t</string-array>\n\t<string-array name=\"reader_controls\" translatable=\"false\">\n\t\t<item>@string/prev_chapter</item>\n\t\t<item>@string/next_chapter</item>\n\t\t<item>@string/pages_slider</item>\n\t\t<item>@string/chapters_and_pages</item>\n\t\t<item>@string/screen_orientation</item>\n\t\t<item>@string/save_page</item>\n\t\t<item>@string/automatic_scroll</item>\n\t\t<item>@string/bookmark_add</item>\n\t</string-array>\n\t<string-array name=\"list_badges\" translatable=\"false\">\n\t\t<item>@string/favourites</item>\n\t\t<item>@string/saved_manga</item>\n\t</string-array>\n\t<string-array name=\"incognito_nsfw_options\" translatable=\"false\">\n\t\t<item>@string/enable</item>\n\t\t<item>@string/ask_every_time</item>\n\t\t<item>@string/disable</item>\n\t</string-array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<attr name=\"expandCollapseIndicator\" format=\"reference\" />\n\n\t<attr name=\"sliderPreferenceStyle\" />\n\t<attr name=\"multiAutoCompleteTextViewPreferenceStyle\" />\n\t<attr name=\"autoCompleteTextViewPreferenceStyle\" />\n\t<attr name=\"themeChooserPreferenceStyle\" />\n\t<attr name=\"listItemTextViewStyle\" />\n\t<attr name=\"fastScrollerStyle\" />\n\t<attr name=\"tipViewStyle\" />\n\t<attr name=\"progressButtonStyle\" />\n\t<attr name=\"dotIndicatorStyle\" />\n\t<attr name=\"badgeViewStyle\" />\n\t<attr name=\"coverImageViewStyle\" />\n\n\t<declare-styleable name=\"CoilImageView\">\n\t\t<attr name=\"allowRgb565\" format=\"boolean\" />\n\t\t<attr name=\"useExistingDrawable\" format=\"boolean\" />\n\t\t<attr name=\"decodeRegion\" format=\"boolean\" />\n\t\t<attr name=\"placeholderDrawable\" format=\"reference\" />\n\t\t<attr name=\"errorDrawable\" format=\"reference\" />\n\t\t<attr name=\"fallbackDrawable\" format=\"reference\" />\n\t\t<attr name=\"crossfadeEnabled\" format=\"boolean\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"CoverImageView\">\n\t\t<attr name=\"aspectRationHeight\" format=\"integer\" />\n\t\t<attr name=\"aspectRationWidth\" format=\"integer\" />\n\t\t<attr name=\"trimImage\" format=\"boolean\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"CoverStackView\">\n\t\t<attr name=\"coverSize\" format=\"dimension\" />\n\t\t<attr name=\"hideEmptyViews\" format=\"boolean\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"FaviconView\">\n\t\t<attr name=\"iconStyle\" format=\"reference\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"SliderPreference\">\n\t\t<attr name=\"android:valueFrom\" />\n\t\t<attr name=\"android:valueTo\" />\n\t\t<attr name=\"android:stepSize\" />\n\t\t<attr name=\"tickVisible\" />\n\t\t<attr name=\"useSimpleSummaryProvider\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"ListItemTextView\">\n\t\t<attr name=\"shapeAppearance\" />\n\t\t<attr name=\"shapeAppearanceOverlay\" />\n\t\t<attr name=\"backgroundFillColor\" format=\"color\" />\n\t\t<attr name=\"checkedDrawableStart\" format=\"reference\" />\n\t\t<attr name=\"checkedDrawableEnd\" format=\"reference\" />\n\t\t<attr name=\"android:insetTop\" />\n\t\t<attr name=\"android:insetBottom\" />\n\t\t<attr name=\"android:insetLeft\" />\n\t\t<attr name=\"android:insetRight\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"TwoLinesItemView\">\n\t\t<attr name=\"shapeAppearance\" />\n\t\t<attr name=\"shapeAppearanceOverlay\" />\n\t\t<attr name=\"backgroundFillColor\" />\n\t\t<attr name=\"android:textColor\" />\n\t\t<attr name=\"android:drawablePadding\" />\n\t\t<attr name=\"android:insetTop\" />\n\t\t<attr name=\"android:insetBottom\" />\n\t\t<attr name=\"android:insetLeft\" />\n\t\t<attr name=\"android:insetRight\" />\n\t\t<attr name=\"title\" />\n\t\t<attr name=\"subtitle\" />\n\t\t<attr name=\"icon\" />\n\t\t<attr name=\"titleTextAppearance\" />\n\t\t<attr name=\"subtitleTextAppearance\" />\n\t\t<attr name=\"android:checked\" />\n\t\t<attr name=\"android:button\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"ProgressDrawable\">\n\t\t<attr name=\"strokeWidth\" />\n\t\t<attr name=\"android:strokeColor\" />\n\t\t<attr name=\"android:textSize\" />\n\t\t<attr name=\"android:textColor\" />\n\t\t<attr name=\"android:fillColor\" />\n\t\t<attr name=\"android:fillAlpha\" />\n\t\t<attr name=\"android:width\" />\n\t\t<attr name=\"android:height\" />\n\t\t<attr name=\"outlineColor\" format=\"color\" />\n\t\t<attr name=\"autoFitTextSize\" format=\"boolean\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"ReadingProgressView\">\n\t\t<attr name=\"progressStyle\" format=\"reference\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"FastScrollRecyclerView\">\n\t\t<attr name=\"hideScrollbar\" format=\"boolean\" />\n\t\t<attr name=\"showBubble\" format=\"boolean\" />\n\t\t<attr name=\"showBubbleAlways\" format=\"boolean\" />\n\t\t<attr name=\"showTrack\" format=\"boolean\" />\n\t\t<attr name=\"bubbleColor\" format=\"color\" />\n\t\t<attr name=\"bubbleTextColor\" format=\"color\" />\n\t\t<attr name=\"thumbColor\" format=\"color\" />\n\t\t<attr name=\"trackColor\" format=\"color\" />\n\t\t<attr name=\"bubbleTextSize\" format=\"dimension\" />\n\t\t<attr name=\"scrollerOffset\" format=\"dimension\" />\n\t\t<attr name=\"bubbleSize\" format=\"enum\">\n\t\t\t<enum name=\"normal\" value=\"0\" />\n\t\t\t<enum name=\"small\" value=\"1\" />\n\t\t</attr>\n\t</declare-styleable>\n\n\t<declare-styleable name=\"BottomSheetHeaderBar\">\n\t\t<attr name=\"title\" />\n\t\t<attr name=\"menu\" />\n\t\t<attr name=\"fitStatusBar\" format=\"boolean\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"AdaptiveSheetHeaderBar\">\n\t\t<attr name=\"title\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"ShapeView\">\n\t\t<attr name=\"strokeWidth\" />\n\t\t<attr name=\"strokeColor\" />\n\t\t<attr name=\"cornerSize\" />\n\t\t<attr name=\"cornerSizeTopLeft\" />\n\t\t<attr name=\"cornerSizeTopRight\" />\n\t\t<attr name=\"cornerSizeBottomLeft\" />\n\t\t<attr name=\"cornerSizeBottomRight\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"AutoCompleteTextViewPreference\">\n\t\t<attr name=\"android:entries\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"TipView\">\n\t\t<attr name=\"icon\" />\n\t\t<attr name=\"title\" />\n\t\t<attr name=\"android:text\" />\n\t\t<attr name=\"primaryButtonText\" format=\"string\" />\n\t\t<attr name=\"secondaryButtonText\" format=\"string\" />\n\t\t<attr name=\"shapeAppearance\" />\n\t\t<attr name=\"shapeAppearanceOverlay\" />\n\t\t<attr name=\"cardBackgroundColor\" />\n\t\t<attr name=\"strokeWidth\" />\n\t\t<attr name=\"strokeColor\" />\n\t\t<attr name=\"elevation\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"FaviconFallbackDrawable\">\n\t\t<attr name=\"backgroundColor\" />\n\t\t<attr name=\"strokeColor\" />\n\t\t<attr name=\"strokeWidth\" />\n\t\t<attr name=\"cornerSize\" />\n\t\t<attr name=\"drawableSize\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"NestedRecyclerView\">\n\t\t<attr name=\"maxHeight\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"ChipsView\">\n\t\t<attr name=\"chipStyle\" />\n\t\t<attr name=\"chipIconVisible\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"ReaderInfoBarView\">\n\t\t<attr name=\"android:textSize\" />\n\t\t<attr name=\"android:strokeWidth\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"ProgressButton\">\n\t\t<attr name=\"titleTextAppearance\" />\n\t\t<attr name=\"subtitleTextAppearance\" />\n\t\t<attr name=\"title\" />\n\t\t<attr name=\"subtitle\" />\n\t\t<attr name=\"android:textColor\" />\n\t\t<attr name=\"android:max\" />\n\t\t<attr name=\"android:progress\" />\n\t\t<attr name=\"baseColor\" format=\"color\" />\n\t\t<attr name=\"progressColor\" format=\"color\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"NonFilteringAutoCompleteTextView\">\n\t\t<attr name=\"readOnly\" format=\"boolean\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"DotsIndicator\">\n\t\t<attr name=\"dotSize\" format=\"dimension\">\n\t\t\t<enum name=\"fit\" value=\"-1\" />\n\t\t</attr>\n\t\t<attr name=\"dotScale\" format=\"float\" />\n\t\t<attr name=\"dotAlpha\" format=\"float\" />\n\t\t<attr name=\"dotColor\" format=\"color\" />\n\t\t<attr name=\"dotSpacing\" format=\"dimension\" />\n\t\t<attr name=\"android:max\" />\n\t\t<attr name=\"android:progress\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"IconsView\">\n\t\t<attr name=\"iconSize\" />\n\t\t<attr name=\"iconSpacing\" format=\"dimension\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"FilterFieldLayout\">\n\t\t<attr name=\"title\" />\n\t\t<attr name=\"showMoreButton\" format=\"boolean\" />\n\t</declare-styleable>\n\n\t<declare-styleable name=\"BadgeView\">\n\t\t<attr name=\"shapeAppearance\" />\n\t\t<attr name=\"backgroundColor\" />\n\t\t<attr name=\"maxCharacterCount\" />\n\t\t<attr name=\"number\" />\n\t</declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<bool name=\"is_tablet\">false</bool>\n\t<bool name=\"light_status_bar\">true</bool>\n\t<bool name=\"light_navigation_bar\">false</bool>\n\t<bool name=\"com_samsung_android_icon_container_has_icon_container\">true</bool>\n\t<bool name=\"is_sync_enabled\">true</bool>\n\t<bool name=\"is_predictive_back_enabled\">true</bool>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<color name=\"error\">#BA1B1B</color>\n\t<color name=\"errorContainer\">#FFDAD4</color>\n\t<color name=\"onError\">#FFFFFF</color>\n\t<color name=\"onErrorContainer\">#410001</color>\n\n\t<!-- AMOLED Theme -->\n\t<color name=\"surface_amoled\">#080808</color>\n\t<color name=\"background_amoled\">#000000</color>\n\n\t<!-- Other Colors -->\n\n\t<color name=\"blue_primary\">#1976D2</color>\n\t<color name=\"grey\">#424242</color>\n\t<color name=\"dim\">#99000000</color>\n\t<color name=\"dim2\">#C8FFFFFF</color>\n\t<color name=\"dim_lite\">#66000000</color>\n\t<color name=\"warning\">#E65100</color>\n\t<color name=\"launcher_background\">#FFFFFF</color>\n\t<color name=\"common_green\">#388E3C</color>\n\t<color name=\"common_red\">#D32F2F</color>\n\t<color name=\"common_yellow\">#FBC02D</color>\n\t<color name=\"nsfw_18\">#FF8A65</color>\n\t<color name=\"nsfw_16\">#FFD54F</color>\n\n\t<!-- Color schemes colors -->\n\t<color name=\"background_miku\">#F7FAF8</color>\n\t<color name=\"background_asuka\">#FFF8F7</color>\n\t<color name=\"background_mion\">#F8FAF6</color>\n\t<color name=\"background_rikka\">#FCF8FD</color>\n\t<color name=\"background_sakura\">#FFF8F8</color>\n\t<color name=\"background_mamimi\">#FFF8F7</color>\n\t<color name=\"background_kanade\">#F3F3F3</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors_kotatsu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Colors for Default theme\n  ~ M3 colors generated by Material Theme Builder (https://goo.gle/material-theme-builder-web)\n-->\n<resources>\n\t<color name=\"kotatsu_primary\">#0059C8</color>\n\t<color name=\"kotatsu_primaryContainer\">#D9E2FF</color>\n\t<color name=\"kotatsu_onPrimary\">#FFFFFF</color>\n\t<color name=\"kotatsu_onPrimaryContainer\">#001944</color>\n\t<color name=\"kotatsu_inversePrimary\">#AFC6FF</color>\n\t<color name=\"kotatsu_secondary\">#575E71</color>\n\t<color name=\"kotatsu_secondaryContainer\">#DBE2F9</color>\n\t<color name=\"kotatsu_onSecondary\">#FFFFFF</color>\n\t<color name=\"kotatsu_onSecondaryContainer\">#141B2C</color>\n\t<color name=\"kotatsu_tertiary\">#725573</color>\n\t<color name=\"kotatsu_tertiaryContainer\">#FDD7FB</color>\n\t<color name=\"kotatsu_onTertiary\">#FFFFFF</color>\n\t<color name=\"kotatsu_onTertiaryContainer\">#2A132C</color>\n\t<color name=\"kotatsu_surface\">#FBF8FD</color>\n\t<color name=\"kotatsu_surfaceDim\">#DBD9DD</color>\n\t<color name=\"kotatsu_surfaceBright\">#FBF8FD</color>\n\t<color name=\"kotatsu_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"kotatsu_surfaceContainerLow\">#F5F3F7</color>\n\t<color name=\"kotatsu_surfaceContainer\">#EFEDF1</color>\n\t<color name=\"kotatsu_surfaceContainerHigh\">#E9E7EC</color>\n\t<color name=\"kotatsu_surfaceContainerHighest\">#E3E2E6</color>\n\t<color name=\"kotatsu_surfaceVariant\">#E1E2EC</color>\n\t<color name=\"kotatsu_onSurface\">#1B1B1F</color>\n\t<color name=\"kotatsu_onSurfaceVariant\">#44464F</color>\n\t<color name=\"kotatsu_inverseSurface\">#303034</color>\n\t<color name=\"kotatsu_inverseOnSurface\">#F2F0F4</color>\n\t<color name=\"kotatsu_background\">#FEFBFF</color>\n\t<color name=\"kotatsu_outline\">#757780</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors_themed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<!-- Totoro -->\n\t<color name=\"totoro_primary\">#3C6090</color>\n\t<color name=\"totoro_onPrimary\">#FFFFFF</color>\n\t<color name=\"totoro_primaryContainer\">#D4E3FF</color>\n\t<color name=\"totoro_onPrimaryContainer\">#224876</color>\n\t<color name=\"totoro_secondary\">#545F71</color>\n\t<color name=\"totoro_onSecondary\">#FFFFFF</color>\n\t<color name=\"totoro_secondaryContainer\">#D8E3F8</color>\n\t<color name=\"totoro_onSecondaryContainer\">#3D4758</color>\n\t<color name=\"totoro_tertiary\">#6E5676</color>\n\t<color name=\"totoro_onTertiary\">#FFFFFF</color>\n\t<color name=\"totoro_tertiaryContainer\">#F7D8FF</color>\n\t<color name=\"totoro_onTertiaryContainer\">#553F5D</color>\n\t<color name=\"totoro_error\">#BA1A1A</color>\n\t<color name=\"totoro_onError\">#FFFFFF</color>\n\t<color name=\"totoro_errorContainer\">#FFDAD6</color>\n\t<color name=\"totoro_onErrorContainer\">#93000A</color>\n\t<color name=\"totoro_background\">#F9F9FF</color>\n\t<color name=\"totoro_onBackground\">#191C20</color>\n\t<color name=\"totoro_surface\">#F9F9FF</color>\n\t<color name=\"totoro_onSurface\">#191C20</color>\n\t<color name=\"totoro_surfaceVariant\">#E0E2EC</color>\n\t<color name=\"totoro_onSurfaceVariant\">#43474E</color>\n\t<color name=\"totoro_outline\">#74777F</color>\n\t<color name=\"totoro_outlineVariant\">#C3C6CF</color>\n\t<color name=\"totoro_scrim\">#000000</color>\n\t<color name=\"totoro_inverseSurface\">#2E3035</color>\n\t<color name=\"totoro_inverseOnSurface\">#F0F0F7</color>\n\t<color name=\"totoro_inversePrimary\">#A6C8FF</color>\n\t<color name=\"totoro_primaryFixed\">#D4E3FF</color>\n\t<color name=\"totoro_onPrimaryFixed\">#001C3A</color>\n\t<color name=\"totoro_primaryFixedDim\">#A6C8FF</color>\n\t<color name=\"totoro_onPrimaryFixedVariant\">#224876</color>\n\t<color name=\"totoro_secondaryFixed\">#D8E3F8</color>\n\t<color name=\"totoro_onSecondaryFixed\">#111C2B</color>\n\t<color name=\"totoro_secondaryFixedDim\">#BCC7DC</color>\n\t<color name=\"totoro_onSecondaryFixedVariant\">#3D4758</color>\n\t<color name=\"totoro_tertiaryFixed\">#F7D8FF</color>\n\t<color name=\"totoro_onTertiaryFixed\">#271430</color>\n\t<color name=\"totoro_tertiaryFixedDim\">#DABDE2</color>\n\t<color name=\"totoro_onTertiaryFixedVariant\">#553F5D</color>\n\t<color name=\"totoro_surfaceDim\">#D9DAE0</color>\n\t<color name=\"totoro_surfaceBright\">#F9F9FF</color>\n\t<color name=\"totoro_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"totoro_surfaceContainerLow\">#F3F3FA</color>\n\t<color name=\"totoro_surfaceContainer\">#EDEDF4</color>\n\t<color name=\"totoro_surfaceContainerHigh\">#E7E8EE</color>\n\t<color name=\"totoro_surfaceContainerHighest\">#E1E2E9</color>\n\t<!-- Asuka -->\n\t<color name=\"asuka_primary\">#904A40</color>\n\t<color name=\"asuka_onPrimary\">#FFFFFF</color>\n\t<color name=\"asuka_primaryContainer\">#FFDAD5</color>\n\t<color name=\"asuka_onPrimaryContainer\">#73342B</color>\n\t<color name=\"asuka_secondary\">#775651</color>\n\t<color name=\"asuka_onSecondary\">#FFFFFF</color>\n\t<color name=\"asuka_secondaryContainer\">#FFDAD5</color>\n\t<color name=\"asuka_onSecondaryContainer\">#5D3F3B</color>\n\t<color name=\"asuka_tertiary\">#705C2E</color>\n\t<color name=\"asuka_onTertiary\">#FFFFFF</color>\n\t<color name=\"asuka_tertiaryContainer\">#FCDFA6</color>\n\t<color name=\"asuka_onTertiaryContainer\">#564419</color>\n\t<color name=\"asuka_error\">#BA1A1A</color>\n\t<color name=\"asuka_onError\">#FFFFFF</color>\n\t<color name=\"asuka_errorContainer\">#FFDAD6</color>\n\t<color name=\"asuka_onErrorContainer\">#93000A</color>\n\t<color name=\"asuka_background\">#FFF8F6</color>\n\t<color name=\"asuka_onBackground\">#231918</color>\n\t<color name=\"asuka_surface\">#FFF8F6</color>\n\t<color name=\"asuka_onSurface\">#231918</color>\n\t<color name=\"asuka_surfaceVariant\">#F5DDDA</color>\n\t<color name=\"asuka_onSurfaceVariant\">#534341</color>\n\t<color name=\"asuka_outline\">#857370</color>\n\t<color name=\"asuka_outlineVariant\">#D8C2BE</color>\n\t<color name=\"asuka_scrim\">#000000</color>\n\t<color name=\"asuka_inverseSurface\">#392E2C</color>\n\t<color name=\"asuka_inverseOnSurface\">#FFEDEA</color>\n\t<color name=\"asuka_inversePrimary\">#FFB4A8</color>\n\t<color name=\"asuka_primaryFixed\">#FFDAD5</color>\n\t<color name=\"asuka_onPrimaryFixed\">#3B0905</color>\n\t<color name=\"asuka_primaryFixedDim\">#FFB4A8</color>\n\t<color name=\"asuka_onPrimaryFixedVariant\">#73342B</color>\n\t<color name=\"asuka_secondaryFixed\">#FFDAD5</color>\n\t<color name=\"asuka_onSecondaryFixed\">#2C1512</color>\n\t<color name=\"asuka_secondaryFixedDim\">#E7BDB6</color>\n\t<color name=\"asuka_onSecondaryFixedVariant\">#5D3F3B</color>\n\t<color name=\"asuka_tertiaryFixed\">#FCDFA6</color>\n\t<color name=\"asuka_onTertiaryFixed\">#251A00</color>\n\t<color name=\"asuka_tertiaryFixedDim\">#DEC38C</color>\n\t<color name=\"asuka_onTertiaryFixedVariant\">#564419</color>\n\t<color name=\"asuka_surfaceDim\">#E8D6D3</color>\n\t<color name=\"asuka_surfaceBright\">#FFF8F6</color>\n\t<color name=\"asuka_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"asuka_surfaceContainerLow\">#FFF0EE</color>\n\t<color name=\"asuka_surfaceContainer\">#FCEAE7</color>\n\t<color name=\"asuka_surfaceContainerHigh\">#F7E4E1</color>\n\t<color name=\"asuka_surfaceContainerHighest\">#F1DEDC</color>\n\t<!-- Itsuka -->\n\t<color name=\"itsuka_primary\">#974800</color>\n\t<color name=\"itsuka_onPrimary\">#FFFFFF</color>\n\t<color name=\"itsuka_primaryContainer\">#FE9245</color>\n\t<color name=\"itsuka_onPrimaryContainer\">#6B3100</color>\n\t<color name=\"itsuka_secondary\">#825334</color>\n\t<color name=\"itsuka_onSecondary\">#FFFFFF</color>\n\t<color name=\"itsuka_secondaryContainer\">#FDBF98</color>\n\t<color name=\"itsuka_onSecondaryContainer\">#794B2D</color>\n\t<color name=\"itsuka_tertiary\">#5D6300</color>\n\t<color name=\"itsuka_onTertiary\">#FFFFFF</color>\n\t<color name=\"itsuka_tertiaryContainer\">#ACB533</color>\n\t<color name=\"itsuka_onTertiaryContainer\">#404500</color>\n\t<color name=\"itsuka_error\">#BA1A1A</color>\n\t<color name=\"itsuka_onError\">#FFFFFF</color>\n\t<color name=\"itsuka_errorContainer\">#FFDAD6</color>\n\t<color name=\"itsuka_onErrorContainer\">#93000A</color>\n\t<color name=\"itsuka_background\">#FFF8F5</color>\n\t<color name=\"itsuka_onBackground\">#231A14</color>\n\t<color name=\"itsuka_surface\">#FFF8F5</color>\n\t<color name=\"itsuka_onSurface\">#231A14</color>\n\t<color name=\"itsuka_surfaceVariant\">#F9DDCE</color>\n\t<color name=\"itsuka_onSurfaceVariant\">#554338</color>\n\t<color name=\"itsuka_outline\">#897366</color>\n\t<color name=\"itsuka_outlineVariant\">#DCC1B3</color>\n\t<color name=\"itsuka_scrim\">#000000</color>\n\t<color name=\"itsuka_inverseSurface\">#392E28</color>\n\t<color name=\"itsuka_inverseOnSurface\">#FFEDE4</color>\n\t<color name=\"itsuka_inversePrimary\">#FFB689</color>\n\t<color name=\"itsuka_primaryFixed\">#FFDBC7</color>\n\t<color name=\"itsuka_onPrimaryFixed\">#311300</color>\n\t<color name=\"itsuka_primaryFixedDim\">#FFB689</color>\n\t<color name=\"itsuka_onPrimaryFixedVariant\">#733500</color>\n\t<color name=\"itsuka_secondaryFixed\">#FFDBC7</color>\n\t<color name=\"itsuka_onSecondaryFixed\">#311300</color>\n\t<color name=\"itsuka_secondaryFixedDim\">#F7B993</color>\n\t<color name=\"itsuka_onSecondaryFixedVariant\">#673C1F</color>\n\t<color name=\"itsuka_tertiaryFixed\">#E1EB64</color>\n\t<color name=\"itsuka_onTertiaryFixed\">#1B1D00</color>\n\t<color name=\"itsuka_tertiaryFixedDim\">#C5CE4A</color>\n\t<color name=\"itsuka_onTertiaryFixedVariant\">#464A00</color>\n\t<color name=\"itsuka_surfaceDim\">#E9D6CD</color>\n\t<color name=\"itsuka_surfaceBright\">#FFF8F5</color>\n\t<color name=\"itsuka_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"itsuka_surfaceContainerLow\">#FFF1EA</color>\n\t<color name=\"itsuka_surfaceContainer\">#FDEAE1</color>\n\t<color name=\"itsuka_surfaceContainerHigh\">#F7E5DB</color>\n\t<color name=\"itsuka_surfaceContainerHighest\">#F1DFD5</color>\n\t<!-- Kanade -->\n\t<color name=\"kanade_primary\">#353543</color>\n\t<color name=\"kanade_onPrimary\">#FFFFFF</color>\n\t<color name=\"kanade_primaryContainer\">#6C6C7B</color>\n\t<color name=\"kanade_onPrimaryContainer\">#FFFFFF</color>\n\t<color name=\"kanade_secondary\">#353636</color>\n\t<color name=\"kanade_onSecondary\">#FFFFFF</color>\n\t<color name=\"kanade_secondaryContainer\">#6D6D6D</color>\n\t<color name=\"kanade_onSecondaryContainer\">#FFFFFF</color>\n\t<color name=\"kanade_tertiary\">#333737</color>\n\t<color name=\"kanade_onTertiary\">#FFFFFF</color>\n\t<color name=\"kanade_tertiaryContainer\">#6A6E6E</color>\n\t<color name=\"kanade_onTertiaryContainer\">#FFFFFF</color>\n\t<color name=\"kanade_error\">#740006</color>\n\t<color name=\"kanade_onError\">#FFFFFF</color>\n\t<color name=\"kanade_errorContainer\">#CF2C27</color>\n\t<color name=\"kanade_onErrorContainer\">#FFFFFF</color>\n\t<color name=\"kanade_background\">#FCF8F9</color>\n\t<color name=\"kanade_onBackground\">#1C1B1C</color>\n\t<color name=\"kanade_surface\">#FCF8F9</color>\n\t<color name=\"kanade_onSurface\">#111112</color>\n\t<color name=\"kanade_surfaceVariant\">#E4E1E8</color>\n\t<color name=\"kanade_onSurfaceVariant\">#36363B</color>\n\t<color name=\"kanade_outline\">#535258</color>\n\t<color name=\"kanade_outlineVariant\">#6D6C72</color>\n\t<color name=\"kanade_scrim\">#000000</color>\n\t<color name=\"kanade_inverseSurface\">#313031</color>\n\t<color name=\"kanade_inverseOnSurface\">#F4F0F1</color>\n\t<color name=\"kanade_inversePrimary\">#C6C5D6</color>\n\t<color name=\"kanade_primaryFixed\">#6C6C7B</color>\n\t<color name=\"kanade_onPrimaryFixed\">#FFFFFF</color>\n\t<color name=\"kanade_primaryFixedDim\">#545362</color>\n\t<color name=\"kanade_onPrimaryFixedVariant\">#FFFFFF</color>\n\t<color name=\"kanade_secondaryFixed\">#6D6D6D</color>\n\t<color name=\"kanade_onSecondaryFixed\">#FFFFFF</color>\n\t<color name=\"kanade_secondaryFixedDim\">#545555</color>\n\t<color name=\"kanade_onSecondaryFixedVariant\">#FFFFFF</color>\n\t<color name=\"kanade_tertiaryFixed\">#6A6E6E</color>\n\t<color name=\"kanade_onTertiaryFixed\">#FFFFFF</color>\n\t<color name=\"kanade_tertiaryFixedDim\">#525556</color>\n\t<color name=\"kanade_onTertiaryFixedVariant\">#FFFFFF</color>\n\t<color name=\"kanade_surfaceDim\">#C9C6C7</color>\n\t<color name=\"kanade_surfaceBright\">#FCF8F9</color>\n\t<color name=\"kanade_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"kanade_surfaceContainerLow\">#F7F3F4</color>\n\t<color name=\"kanade_surfaceContainer\">#EBE7E8</color>\n\t<color name=\"kanade_surfaceContainerHigh\">#DFDCDD</color>\n\t<color name=\"kanade_surfaceContainerHighest\">#D4D1D2</color>\n\t<!-- Mamimi -->\n\t<color name=\"mamimi_primary\">#465D91</color>\n\t<color name=\"mamimi_onPrimary\">#FFFFFF</color>\n\t<color name=\"mamimi_primaryContainer\">#D9E2FF</color>\n\t<color name=\"mamimi_onPrimaryContainer\">#2D4578</color>\n\t<color name=\"mamimi_secondary\">#575E71</color>\n\t<color name=\"mamimi_onSecondary\">#FFFFFF</color>\n\t<color name=\"mamimi_secondaryContainer\">#DBE2F9</color>\n\t<color name=\"mamimi_onSecondaryContainer\">#404659</color>\n\t<color name=\"mamimi_tertiary\">#725572</color>\n\t<color name=\"mamimi_onTertiary\">#FFFFFF</color>\n\t<color name=\"mamimi_tertiaryContainer\">#FDD7FB</color>\n\t<color name=\"mamimi_onTertiaryContainer\">#593E5A</color>\n\t<color name=\"mamimi_error\">#BA1A1A</color>\n\t<color name=\"mamimi_onError\">#FFFFFF</color>\n\t<color name=\"mamimi_errorContainer\">#FFDAD6</color>\n\t<color name=\"mamimi_onErrorContainer\">#93000A</color>\n\t<color name=\"mamimi_background\">#FAF8FF</color>\n\t<color name=\"mamimi_onBackground\">#1A1B20</color>\n\t<color name=\"mamimi_surface\">#FAF8FF</color>\n\t<color name=\"mamimi_onSurface\">#1A1B20</color>\n\t<color name=\"mamimi_surfaceVariant\">#E1E2EC</color>\n\t<color name=\"mamimi_onSurfaceVariant\">#44464F</color>\n\t<color name=\"mamimi_outline\">#757780</color>\n\t<color name=\"mamimi_outlineVariant\">#C5C6D0</color>\n\t<color name=\"mamimi_scrim\">#000000</color>\n\t<color name=\"mamimi_inverseSurface\">#2F3036</color>\n\t<color name=\"mamimi_inverseOnSurface\">#F1F0F7</color>\n\t<color name=\"mamimi_inversePrimary\">#AFC6FF</color>\n\t<color name=\"mamimi_primaryFixed\">#D9E2FF</color>\n\t<color name=\"mamimi_onPrimaryFixed\">#001944</color>\n\t<color name=\"mamimi_primaryFixedDim\">#AFC6FF</color>\n\t<color name=\"mamimi_onPrimaryFixedVariant\">#2D4578</color>\n\t<color name=\"mamimi_secondaryFixed\">#DBE2F9</color>\n\t<color name=\"mamimi_onSecondaryFixed\">#141B2C</color>\n\t<color name=\"mamimi_secondaryFixedDim\">#BFC6DC</color>\n\t<color name=\"mamimi_onSecondaryFixedVariant\">#404659</color>\n\t<color name=\"mamimi_tertiaryFixed\">#FDD7FB</color>\n\t<color name=\"mamimi_onTertiaryFixed\">#2A132C</color>\n\t<color name=\"mamimi_tertiaryFixedDim\">#DFBBDE</color>\n\t<color name=\"mamimi_onTertiaryFixedVariant\">#593E5A</color>\n\t<color name=\"mamimi_surfaceDim\">#DAD9E0</color>\n\t<color name=\"mamimi_surfaceBright\">#FAF8FF</color>\n\t<color name=\"mamimi_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"mamimi_surfaceContainerLow\">#F4F3FA</color>\n\t<color name=\"mamimi_surfaceContainer\">#EEEDF4</color>\n\t<color name=\"mamimi_surfaceContainerHigh\">#E8E7EF</color>\n\t<color name=\"mamimi_surfaceContainerHighest\">#E2E2E9</color>\n\t<!-- Miku -->\n\t<color name=\"miku_primary\">#00696D</color>\n\t<color name=\"miku_onPrimary\">#FFFFFF</color>\n\t<color name=\"miku_primaryContainer\">#50C1C6</color>\n\t<color name=\"miku_onPrimaryContainer\">#004C4F</color>\n\t<color name=\"miku_secondary\">#3F6566</color>\n\t<color name=\"miku_onSecondary\">#FFFFFF</color>\n\t<color name=\"miku_secondaryContainer\">#BFE7E9</color>\n\t<color name=\"miku_onSecondaryContainer\">#43696B</color>\n\t<color name=\"miku_tertiary\">#744E92</color>\n\t<color name=\"miku_onTertiary\">#FFFFFF</color>\n\t<color name=\"miku_tertiaryContainer\">#CBA0EB</color>\n\t<color name=\"miku_onTertiaryContainer\">#583375</color>\n\t<color name=\"miku_error\">#BA1A1A</color>\n\t<color name=\"miku_onError\">#FFFFFF</color>\n\t<color name=\"miku_errorContainer\">#FFDAD6</color>\n\t<color name=\"miku_onErrorContainer\">#93000A</color>\n\t<color name=\"miku_background\">#F5FAFA</color>\n\t<color name=\"miku_onBackground\">#171D1D</color>\n\t<color name=\"miku_surface\">#F5FAFA</color>\n\t<color name=\"miku_onSurface\">#171D1D</color>\n\t<color name=\"miku_surfaceVariant\">#D8E5E5</color>\n\t<color name=\"miku_onSurfaceVariant\">#3D4949</color>\n\t<color name=\"miku_outline\">#6D797A</color>\n\t<color name=\"miku_outlineVariant\">#BCC9C9</color>\n\t<color name=\"miku_scrim\">#000000</color>\n\t<color name=\"miku_inverseSurface\">#2C3132</color>\n\t<color name=\"miku_inverseOnSurface\">#EDF2F1</color>\n\t<color name=\"miku_inversePrimary\">#69D7DC</color>\n\t<color name=\"miku_primaryFixed\">#87F3F8</color>\n\t<color name=\"miku_onPrimaryFixed\">#002021</color>\n\t<color name=\"miku_primaryFixedDim\">#69D7DC</color>\n\t<color name=\"miku_onPrimaryFixedVariant\">#004F52</color>\n\t<color name=\"miku_secondaryFixed\">#C2EAEC</color>\n\t<color name=\"miku_onSecondaryFixed\">#002021</color>\n\t<color name=\"miku_secondaryFixedDim\">#A6CECF</color>\n\t<color name=\"miku_onSecondaryFixedVariant\">#274D4E</color>\n\t<color name=\"miku_tertiaryFixed\">#F2DAFF</color>\n\t<color name=\"miku_onTertiaryFixed\">#2D044A</color>\n\t<color name=\"miku_tertiaryFixedDim\">#E0B6FF</color>\n\t<color name=\"miku_onTertiaryFixedVariant\">#5B3679</color>\n\t<color name=\"miku_surfaceDim\">#D6DBDB</color>\n\t<color name=\"miku_surfaceBright\">#F5FAFA</color>\n\t<color name=\"miku_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"miku_surfaceContainerLow\">#F0F4F4</color>\n\t<color name=\"miku_surfaceContainer\">#EAEFEE</color>\n\t<color name=\"miku_surfaceContainerHigh\">#E4E9E9</color>\n\t<color name=\"miku_surfaceContainerHighest\">#DEE3E3</color>\n\t<!-- Mion -->\n\t<color name=\"mion_primary\">#3B693A</color>\n    <color name=\"mion_onPrimary\">#FFFFFF</color>\n    <color name=\"mion_primaryContainer\">#BCF0B4</color>\n    <color name=\"mion_onPrimaryContainer\">#235024</color>\n    <color name=\"mion_secondary\">#7B580D</color>\n    <color name=\"mion_onSecondary\">#FFFFFF</color>\n    <color name=\"mion_secondaryContainer\">#FFDEA8</color>\n    <color name=\"mion_onSecondaryContainer\">#5E4200</color>\n    <color name=\"mion_tertiary\">#316A42</color>\n    <color name=\"mion_onTertiary\">#FFFFFF</color>\n    <color name=\"mion_tertiaryContainer\">#B3F1BE</color>\n    <color name=\"mion_onTertiaryContainer\">#16512C</color>\n    <color name=\"mion_error\">#BA1A1A</color>\n    <color name=\"mion_onError\">#FFFFFF</color>\n    <color name=\"mion_errorContainer\">#FFDAD6</color>\n    <color name=\"mion_onErrorContainer\">#93000A</color>\n    <color name=\"mion_background\">#F7FBF1</color>\n    <color name=\"mion_onBackground\">#191D17</color>\n    <color name=\"mion_surface\">#F7FBF1</color>\n    <color name=\"mion_onSurface\">#191D17</color>\n    <color name=\"mion_surfaceVariant\">#DEE5D8</color>\n    <color name=\"mion_onSurfaceVariant\">#424940</color>\n    <color name=\"mion_outline\">#72796F</color>\n    <color name=\"mion_outlineVariant\">#C2C9BD</color>\n    <color name=\"mion_scrim\">#000000</color>\n    <color name=\"mion_inverseSurface\">#2D322C</color>\n    <color name=\"mion_inverseOnSurface\">#EEF2E9</color>\n    <color name=\"mion_inversePrimary\">#A1D39A</color>\n    <color name=\"mion_primaryFixed\">#BCF0B4</color>\n    <color name=\"mion_onPrimaryFixed\">#002204</color>\n    <color name=\"mion_primaryFixedDim\">#A1D39A</color>\n    <color name=\"mion_onPrimaryFixedVariant\">#235024</color>\n    <color name=\"mion_secondaryFixed\">#FFDEA8</color>\n    <color name=\"mion_onSecondaryFixed\">#271900</color>\n    <color name=\"mion_secondaryFixedDim\">#EEBF6D</color>\n    <color name=\"mion_onSecondaryFixedVariant\">#5E4200</color>\n    <color name=\"mion_tertiaryFixed\">#B3F1BE</color>\n    <color name=\"mion_onTertiaryFixed\">#00210C</color>\n    <color name=\"mion_tertiaryFixedDim\">#98D4A4</color>\n    <color name=\"mion_onTertiaryFixedVariant\">#16512C</color>\n    <color name=\"mion_surfaceDim\">#D8DBD2</color>\n    <color name=\"mion_surfaceBright\">#F7FBF1</color>\n    <color name=\"mion_surfaceContainerLowest\">#FFFFFF</color>\n    <color name=\"mion_surfaceContainerLow\">#F1F5EC</color>\n    <color name=\"mion_surfaceContainer\">#ECEFE6</color>\n    <color name=\"mion_surfaceContainerHigh\">#E6E9E0</color>\n    <color name=\"mion_surfaceContainerHighest\">#E0E4DB</color>\n\t<!-- Rikka -->\n\t<color name=\"rikka_primary\">#68548D</color>\n\t<color name=\"rikka_onPrimary\">#FFFFFF</color>\n\t<color name=\"rikka_primaryContainer\">#EBDCFF</color>\n\t<color name=\"rikka_onPrimaryContainer\">#503C74</color>\n\t<color name=\"rikka_secondary\">#635B70</color>\n\t<color name=\"rikka_onSecondary\">#FFFFFF</color>\n\t<color name=\"rikka_secondaryContainer\">#EADEF7</color>\n\t<color name=\"rikka_onSecondaryContainer\">#4B4358</color>\n\t<color name=\"rikka_tertiary\">#755B0B</color>\n\t<color name=\"rikka_onTertiary\">#FFFFFF</color>\n\t<color name=\"rikka_tertiaryContainer\">#FFDF95</color>\n\t<color name=\"rikka_onTertiaryContainer\">#594400</color>\n\t<color name=\"rikka_error\">#BA1A1A</color>\n\t<color name=\"rikka_onError\">#FFFFFF</color>\n\t<color name=\"rikka_errorContainer\">#FFDAD6</color>\n\t<color name=\"rikka_onErrorContainer\">#93000A</color>\n\t<color name=\"rikka_background\">#FEF7FF</color>\n\t<color name=\"rikka_onBackground\">#1D1B20</color>\n\t<color name=\"rikka_surface\">#FEF7FF</color>\n\t<color name=\"rikka_onSurface\">#1D1B20</color>\n\t<color name=\"rikka_surfaceVariant\">#E7E0EB</color>\n\t<color name=\"rikka_onSurfaceVariant\">#49454E</color>\n\t<color name=\"rikka_outline\">#7A757F</color>\n\t<color name=\"rikka_outlineVariant\">#CBC4CF</color>\n\t<color name=\"rikka_scrim\">#000000</color>\n\t<color name=\"rikka_inverseSurface\">#322F35</color>\n\t<color name=\"rikka_inverseOnSurface\">#F5EFF7</color>\n\t<color name=\"rikka_inversePrimary\">#D3BBFD</color>\n\t<color name=\"rikka_primaryFixed\">#EBDCFF</color>\n\t<color name=\"rikka_onPrimaryFixed\">#230F46</color>\n\t<color name=\"rikka_primaryFixedDim\">#D3BBFD</color>\n\t<color name=\"rikka_onPrimaryFixedVariant\">#503C74</color>\n\t<color name=\"rikka_secondaryFixed\">#EADEF7</color>\n\t<color name=\"rikka_onSecondaryFixed\">#1F182A</color>\n\t<color name=\"rikka_secondaryFixedDim\">#CDC2DB</color>\n\t<color name=\"rikka_onSecondaryFixedVariant\">#4B4358</color>\n\t<color name=\"rikka_tertiaryFixed\">#FFDF95</color>\n\t<color name=\"rikka_onTertiaryFixed\">#251A00</color>\n\t<color name=\"rikka_tertiaryFixedDim\">#E6C36C</color>\n\t<color name=\"rikka_onTertiaryFixedVariant\">#594400</color>\n\t<color name=\"rikka_surfaceDim\">#DED8E0</color>\n\t<color name=\"rikka_surfaceBright\">#FEF7FF</color>\n\t<color name=\"rikka_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"rikka_surfaceContainerLow\">#F8F1FA</color>\n\t<color name=\"rikka_surfaceContainer\">#F2ECF4</color>\n\t<color name=\"rikka_surfaceContainerHigh\">#EDE6EE</color>\n\t<color name=\"rikka_surfaceContainerHighest\">#E7E0E8</color>\n\t<!-- Sakura -->\n\t<color name=\"sakura_primary\">#8C4A60</color>\n\t<color name=\"sakura_onPrimary\">#FFFFFF</color>\n\t<color name=\"sakura_primaryContainer\">#FFD9E2</color>\n\t<color name=\"sakura_onPrimaryContainer\">#703348</color>\n\t<color name=\"sakura_secondary\">#74565F</color>\n\t<color name=\"sakura_onSecondary\">#FFFFFF</color>\n\t<color name=\"sakura_secondaryContainer\">#FFD9E2</color>\n\t<color name=\"sakura_onSecondaryContainer\">#5B3F47</color>\n\t<color name=\"sakura_tertiary\">#2C638B</color>\n\t<color name=\"sakura_onTertiary\">#FFFFFF</color>\n\t<color name=\"sakura_tertiaryContainer\">#CCE5FF</color>\n\t<color name=\"sakura_onTertiaryContainer\">#074B72</color>\n\t<color name=\"sakura_error\">#BA1A1A</color>\n\t<color name=\"sakura_onError\">#FFFFFF</color>\n\t<color name=\"sakura_errorContainer\">#FFDAD6</color>\n\t<color name=\"sakura_onErrorContainer\">#93000A</color>\n\t<color name=\"sakura_background\">#FFF8F8</color>\n\t<color name=\"sakura_onBackground\">#22191C</color>\n\t<color name=\"sakura_surface\">#FFF8F8</color>\n\t<color name=\"sakura_onSurface\">#22191C</color>\n\t<color name=\"sakura_surfaceVariant\">#F2DDE1</color>\n\t<color name=\"sakura_onSurfaceVariant\">#514347</color>\n\t<color name=\"sakura_outline\">#837377</color>\n\t<color name=\"sakura_outlineVariant\">#D5C2C6</color>\n\t<color name=\"sakura_scrim\">#000000</color>\n\t<color name=\"sakura_inverseSurface\">#372E30</color>\n\t<color name=\"sakura_inverseOnSurface\">#FDEDEF</color>\n\t<color name=\"sakura_inversePrimary\">#FFB1C8</color>\n\t<color name=\"sakura_primaryFixed\">#FFD9E2</color>\n\t<color name=\"sakura_onPrimaryFixed\">#3A071D</color>\n\t<color name=\"sakura_primaryFixedDim\">#FFB1C8</color>\n\t<color name=\"sakura_onPrimaryFixedVariant\">#703348</color>\n\t<color name=\"sakura_secondaryFixed\">#FFD9E2</color>\n\t<color name=\"sakura_onSecondaryFixed\">#2B151C</color>\n\t<color name=\"sakura_secondaryFixedDim\">#E3BDC6</color>\n\t<color name=\"sakura_onSecondaryFixedVariant\">#5B3F47</color>\n\t<color name=\"sakura_tertiaryFixed\">#CCE5FF</color>\n\t<color name=\"sakura_onTertiaryFixed\">#001D31</color>\n\t<color name=\"sakura_tertiaryFixedDim\">#99CCFA</color>\n\t<color name=\"sakura_onTertiaryFixedVariant\">#074B72</color>\n\t<color name=\"sakura_surfaceDim\">#E6D6D9</color>\n\t<color name=\"sakura_surfaceBright\">#FFF8F8</color>\n\t<color name=\"sakura_surfaceContainerLowest\">#FFFFFF</color>\n\t<color name=\"sakura_surfaceContainerLow\">#FFF0F2</color>\n\t<color name=\"sakura_surfaceContainer\">#FAEAED</color>\n\t<color name=\"sakura_surfaceContainerHigh\">#F5E4E7</color>\n\t<color name=\"sakura_surfaceContainerHighest\">#EFDFE1</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/constants.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"url_github\" translatable=\"false\">https://github.com/KotatsuApp/Kotatsu</string>\n\t<string name=\"url_telegram_web\" translatable=\"false\">https://t.me/kotatsuapp</string>\n\t<string name=\"url_telegram\" translatable=\"false\">tg://resolve?domain=kotatsuapp</string>\n\t<string name=\"url_weblate\" translatable=\"false\">https://hosted.weblate.org/engage/kotatsu</string>\n\t<string name=\"url_user_manual\" translatable=\"false\">https://kotatsu.app/manuals/guides/getting-started/</string>\n\t<string name=\"url_error_report\" translatable=\"false\">https://bugs.kotatsu.app/report</string>\n\t<string name=\"account_type_sync\" translatable=\"false\">org.kotatsu.sync</string>\n\t<string name=\"github_updates_repo\" translatable=\"false\">KotatsuApp/Kotatsu</string>\n\t<string name=\"shikimori_clientId\" translatable=\"false\">Mw6F0tPEOgyV7F9U9Twg50Q8SndMY7hzIOfXg0AX_XU</string>\n\t<string name=\"shikimori_clientSecret\" translatable=\"false\">euBMt1GGRSDpVIFQVPxZrO7Kh6X4gWyv0dABuj4B-M8</string>\n\t<string name=\"anilist_clientId\" translatable=\"false\">9887</string>\n\t<string name=\"anilist_clientSecret\" translatable=\"false\">wrMqFosItQWsmB8dtAHfIFPDt15FfQi2ZGiKkJoW</string>\n\t<string name=\"mal_clientId\" translatable=\"false\">6cd8e6349e9a36bc1fc1ab97703c9fd1</string>\n\t<string name=\"kitsu_clientId\" translatable=\"false\">dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd</string>\n\t<string name=\"kitsu_clientSecret\" translatable=\"false\">54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151</string>\n\t<string name=\"acra_login\" translatable=\"false\">zPALLBPdpn5mnCB4</string>\n\t<string name=\"acra_password\" translatable=\"false\">kgpuhoNJpSsQDCwu</string>\n\t<string name=\"sync_authority_history\" translatable=\"false\">org.koitharu.kotatsu.history</string>\n\t<string name=\"sync_authority_favourites\" translatable=\"false\">org.koitharu.kotatsu.favourites</string>\n\t<string name=\"tg_backup_bot_name\" translatable=\"false\">kotatsu_backup_bot</string>\n\t<string name=\"discord_app_id\" translatable=\"false\">1395464028611940393</string>\n\t<string name=\"app_icon_url\" translatable=\"false\">https://raw.githubusercontent.com/KotatsuApp/Kotatsu/refs/heads/devel/metadata/en-US/icon.png</string>\n\t<string-array name=\"values_theme\" translatable=\"false\">\n\t\t<item>-1</item>\n\t\t<item>1</item>\n\t\t<item>2</item>\n\t</string-array>\n\t<string-array name=\"values_track_sources\" translatable=\"false\">\n\t\t<item>favourites</item>\n\t\t<item>history</item>\n\t</string-array>\n\t<string-array name=\"values_track_sources_default\" translatable=\"false\">\n\t\t<item>favourites</item>\n\t</string-array>\n\t<string-array name=\"values_network_policy\" translatable=\"false\">\n\t\t<item>1</item>\n\t\t<item>2</item>\n\t\t<item>0</item>\n\t</string-array>\n\t<string-array name=\"values_image_proxies\" translatable=\"false\">\n\t\t<item>-1</item>\n\t\t<item>0</item>\n\t\t<item>1</item>\n\t</string-array>\n\t<string-array name=\"sync_url_list\" translatable=\"false\">\n\t\t<item>https://sync.kotatsu.app</item>\n\t\t<item>https://moe.shirizu.org</item>\n\t\t<item>http://54.254.71.100</item>\n\t\t<item>http://86.57.183.214:8081</item>\n\t</string-array>\n\t<string-array name=\"values_proxy_types\" translatable=\"false\">\n\t\t<item>DIRECT</item>\n\t\t<item>HTTP</item>\n\t\t<item>SOCKS</item>\n\t</string-array>\n\t<string-array name=\"values_backup_frequency\" translatable=\"false\">\n\t\t<item>0.25</item>\n\t\t<item>1</item>\n\t\t<item>2</item>\n\t\t<item>7</item>\n\t\t<item>14</item>\n\t\t<item>30</item>\n\t</string-array>\n\t<string-array name=\"details_tabs_values\" translatable=\"false\">\n\t\t<item>-1</item>\n\t\t<item>0</item>\n\t\t<item>1</item>\n\t\t<item>2</item>\n\t</string-array>\n\t<string-array name=\"values_tracker_frequency\" translatable=\"false\">\n\t\t<item>-1</item>\n\t\t<item>0.4</item>\n\t\t<item>1</item>\n\t\t<item>2</item>\n\t</string-array>\n\t<string-array name=\"values_reader_crop\" translatable=\"false\">\n\t\t<item>1</item>\n\t\t<item>2</item>\n\t</string-array>\n\t<string-array name=\"values_list_badges\" translatable=\"false\">\n\t\t<!-- MangaListMapper flags -->\n\t\t<item>4</item>\n\t\t<item>1</item>\n\t</string-array>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<!-- Common dimensions -->\n\t<dimen name=\"margin_normal\">16dp</dimen>\n\t<dimen name=\"margin_small\">8dp</dimen>\n\t<!-- List spacing -->\n\t<dimen name=\"list_spacing_small\">6dp</dimen>\n\t<dimen name=\"list_spacing_normal\">8dp</dimen>\n\t<dimen name=\"list_spacing_large\">12dp</dimen>\n\t<!-- Navigation -->\n\t<dimen name=\"nav_bar_height_compact\">0dp</dimen>\n\n\t<dimen name=\"grid_spacing\">8dp</dimen>\n\t<dimen name=\"list_spacing\">8dp</dimen>\n\t<dimen name=\"grid_spacing_outer\">2dp</dimen>\n\t<dimen name=\"grid_spacing_outer_double\">4dp</dimen>\n\t<dimen name=\"manga_list_details_item_height\">120dp</dimen>\n\t<dimen name=\"recommendation_item_height\">90dp</dimen>\n\t<dimen name=\"bookmark_item_height\">120dp</dimen>\n\t<dimen name=\"chapter_list_item_height\">48dp</dimen>\n\t<dimen name=\"preferred_grid_width\">120dp</dimen>\n\t<dimen name=\"small_grid_width\">92dp</dimen>\n\t<dimen name=\"smaller_grid_width\">102dp</dimen>\n\t<dimen name=\"list_footer_height_outer\">48dp</dimen>\n\t<dimen name=\"screen_padding\">16dp</dimen>\n\t<dimen name=\"selection_stroke_width\">2dp</dimen>\n\t<dimen name=\"list_selector_corner\">12dp</dimen>\n\t<dimen name=\"toolbar_button_margin\">10dp</dimen>\n\t<dimen name=\"widget_cover_height\">116dp</dimen>\n\t<dimen name=\"widget_cover_width\">84dp</dimen>\n\t<dimen name=\"scrobbling_list_spacing\">12dp</dimen>\n\t<dimen name=\"explore_grid_width\">120dp</dimen>\n\t<dimen name=\"chapter_grid_width\">80dp</dimen>\n\t<dimen name=\"side_card_offset\">8dp</dimen>\n\t<dimen name=\"webtoon_pages_gap\">24dp</dimen>\n\t<dimen name=\"details_bs_peek_height\">120dp</dimen>\n\t<dimen name=\"spinner_height\">56dp</dimen>\n\t<dimen name=\"category_covers_height\">86dp</dimen>\n\n\t<dimen name=\"search_suggestions_manga_height\">142dp</dimen>\n\t<dimen name=\"search_suggestions_manga_spacing\">6dp</dimen>\n\n\t<dimen name=\"card_indicator_size\">32dp</dimen>\n\t<dimen name=\"card_indicator_size_small\">24dp</dimen>\n\t<dimen name=\"card_indicator_offset\">8dp</dimen>\n\n\t<dimen name=\"chapter_check_size\">16dp</dimen>\n\t<dimen name=\"chapter_check_offset\">6dp</dimen>\n\n\t<dimen name=\"appwidget_corner_radius_inner\">20dp</dimen>\n\t<dimen name=\"appwidget_corner_radius_background\">28dp</dimen>\n\n\t<!-- FastScroller -->\n\t<dimen name=\"fastscroll_bubble_radius\">44dp</dimen>\n\t<dimen name=\"fastscroll_bubble_size\">88dp</dimen>\n\t<dimen name=\"fastscroll_bubble_text_size\">48sp</dimen>\n\t<dimen name=\"fastscroll_bubble_padding\">16dp</dimen>\n\n\t<dimen name=\"fastscroll_bubble_radius_small\">32dp</dimen>\n\t<dimen name=\"fastscroll_bubble_size_small\">36dp</dimen>\n\t<dimen name=\"fastscroll_bubble_text_size_small\">24sp</dimen>\n\t<dimen name=\"fastscroll_bubble_padding_small\">12dp</dimen>\n\n\t<dimen name=\"fastscroll_handle_height\">58dp</dimen>\n\t<dimen name=\"fastscroll_handle_width\">6dp</dimen>\n\t<dimen name=\"fastscroll_handle_radius\">4dp</dimen>\n\n\t<dimen name=\"fastscroll_track_width\">1dp</dimen>\n\n\t<dimen name=\"fastscroll_scrollbar_margin_top\">8dp</dimen>\n\t<dimen name=\"fastscroll_scrollbar_margin_bottom\">8dp</dimen>\n\t<dimen name=\"fastscroll_scrollbar_padding_start\">12dp</dimen>\n\t<dimen name=\"fastscroll_scrollbar_padding_end\">6dp</dimen>\n\n\t<dimen name=\"m3_side_sheet_width\">400dp</dimen>\n\t<dimen name=\"m3_card_corner\">12dp</dimen>\n\n\t<dimen name=\"reader_scroll_delta_min\">200dp</dimen>\n\n\t<dimen name=\"badge_offset\">12dp</dimen>\n\n\t<dimen name=\"chip_icon_corner\">3dp</dimen>\n\t<dimen name=\"read_button_min_width\">92dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<item name=\"toolbar\" type=\"id\" />\n\t<item name=\"container\" type=\"id\" />\n\t<item name=\"action_tracker\" type=\"id\" />\n\t<item name=\"fast_scroller\" type=\"id\" />\n\t<item name=\"group_branches\" type=\"id\" />\n\t<item name=\"layout_tip\" type=\"id\" />\n\t<item name=\"group_period\" type=\"id\" />\n\t<!-- Navigation -->\n\t<item name=\"nav_history\" type=\"id\" />\n\t<item name=\"nav_favorites\" type=\"id\" />\n\t<item name=\"nav_local\" type=\"id\" />\n\t<item name=\"nav_explore\" type=\"id\" />\n\t<item name=\"nav_feed\" type=\"id\" />\n\t<item name=\"nav_updated\" type=\"id\" />\n\t<item name=\"nav_suggestions\" type=\"id\" />\n\t<item name=\"nav_bookmarks\" type=\"id\" />\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<integer name=\"config_longAnimTime\">1000</integer>\n\t<integer name=\"config_defaultAnimTime\">300</integer>\n\t<integer name=\"config_shorterAnimTime\">150</integer>\n\t<integer name=\"config_tinyAnimTime\">50</integer>\n\n\t<integer name=\"manga_badge_max_character_count\">3</integer>\n\t<integer name=\"explore_buttons_columns\">2</integer>\n\t<integer name=\"details_description_lines\">4</integer>\n\t<integer name=\"details_title_lines\">4</integer>\n\n\t<integer name=\"splash_screen_duration\">450</integer>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<plurals name=\"items\">\n\t\t<item quantity=\"one\">%1$d item</item>\n\t\t<item quantity=\"other\">%1$d items</item>\n\t</plurals>\n\t<plurals name=\"new_chapters\">\n\t\t<item quantity=\"one\">%1$d new chapter</item>\n\t\t<item quantity=\"other\">%1$d new chapters</item>\n\t</plurals>\n\t<plurals name=\"chapters\">\n\t\t<item quantity=\"one\">%1$d chapter</item>\n\t\t<item quantity=\"other\">%1$d chapters</item>\n\t</plurals>\n\t<plurals name=\"minutes_ago\">\n\t\t<item quantity=\"one\">%1$d minute ago</item>\n\t\t<item quantity=\"other\">%1$d minutes ago</item>\n\t</plurals>\n\t<plurals name=\"hours_ago\">\n\t\t<item quantity=\"one\">%1$d hour ago</item>\n\t\t<item quantity=\"other\">%1$d hours ago</item>\n\t</plurals>\n\t<plurals name=\"days_ago\">\n\t\t<item quantity=\"one\">%1$d day ago</item>\n\t\t<item quantity=\"other\">%1$d days ago</item>\n\t</plurals>\n\t<plurals name=\"months_ago\">\n\t\t<item quantity=\"one\">%1$d month ago</item>\n\t\t<item quantity=\"other\">%1$d months ago</item>\n\t</plurals>\n\t<plurals name=\"hours\">\n\t\t<item quantity=\"one\">%1$d hour</item>\n\t\t<item quantity=\"other\">%1$d hours</item>\n\t</plurals>\n\t<plurals name=\"minutes\">\n\t\t<item quantity=\"one\">%1$d minute</item>\n\t\t<item quantity=\"other\">%1$d minutes</item>\n\t</plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"app_name\" translatable=\"false\">Kotatsu</string>\n    <string name=\"local_storage\">Local storage</string>\n    <string name=\"favourites\">Favorites</string>\n    <string name=\"history\">History</string>\n    <string name=\"error_occurred\">An error occurred</string>\n    <string name=\"network_error\">Network error</string>\n    <string name=\"details\">Details</string>\n    <string name=\"chapters\">Chapters</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"detailed_list\">Detailed list</string>\n    <string name=\"grid\">Grid</string>\n    <string name=\"list_mode\">List mode</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"remote_sources\">Manga sources</string>\n    <string name=\"loading_\">Loading…</string>\n    <string name=\"computing_\">Computing…</string>\n    <string name=\"chapter_d_of_d\">Chapter %1$d of %2$d</string>\n    <string name=\"close\">Close</string>\n    <string name=\"try_again\">Try again</string>\n    <!-- Should be short -->\n    <string name=\"retry\">Retry</string>\n    <string name=\"clear_history\">Clear history</string>\n    <string name=\"nothing_found\">Nothing found</string>\n    <string name=\"history_is_empty\">No history yet</string>\n    <string name=\"read\">Read</string>\n    <string name=\"you_have_not_favourites_yet\">No favorites yet</string>\n    <string name=\"add_to_favourites\">Favorite this</string>\n    <string name=\"add_new_category\">New category</string>\n    <string name=\"add\">Add</string>\n    <string name=\"save\">Save</string>\n    <string name=\"share\">Share</string>\n    <string name=\"create_shortcut\">Create shortcut</string>\n    <string name=\"share_s\">Share %s</string>\n    <string name=\"search\">Search</string>\n    <string name=\"search_manga\">Search manga</string>\n    <string name=\"manga_downloading_\">Downloading…</string>\n    <string name=\"processing_\">Processing…</string>\n    <string name=\"download_complete\">Downloaded</string>\n    <string name=\"downloads\">Downloads</string>\n    <string name=\"by_name\">Name</string>\n    <string name=\"popular\">Popular</string>\n    <string name=\"updated\">Updated</string>\n    <string name=\"newest\">Newest</string>\n    <string name=\"by_rating\">Rating</string>\n    <string name=\"sort_order\">Sorting order</string>\n    <string name=\"filter\">Filter</string>\n    <string name=\"saved_filters\">Saved filters</string>\n    <string name=\"theme\">Theme</string>\n    <string name=\"light\">Light</string>\n    <string name=\"dark\">Dark</string>\n    <!-- Should be as abstract as possible -->\n    <string name=\"follow_system\">Follow system</string>\n    <string name=\"pages\">Pages</string>\n    <string name=\"clear\">Clear</string>\n    <string name=\"remove\">Remove</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" deleted from local storage</string>\n    <string name=\"save_page\">Save page</string>\n    <string name=\"page_saved\">Page saved</string>\n    <string name=\"pages_saved\">Pages saved</string>\n    <string name=\"share_image\">Share image</string>\n    <string name=\"_import\">Import</string>\n    <string name=\"delete\">Delete</string>\n    <string name=\"operation_not_supported\">This operation is not supported</string>\n    <string name=\"text_file_not_supported\">Either pick a ZIP or CBZ file.</string>\n    <string name=\"no_description\">No description</string>\n    <string name=\"clear_pages_cache\">Clear page cache</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Read mode</string>\n    <string name=\"grid_size\">Grid size</string>\n    <string name=\"search_on_s\">Search on %s</string>\n    <string name=\"delete_manga\">Delete manga</string>\n    <string name=\"text_delete_local_manga\">Permanently delete \\\"%s\\\" from device?</string>\n    <string name=\"reader_settings\">Reader settings</string>\n    <string name=\"switch_pages\">Switch pages</string>\n    <string name=\"_continue\">Continue</string>\n    <string name=\"error\">Error</string>\n    <string name=\"clear_thumbs_cache\">Clear thumbnails cache</string>\n    <string name=\"clear_search_history\">Clear search history</string>\n    <string name=\"search_history_cleared\">Cleared</string>\n    <string name=\"internal_storage\">Internal storage</string>\n    <string name=\"external_storage\">External storage</string>\n    <string name=\"domain\">Domain</string>\n    <string name=\"app_update_available\">A new version of the app is available</string>\n    <string name=\"open_in_browser\">Open in web browser</string>\n    <string name=\"notifications\">Notifications</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d of %2$d on</string>\n    <string name=\"new_chapters\">New chapters</string>\n    <string name=\"download\">Download</string>\n    <string name=\"notifications_settings\">Notifications settings</string>\n    <string name=\"notification_sound\">Notification sound</string>\n    <string name=\"light_indicator\">LED indicator</string>\n    <string name=\"vibration\">Vibration</string>\n    <string name=\"favourites_categories\">Favorite categories</string>\n    <string name=\"remove_category\">Remove</string>\n    <string name=\"text_empty_holder_primary\">It\\'s kind of empty here…</string>\n    <string name=\"text_search_holder_secondary\">Try to reformulate the query.</string>\n    <string name=\"text_history_holder_primary\">What you read will be displayed here</string>\n    <string name=\"text_history_holder_secondary\">Find what to read in the «Explore» section</string>\n    <string name=\"text_empty_holder_secondary_filtered\">There are no manga matching the filters you selected</string>\n    <string name=\"text_local_holder_primary\">Save something first</string>\n    <string name=\"text_local_holder_secondary\">Save something from an online catalog or import it from a file.</string>\n    <string name=\"manga_shelf\">Shelf</string>\n    <string name=\"recent_manga\">Recent</string>\n    <string name=\"pages_animation\">Page animation</string>\n    <string name=\"manga_save_location\">Downloads folder</string>\n    <string name=\"not_available\">Not available</string>\n    <string name=\"cannot_find_available_storage\">No available storage</string>\n    <string name=\"other_storage\">Other storage</string>\n    <string name=\"done\">Done</string>\n    <string name=\"all_favourites\">All favorites</string>\n    <string name=\"favourites_category_empty\">Empty category</string>\n    <string name=\"read_later\">Read later</string>\n    <string name=\"updates\">Updates</string>\n    <string name=\"text_feed_holder\">New chapters of what you are reading are shown here</string>\n    <string name=\"search_results\">Search results</string>\n    <string name=\"new_version_s\">New version: %s</string>\n    <string name=\"size_s\">Size: %s</string>\n    <string name=\"clear_updates_feed\">Clear updates feed</string>\n    <string name=\"updates_feed_cleared\">Cleared</string>\n    <string name=\"rotate_screen\">Rotate screen</string>\n    <string name=\"update\">Update</string>\n    <string name=\"feed_will_update_soon\">Feed update will start soon</string>\n    <string name=\"track_sources\">Look for updates</string>\n    <string name=\"dont_check\">Don\\'t check</string>\n    <string name=\"enter_password\">Enter password</string>\n    <string name=\"wrong_password\">Wrong password</string>\n    <string name=\"protect_application\">Protect the app</string>\n    <string name=\"protect_application_summary\">Ask for password when starting Kotatsu</string>\n    <string name=\"repeat_password\">Repeat the password</string>\n    <string name=\"passwords_mismatch\">Mismatching passwords</string>\n    <string name=\"about\">About</string>\n    <string name=\"app_version\">Version %s</string>\n    <string name=\"check_for_updates\">Check for updates</string>\n    <string name=\"no_update_available\">No updates available</string>\n    <string name=\"right_to_left\">Right-to-left</string>\n    <string name=\"create_category\">New category</string>\n    <string name=\"scale_mode\">Scale mode</string>\n    <string name=\"zoom_mode_fit_center\">Fit center</string>\n    <string name=\"zoom_mode_fit_height\">Fit to height</string>\n    <string name=\"zoom_mode_fit_width\">Fit to width</string>\n    <string name=\"zoom_mode_keep_start\">Keep at start</string>\n    <string name=\"black_dark_theme\">Black</string>\n    <string name=\"black_dark_theme_summary\">Uses less power on AMOLED screens</string>\n    <string name=\"backup_restore\">Backup and restore</string>\n    <string name=\"create_backup\">Create data backup</string>\n    <string name=\"restore_backup\">Restore from backup</string>\n    <string name=\"data_restored\">Restored</string>\n    <string name=\"preparing_\">Preparing…</string>\n    <string name=\"file_not_found\">File not found</string>\n    <string name=\"data_restored_success\">All data was restored</string>\n    <string name=\"data_restored_with_errors\">The data was restored, but there are errors</string>\n    <string name=\"backup_information\">You can create backup of your history and favorites and restore it</string>\n    <string name=\"just_now\">Just now</string>\n    <string name=\"yesterday\">Yesterday</string>\n    <string name=\"long_ago\">Long ago</string>\n    <string name=\"group\">Group</string>\n    <string name=\"today\">Today</string>\n    <string name=\"tap_to_try_again\">Tap to try again</string>\n    <string name=\"reader_mode_hint\">The chosen configuration will be remembered for this manga</string>\n    <string name=\"silent\">Silent</string>\n    <string name=\"captcha_required\">CAPTCHA required</string>\n    <string name=\"captcha_solve\">Solve</string>\n    <string name=\"clear_cookies\">Clear cookies</string>\n    <string name=\"cookies_cleared\">All cookies were removed</string>\n    <string name=\"clear_feed\">Clear feed</string>\n    <string name=\"text_clear_updates_feed_prompt\">Clear all update history permanently?</string>\n    <string name=\"check_for_new_chapters\">Check for new chapters</string>\n    <string name=\"reverse\">Reverse</string>\n    <string name=\"chapters_grid_view\">Grid view</string>\n    <string name=\"sign_in\">Sign in</string>\n    <string name=\"auth_required\">Sign in to view this content</string>\n    <string name=\"default_s\">Default: %s</string>\n    <string name=\"next\">Next</string>\n    <string name=\"protect_application_subtitle\">Enter a password to start the app with</string>\n    <string name=\"confirm\">Confirm</string>\n    <string name=\"password_length_hint\">The password must be 4 characters or more</string>\n    <string name=\"text_clear_search_history_prompt\">Remove all recent search queries permanently?</string>\n    <string name=\"welcome\">Welcome</string>\n    <string name=\"backup_saved\">Backup saved</string>\n    <string name=\"tracker_warning\">Some devices have different system behavior, which may break background tasks.</string>\n    <string name=\"read_more\">Read more</string>\n    <string name=\"queued\">Queued</string>\n    <string name=\"chapter_is_missing\">The chapter is missing</string>\n    <string name=\"about_app_translation_summary\">Translate this app</string>\n    <string name=\"about_app_translation\">Translation</string>\n    <string name=\"auth_complete\">Authorized</string>\n    <string name=\"auth_not_supported_by\">Logging in on %s is not supported</string>\n    <string name=\"text_clear_cookies_prompt\">You will be logged out from all sources</string>\n    <string name=\"genres\">Genres</string>\n    <string name=\"state_finished\">Finished</string>\n    <string name=\"state_ongoing\">Ongoing</string>\n    <string name=\"system_default\">Default</string>\n    <string name=\"exclude_nsfw_from_history\">Exclude NSFW manga from history</string>\n    <string name=\"show_pages_numbers\">Numbered pages</string>\n    <string name=\"screenshots_policy\">Screenshot policy</string>\n    <string name=\"screenshots_allow\">Allow</string>\n    <string name=\"screenshots_block_nsfw\">Block on NSFW</string>\n    <string name=\"screenshots_block_all\">Always block</string>\n    <string name=\"suggestions\">Suggestions</string>\n    <string name=\"suggestions_enable\">Enable suggestions</string>\n    <string name=\"suggestions_summary\">Suggest manga based on your preferences</string>\n    <string name=\"suggestions_info\">All data is only analyzed locally on this device and never sent anywhere.</string>\n    <string name=\"text_suggestion_holder\">Start reading manga and you will get personalized suggestions</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Do not suggest NSFW manga</string>\n    <string name=\"enabled\">Enabled</string>\n    <string name=\"disabled\">Disabled</string>\n    <string name=\"reset_filter\">Reset filter</string>\n    <string name=\"enter_name\">Enter name</string>\n    <string name=\"pinned_sources_only\">Pinned sources only</string>\n    <string name=\"hide_empty_sources\">Hide empty sources</string>\n    <string name=\"onboard_text\">Select languages which you want to read manga. You can change it later in settings.</string>\n    <string name=\"never\">Never</string>\n    <string name=\"only_using_wifi\">Only on Wi-Fi</string>\n    <string name=\"always\">Always</string>\n    <string name=\"preload_pages\">Preload pages</string>\n    <string name=\"logged_in_as\">Logged in as %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"various_languages\">Various languages</string>\n    <string name=\"search_chapters\">Find chapter</string>\n    <string name=\"chapters_empty\">No chapters in this manga</string>\n    <string name=\"percent_string_pattern\" translatable=\"false\">%1$s%%</string>\n    <string name=\"appearance\">Appearance</string>\n    <string name=\"suggestions_updating\">Suggestions updating</string>\n    <string name=\"suggestions_excluded_genres\">Exclude genres</string>\n    <string name=\"suggestions_excluded_genres_summary\">Specify genres that you do not want to see in the suggestions</string>\n    <string name=\"text_delete_local_manga_batch\">Delete selected items from device permanently?</string>\n    <string name=\"removal_completed\">Removal completed</string>\n    <string name=\"shikimori\" translatable=\"false\">Shikimori</string>\n    <string name=\"anilist\" translatable=\"false\">AniList</string>\n    <string name=\"download_slowdown\">Download slowdown</string>\n    <string name=\"download_slowdown_summary\">Helps avoid blocking your IP address</string>\n    <string name=\"local_manga_processing\">Saved manga processing</string>\n    <string name=\"chapters_will_removed_background\">Chapters will be removed in the background</string>\n    <string name=\"canceled\">Canceled</string>\n    <string name=\"account_already_exists\">Account already exists</string>\n    <string name=\"back\">Back</string>\n    <string name=\"sync\">Synchronization</string>\n    <string name=\"sync_title\">Sync your data</string>\n    <string name=\"email_enter_hint\">Enter your email to continue</string>\n    <string name=\"hide\">Hide</string>\n    <string name=\"new_sources_text\">New manga sources are available</string>\n    <string name=\"check_new_chapters_title\">Check for new chapters and notify about it</string>\n    <string name=\"show_notification_new_chapters_on\">You will receive notifications about updates of manga you are reading</string>\n    <string name=\"show_notification_new_chapters_off\">You will not receive notifications but new chapters will be highlighted in the lists</string>\n    <string name=\"notifications_enable\">Enable notifications</string>\n    <string name=\"name\">Name</string>\n    <string name=\"edit\">Edit</string>\n    <string name=\"edit_category\">Edit category</string>\n    <string name=\"tracking\">Tracking</string>\n    <string name=\"empty_favourite_categories\">No favorite categories</string>\n    <string name=\"logout\">Log out</string>\n    <string name=\"bookmark_add\">Add bookmark</string>\n    <string name=\"bookmark_remove\">Remove bookmark</string>\n    <string name=\"bookmarks\">Bookmarks</string>\n    <string name=\"bookmark_removed\">Bookmark removed</string>\n    <string name=\"bookmark_added\">Bookmark added</string>\n    <string name=\"undo\">Undo</string>\n    <string name=\"removed_from_history\">Removed from history</string>\n    <string name=\"dns_over_https\">DNS over HTTPS</string>\n    <string name=\"default_mode\">Default mode</string>\n    <string name=\"detect_reader_mode\">Autodetect reader mode</string>\n    <string name=\"detect_reader_mode_summary\">Automatically detect if manga is webtoon</string>\n    <string name=\"disable_battery_optimization\">Disable battery optimization</string>\n    <string name=\"disable_battery_optimization_summary\">Helps with background updates checks</string>\n    <string name=\"crash_text\">Something went wrong. Please submit a bug report to the developers to help us fix it.</string>\n    <string name=\"send\">Send</string>\n    <string name=\"status_planned\">Planned</string>\n    <string name=\"status_reading\">Reading</string>\n    <string name=\"status_re_reading\">Re-reading</string>\n    <string name=\"status_completed\">Completed</string>\n    <string name=\"status_on_hold\">On hold</string>\n    <string name=\"status_dropped\">Dropped</string>\n    <string name=\"disable_all\">Disable all</string>\n    <string name=\"use_fingerprint\">Use biometric if available</string>\n    <string name=\"appwidget_shelf_description\">Manga from your favorites</string>\n    <string name=\"appwidget_recent_description\">Your recently read manga</string>\n    <string name=\"report\">Report</string>\n    <string name=\"show_reading_indicators\">Show reading progress indicators</string>\n    <string name=\"data_deletion\">Data deletion</string>\n    <string name=\"show_reading_indicators_summary\">Show percentage read in history and favorites</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga marked as NSFW will never be added to the history and your progress will not be saved</string>\n    <string name=\"clear_cookies_summary\">Can help in case of some issues. All authorizations will be invalidated</string>\n    <string name=\"show_all\">Show all</string>\n    <string name=\"invalid_domain_message\">Invalid domain</string>\n    <string name=\"invalid_server_address_message\">Invalid server address</string>\n    <string name=\"select_range\">Select range</string>\n    <string name=\"clear_all_history\">Clear all history</string>\n    <string name=\"last_2_hours\">Last 2 hours</string>\n    <string name=\"history_cleared\">History cleared</string>\n    <string name=\"manage\">Manage</string>\n    <string name=\"no_bookmarks_yet\">No bookmarks yet</string>\n    <string name=\"no_bookmarks_summary\">You can create bookmark while reading manga</string>\n    <string name=\"bookmarks_removed\">Bookmarks removed</string>\n    <string name=\"no_manga_sources\">No manga sources</string>\n    <string name=\"no_manga_sources_text\">Enable manga sources to read manga online</string>\n    <string name=\"random\">Random</string>\n    <string name=\"categories_delete_confirm\">Are you sure you want to delete the selected favorite categories?\\nAll manga in it will be lost and this cannot be undone.</string>\n    <string name=\"reorder\">Reorder</string>\n    <string name=\"empty\">Empty</string>\n    <string name=\"explore\">Explore</string>\n    <string name=\"confirm_exit\">Press \"Back\" again to exit</string>\n    <string name=\"exit_confirmation_summary\">Press \"Back\" twice to exit the app</string>\n    <string name=\"exit_confirmation\">Exit confirmation</string>\n    <string name=\"saved_manga\">Saved manga</string>\n    <string name=\"pages_cache\">Pages cache</string>\n    <string name=\"other_cache\">Other cache</string>\n    <string name=\"storage_usage\">Storage usage</string>\n    <string name=\"available\">Available</string>\n    <string name=\"memory_usage_pattern\" translatable=\"false\">%1$s - %2$s</string>\n    <string name=\"removed_from_favourites\">Removed from favorites</string>\n    <string name=\"options\">Options</string>\n    <string name=\"not_found_404\">Content not found or removed</string>\n    <string name=\"download_summary_pattern\" translatable=\"false\">%1$s · %2$s</string>\n    <string name=\"incognito_mode\">Incognito mode</string>\n    <string name=\"no_chapters\">No chapters</string>\n    <string name=\"automatic_scroll\">Automatic scroll</string>\n    <string name=\"reader_info_pattern\">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Show information bar in reader</string>\n    <string name=\"comics_archive\">Comics archive</string>\n    <string name=\"folder_with_images\">Folder with images</string>\n    <string name=\"importing_manga\">Importing manga</string>\n    <string name=\"import_completed\">Import completed</string>\n    <string name=\"import_completed_hint\">You can delete the original file from storage to save space</string>\n    <string name=\"import_will_start_soon\">Import will start soon</string>\n    <string name=\"feed\">Feed</string>\n    <string name=\"manga_error_description_pattern\">Error details:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Try to &lt;a href=\"%2$s\"&gt;open manga in a web browser&lt;/a&gt; to ensure it is available on its source&lt;br&gt;2. Make sure you are using the &lt;a href=\"kotatsu://about\"&gt;latest version of Kotatsu&lt;/a&gt;&lt;br&gt;3. If it is available, send an error report to the developers.</string>\n    <string name=\"history_shortcuts\">Show recent manga shortcuts</string>\n    <string name=\"history_shortcuts_summary\">Make recent manga available by long pressing on application icon</string>\n    <string name=\"reader_control_ltr_summary\">Do not adjust the page switching direction to the reader mode, e. g. pressing the right key always switches to the next page. This option affects only hardware input devices</string>\n    <string name=\"reader_control_ltr\">Ergonomic reader control</string>\n    <string name=\"color_correction\">Color correction</string>\n    <string name=\"brightness\">Brightness</string>\n    <string name=\"contrast\">Contrast</string>\n    <string name=\"reset\">Reset</string>\n    <string name=\"text_unsaved_changes_prompt\">Save or discard unsaved changes\\?</string>\n    <string name=\"discard\">Discard</string>\n    <string name=\"error_no_space_left\">No space left on device</string>\n    <string name=\"reader_slider\">Show page switching slider</string>\n    <string name=\"webtoon_zoom\">Webtoon zoom</string>\n    <string name=\"network_unavailable\">Network is not available</string>\n    <string name=\"network_unavailable_hint\">Turn on Wi-Fi or mobile network to read manga online</string>\n    <string name=\"server_error\">Server side error (%1$d). Please try again later</string>\n    <string name=\"clear_new_chapters_counters\">Also clear information about new chapters</string>\n    <string name=\"compact\">Compact</string>\n    <string name=\"mal\" translatable=\"false\">MyAnimeList</string>\n    <string name=\"source_disabled\">Source disabled</string>\n    <string name=\"prefetch_content\">Content preloading</string>\n    <string name=\"mark_as_current\">Mark as current</string>\n    <string name=\"language\">Language</string>\n    <string name=\"share_logs\">Share logs</string>\n    <string name=\"enable_logging\">Enable logging</string>\n    <string name=\"enable_logging_summary\">Record some actions for debug purposes. Don\\'t turn it on if you\\'re not sure what you\\'re doing</string>\n    <string name=\"show_suspicious_content\">Show suspicious content</string>\n    <string name=\"theme_name_dynamic\">Dynamic</string>\n    <string name=\"theme_name_expressive\">Expressive (Test)</string>\n    <string name=\"color_theme\">Color scheme</string>\n    <string name=\"show_in_grid_view\">Show in grid view</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"nothing_here\">There is nothing here</string>\n    <string name=\"scrobbling_empty_hint\">To track reading progress, select Menu → Track on the manga details screen.</string>\n    <string name=\"services\">Services</string>\n    <string name=\"allow_unstable_updates\">Allow unstable updates</string>\n    <string name=\"allow_unstable_updates_summary\">Receive notifications about unstable builds</string>\n    <string name=\"download_started\">Download started</string>\n    <string name=\"got_it\">Got it</string>\n    <string name=\"sources_reorder_tip\">Tap and hold on an item to reorder them</string>\n    <string name=\"user_agent\">UserAgent header</string>\n    <string name=\"settings_apply_restart_required\">Please restart the application to apply these changes</string>\n    <string name=\"comics_archive_import_description\">You can select one or more .cbz or .zip files, each file will be recognized as a separate manga.</string>\n    <string name=\"folder_with_images_import_description\">You can select a directory with archives or images. Each archive (or subdirectory) will be recognized as a chapter.</string>\n    <string name=\"speed\">Speed</string>\n    <string name=\"show_on_shelf\">Show on the Shelf</string>\n    <string name=\"sync_auth_hint\">You can sign in into an existing account or create a new one</string>\n    <string name=\"find_similar\">Find similar</string>\n    <string name=\"sync_settings\">Synchronization settings</string>\n    <string name=\"server_address\">Server address</string>\n    <string name=\"sync_host_description\">You can use a self-hosted synchronization server or a default one. Don\\'t change this if you\\'re not sure what you\\'re doing.</string>\n    <string name=\"ignore_ssl_errors\">Ignore SSL errors</string>\n    <string name=\"mirror_switching\">Choose mirror automatically</string>\n    <string name=\"mirror_switching_summary\">Automatically switch domains for manga sources on errors if mirrors are available</string>\n    <string name=\"pause\">Pause</string>\n    <string name=\"resume\">Resume</string>\n    <string name=\"paused\">Paused</string>\n    <!-- Menu item; action to remove completed items -->\n    <string name=\"remove_completed\">Remove completed</string>\n    <string name=\"cancel_all\">Cancel all</string>\n    <string name=\"downloads_wifi_only\">Download only via Wi-Fi</string>\n    <string name=\"downloads_wifi_only_summary\">Stop downloading when switching to a mobile network</string>\n    <string name=\"suggestion_manga\">Suggestion: %s</string>\n    <string name=\"suggestions_notifications_summary\">Sometimes show notifications with suggested manga</string>\n    <string name=\"more\">More</string>\n    <string name=\"enable\">Enable</string>\n    <string name=\"no_thanks\">No thanks</string>\n    <string name=\"cancel_all_downloads_confirm\">All active downloads will be cancelled, partially downloaded data will be lost</string>\n    <string name=\"remove_completed_downloads_confirm\">Your downloads history will be permanently deleted. No downloaded files will be affected</string>\n    <string name=\"text_downloads_list_holder\">You don\\'t have any downloads</string>\n    <string name=\"downloads_resumed\">Downloads have been resumed</string>\n    <string name=\"downloads_paused\">Downloads have been paused</string>\n    <string name=\"downloads_removed\">Downloads have been removed</string>\n    <string name=\"downloads_cancelled\">Downloads have been cancelled</string>\n    <string name=\"suggestions_enable_prompt\">Do you want to receive personalized manga suggestions?</string>\n    <string name=\"web_view_unavailable\">WebView not available: check if WebView provider is installed</string>\n    <string name=\"clear_network_cache\">Clear network cache</string>\n    <string name=\"type\">Type</string>\n    <string name=\"address\">Address</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"invalid_value_message\">Invalid value</string>\n    <string name=\"kitsu\" translatable=\"false\">Kitsu</string>\n    <string name=\"email_password_enter_hint\">Enter your email and password to continue</string>\n    <string name=\"downloaded\">Downloaded</string>\n    <string name=\"images_proxy_title\">Images optimization proxy</string>\n    <string name=\"images_procy_description\">Use the wsrv.nl service to reduce traffic usage and speed up image loading if possible</string>\n    <string name=\"invert_colors\">Invert colors</string>\n    <string name=\"username\">Username</string>\n    <string name=\"password\">Password</string>\n    <string name=\"authorization_optional\">Authorization (optional)</string>\n    <string name=\"invalid_port_number\">Invalid port number</string>\n    <string name=\"network\">Network</string>\n    <string name=\"data_and_privacy\">Data and privacy</string>\n    <string name=\"restore_summary\">Restore previously created backup</string>\n    <string name=\"webtoon_zoom_summary\">Allow zoom in gesture in webtoon mode</string>\n    <string name=\"pull_to_prev_chapter\">Release to open previous chapter</string>\n    <string name=\"pull_to_next_chapter\">Release to open next chapter</string>\n    <string name=\"pull_top_no_prev\">No previous chapter</string>\n    <string name=\"pull_bottom_no_next\">No next chapter</string>\n    <string name=\"reader_info_bar_summary\">Show the current time and reading progress at the top of the screen</string>\n    <string name=\"show_pages_numbers_summary\">Show page numbers in bottom corner</string>\n    <string name=\"clear_source_cookies_summary\">Clear cookies for specified domain only. In most cases will invalidate authorization</string>\n    <string name=\"download_option_all_chapters\">All chapters with translation %s</string>\n    <string name=\"download_option_whole_manga\">The whole manga</string>\n    <string name=\"download_option_first_n_chapters\">First %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Next unread %s</string>\n    <string name=\"download_option_all_unread\">All unread chapters</string>\n    <string name=\"download_option_all_unread_b\">All unread chapters (%s)</string>\n    <string name=\"download_option_manual_selection\">Select chapters manually</string>\n    <string name=\"pick_custom_directory\">Pick custom directory</string>\n    <string name=\"no_access_to_file\">You have no access to this file or directory</string>\n    <string name=\"local_manga_directories\">Local manga directories</string>\n    <string name=\"description\">Description</string>\n    <string name=\"this_month\">This month</string>\n    <string name=\"voice_search\">Voice search</string>\n    <string name=\"related_manga\">Related manga</string>\n    <string name=\"color_light\">Light</string>\n    <string name=\"color_dark\">Dark</string>\n    <string name=\"color_white\">White</string>\n    <string name=\"color_black\">Black</string>\n    <string name=\"background\">Background</string>\n    <string name=\"data_not_restored\">Data was not restored</string>\n    <string name=\"data_not_restored_text\">Make sure you have selected the correct backup file</string>\n    <string name=\"manage_categories\">Manage categories</string>\n    <string name=\"suggestions_wifi_only_summary\">Do not update suggestions using metered network connections</string>\n    <string name=\"tracker_wifi_only_summary\">Do not check for new chapters using metered network connections</string>\n    <string name=\"search_hint\">Enter manga title, genre or source name</string>\n    <string name=\"progress\">Progress</string>\n    <string name=\"order_added\">Added</string>\n    <string name=\"show\">Show</string>\n    <string name=\"captcha_required_summary\">%s requires a captcha to be resolved to work properly</string>\n    <string name=\"languages\">Languages</string>\n    <string name=\"unknown\">Unknown</string>\n    <string name=\"in_progress\">In progress</string>\n    <string name=\"disable_nsfw\">Disable NSFW</string>\n    <string name=\"too_many_requests_message\">Too many requests. Try again later</string>\n    <string name=\"too_many_requests_message_retry\">Too many requests. Try again after %s</string>\n    <string name=\"related_manga_summary\">Show a list of related manga. In some cases it may be inaccurate or missing</string>\n    <string name=\"advanced\">Advanced</string>\n    <string name=\"manga_list\">Manga list</string>\n    <string name=\"error_corrupted_file\">Invalid data is returned or file is corrupted</string>\n    <string name=\"on_device\">On device</string>\n    <string name=\"directories\">Directories</string>\n    <string name=\"main_screen_sections\">Main screen sections</string>\n    <string name=\"items_limit_exceeded\">No more items can be added</string>\n    <string name=\"to_top\">To top</string>\n    <string name=\"moved_to_top\">Moved to top</string>\n    <string name=\"zoom_out\">Zoom out</string>\n    <string name=\"zoom_in\">Zoom in</string>\n    <string name=\"reader_zoom_buttons\">Show zoom buttons</string>\n    <string name=\"reader_zoom_buttons_summary\">Whether to show zoom control buttons in the bottom right corner</string>\n    <string name=\"keep_screen_on\">Keep screen on</string>\n    <string name=\"keep_screen_on_summary\">Do not turn the screen off while you\\'re reading manga</string>\n    <string name=\"state_abandoned\">Dropped</string>\n    <string name=\"enhanced_colors_summary\">Reduces banding, but may impact performance</string>\n    <string name=\"enhanced_colors\">32-bit color mode</string>\n    <string name=\"suggest_new_sources\">Suggest new sources after app update</string>\n    <string name=\"suggest_new_sources_summary\">Prompt to enable newly added sources after updating the application</string>\n    <string name=\"list_options\">List options</string>\n    <string name=\"by_relevance\">Relevance</string>\n    <string name=\"categories\">Categories</string>\n    <string name=\"online_variant\">Online variant</string>\n    <string name=\"periodic_backups\">Periodic backups</string>\n    <string name=\"backup_frequency\">Backup creation frequency</string>\n    <string name=\"frequency_every_6_hours\">Every 6 hours</string>\n    <string name=\"frequency_every_day\">Every day</string>\n    <string name=\"frequency_every_2_days\">Every 2 days</string>\n    <string name=\"frequency_once_per_week\">Once per week</string>\n    <string name=\"frequency_twice_per_month\">Twice per month</string>\n    <string name=\"frequency_once_per_month\">Once per month</string>\n    <string name=\"periodic_backups_enable\">Enable periodic backups</string>\n    <string name=\"backups_output_directory\">Backups output directory</string>\n    <string name=\"last_successful_backup\">Last successful backup: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">Lock screen rotation</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Comics</string>\n    <string name=\"content_type_other\">Other</string>\n    <string name=\"source_summary_pattern\" translatable=\"false\">%1$s, %2$s</string>\n    <string name=\"sources_catalog\">Sources catalog</string>\n    <string name=\"source_enabled\">Source enabled</string>\n    <string name=\"no_manga_sources_catalog_text\">There are no sources available in this section, or all of it might have been already added.\\nStay tuned</string>\n    <string name=\"no_manga_sources_found\">No available manga sources found by your query</string>\n    <string name=\"catalog\">Catalog</string>\n    <string name=\"manage_sources\">Manage sources</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"available_d\">Available: %1$d</string>\n    <string name=\"disable_nsfw_summary\">Disable NSFW sources and hide adult manga from list if possible</string>\n    <string name=\"state_paused\">Paused</string>\n    <string name=\"reader_optimize\">Reduce memory consumption (beta)</string>\n    <string name=\"reader_optimize_summary\">Reduce offscreen pages quality to use less memory</string>\n    <string name=\"state\">State</string>\n    <string name=\"error_multiple_genres_not_supported\">Filtering by multiple genres is not supported by this manga source</string>\n    <string name=\"error_multiple_states_not_supported\">Filtering by multiple states is not supported by this manga source</string>\n    <string name=\"error_search_not_supported\">Search is not supported by this manga source</string>\n    <string name=\"downloads_settings_info\">You can enable download slowdown for each manga source individually in the source settings if you are having problems with server-side blocking</string>\n    <string name=\"skip\">Skip</string>\n    <string name=\"grayscale\">Grayscale</string>\n    <string name=\"globally\">Globally</string>\n    <string name=\"this_manga\">This manga</string>\n    <string name=\"color_correction_apply_text\">These settings can be applied globally or only to the current manga. If applied globally, individual settings will not be overridden.</string>\n    <string name=\"apply\">Apply</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Filtering by both genres and locale is not supported by this source</string>\n    <string name=\"error_filter_states_genre_not_supported\">Filtering by both genres and states is not supported by this source</string>\n    <string name=\"genres_search_hint\">Start typing the genre name</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Might help with getting the download started if you have any issues with it</string>\n    <string name=\"welcome_text\">Please select which content sources you would like to enable. This can also be configured later in settings</string>\n    <string name=\"sync_auth\">Login to sync account</string>\n    <string name=\"restore\">Restore</string>\n    <string name=\"backup_date_\">Backup date: %s</string>\n    <string name=\"state_upcoming\">Upcoming</string>\n    <string name=\"by_name_reverse\">Name reversed</string>\n    <string name=\"content_rating\">Content rating</string>\n    <string name=\"genres_exclude\">Exclude genres</string>\n    <string name=\"rating_safe\">Safe</string>\n    <string name=\"rating_suggestive\">Suggestive</string>\n    <string name=\"rating_adult\">Adult</string>\n    <string name=\"default_tab\">Default tab</string>\n    <string name=\"mark_as_completed\">Mark as completed</string>\n    <string name=\"mark_as_completed_prompt\">Mark selected manga as completely read?\\n\\nWarning: current reading progress will be lost.</string>\n    <string name=\"category_hidden_done\">This category was hidden from the main screen and is accessible through Menu → Manage categories</string>\n    <string name=\"remaining_time_pattern\" translatable=\"false\">%1$s %2$s</string>\n    <string name=\"volume_\">Volume %d</string>\n    <string name=\"volume_unknown\">Unknown volume</string>\n    <string name=\"incognito_mode_hint\">Your reading progress will not be saved</string>\n    <string name=\"vertical\">Vertical</string>\n    <string name=\"last_read\">Last read</string>\n    <string name=\"show_menu\">Show menu</string>\n    <string name=\"toggle_ui\">Show/hide UI</string>\n    <string name=\"prev_chapter\">Previous chapter</string>\n    <string name=\"next_chapter\">Next chapter</string>\n    <string name=\"prev_page\">Previous page</string>\n    <string name=\"next_page\">Next page</string>\n    <string name=\"reader_actions\">Reader actions</string>\n    <string name=\"reader_actions_summary\">Configure actions for tappable screen areas</string>\n    <string name=\"switch_pages_volume_buttons\">Enable volume buttons</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Use volume buttons for switching pages</string>\n    <string name=\"reader_navigation_inverted\">Invert navigation controls</string>\n    <string name=\"reader_navigation_inverted_summary\">Swap the direction of volume button and directional hardware key navigation (left/up/down/right)</string>\n    <string name=\"tap_action\">Tap action</string>\n    <string name=\"long_tap_action\">Long tap action</string>\n    <string name=\"none\">None</string>\n    <string name=\"config_reset_confirm\">Reset settings to default values? This action cannot be undone.</string>\n    <string name=\"use_two_pages_landscape\">Use two pages layout on landscape orientation (beta)</string>\n    <string name=\"auto_double_foldable\">Auto Two-Page On Foldable</string>\n    <string name=\"two_page_scroll_sensitivity\">Two-Page Scroll Sensitivity</string>\n    <string name=\"default_webtoon_zoom_out\">Default webtoon zoom out</string>\n    <string name=\"fullscreen_mode\">Fullscreen mode</string>\n    <string name=\"reader_fullscreen_summary\">Hide system status and navigation bars</string>\n    <string name=\"fraction_pattern\" translatable=\"false\">%1$d/%2$d</string>\n    <string name=\"reading_time_estimation\">Show estimated reading time</string>\n    <string name=\"reading_time_estimation_summary\">The time estimation value may be inaccurate</string>\n    <string name=\"suggestions_unavailable_text\">Suggestions feature is disabled</string>\n    <string name=\"check_for_new_chapters_disabled\">Checking for new chapters is disabled</string>\n    <string name=\"show_labels_in_navbar\">Show labels in navigation bar</string>\n    <string name=\"pages_saving\">Saving pages</string>\n    <string name=\"ask_for_dest_dir_every_time\">Ask for the destination dir every time</string>\n    <string name=\"default_page_save_dir\">Default page save directory</string>\n    <string name=\"remove_from_history\">Remove from history</string>\n    <string name=\"location\">Location</string>\n    <string name=\"preferred_download_format\">Preferred download format</string>\n    <string name=\"automatic\">Automatic</string>\n    <string name=\"single_cbz_file\">Single CBZ file</string>\n    <string name=\"multiple_cbz_files\">Multiple CBZ files</string>\n    <string name=\"reading_stats\">Reading statistics</string>\n    <string name=\"other_manga\">Other manga</string>\n    <string name=\"less_than_minute\">Less than a minute</string>\n    <string name=\"statistics\">Statistics</string>\n    <string name=\"clear_stats\">Clear statistics</string>\n    <string name=\"stats_cleared\">Statistics cleared</string>\n    <string name=\"clear_stats_confirm\">Do you really want to clear all reading statistics? This action cannot be undone.</string>\n    <string name=\"week\">Week</string>\n    <string name=\"month\">Month</string>\n    <string name=\"all_time\">All time</string>\n    <string name=\"day\">Day</string>\n    <string name=\"three_months\">Three months</string>\n    <string name=\"empty_stats_text\">There are no statistics for the selected period</string>\n    <string name=\"pages_read_s\">Pages read: %s</string>\n    <string name=\"alternatives\">Alternatives</string>\n    <string name=\"migrate\">Migrate</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" from \\\"%2$s\\\" will be replaced with \\\"%3$s\\\" from \\\"%4$s\\\" in your history and favorites (if present)</string>\n    <string name=\"manga_migration\">Manga migration</string>\n    <string name=\"migration_completed\">Migration completed</string>\n    <string name=\"delete_read_chapters\">Delete read chapters</string>\n    <string name=\"no_chapters_deleted\">No chapters have been deleted</string>\n    <string name=\"chapters_deleted_pattern\">Removed %1$s, cleared %2$s</string>\n    <string name=\"delete_read_chapters_summary\">Delete chapters you have already read from local storage to free up space</string>\n    <string name=\"delete_read_chapters_prompt\">This will permanently delete all chapters marked as read from your local storage. You can re-download it later, but the imported chapters may be lost forever</string>\n    <string name=\"delete_read_chapters_auto\">Delete read chapters automatically</string>\n    <string name=\"runs_on_app_start\">Runs when the application starts</string>\n    <string name=\"split_by_translations\">Split by translations</string>\n    <string name=\"split_by_translations_summary\">Show chapters with different translations separately, rather than in one list</string>\n    <string name=\"order_oldest\">Oldest</string>\n    <string name=\"long_ago_read\">Long time ago read</string>\n    <string name=\"unread\">Unread</string>\n    <string name=\"enable_source\">Enable source</string>\n    <string name=\"unsupported_source\">This manga source is not supported</string>\n    <string name=\"show_pages_thumbs\">Show pages thumbnails</string>\n    <string name=\"show_pages_thumbs_summary\">Enable the \\\"Pages\\\" tab on the details screen</string>\n    <string name=\"error_no_data_received\">No data was received from server</string>\n    <string name=\"unsupported_backup_message\">Please select a proper Kotatsu backup file</string>\n    <string name=\"list_ellipsize_pattern\" translatable=\"false\">(+%d)</string>\n    <!-- Short hours format pattern -->\n    <string name=\"hours_short\">%d h</string>\n    <!-- Short minutes format pattern -->\n    <string name=\"minutes_short\">%d m</string>\n    <!-- Short seconds format pattern -->\n    <string name=\"seconds_short\">%d s</string>\n    <!-- Short hours and minutes format pattern -->\n    <string name=\"hours_minutes_short\">%1$d h %2$d m</string>\n    <!-- Short minutes and seconds format pattern -->\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"fix\">Fix</string>\n    <string name=\"missing_storage_permission\">There is no permission to access manga on external storage</string>\n    <string name=\"last_used\">Last used</string>\n    <string name=\"show_updated\">Show updated</string>\n    <string name=\"webtoon_gaps\">Gaps in webtoon mode</string>\n    <string name=\"webtoon_gaps_summary\">Show vertical gaps between pages in webtoon mode</string>\n    <string name=\"enable_pull_gesture_title\">Enable pull gesture</string>\n    <string name=\"enable_pull_gesture_summary\">Use pull gesture to switch chapters in webtoon</string>\n    <string name=\"less_frequently\">Less frequently</string>\n    <string name=\"more_frequently\">More frequently</string>\n    <string name=\"frequency_of_check\">Frequency of check</string>\n    <string name=\"new_chapters_pattern\" translatable=\"false\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Pin navigation UI</string>\n    <string name=\"pin_navigation_ui_summary\">Do not hide navigation bar and search view on scroll</string>\n    <string name=\"search_suggestions\">Search suggestions</string>\n    <string name=\"recent_queries\">Recent queries</string>\n    <string name=\"suggested_queries\">Suggested queries</string>\n    <string name=\"authors\">Authors</string>\n    <string name=\"blocked_by_server_message\">You are blocked by the server. Try to use a different network connection (VPN, Proxy, etc.)</string>\n    <string name=\"disable\">Disable</string>\n    <string name=\"sources_disabled\">Sources disabled</string>\n    <string name=\"disable_connectivity_check\">Disable connectivity check</string>\n    <string name=\"ignore_ssl_errors_summary\">You can disable SSL certificates verification in case you face an SSL-related issues when accessing network resources. This may affect your security. Application restarting is required after changing this setting.</string>\n    <string name=\"disable_connectivity_check_summary\">Skip the connectivity check in case you have issues with it (e.g. going offline mode even though the network is connected)</string>\n    <string name=\"disable_nsfw_notifications\">Disable NSFW notifications</string>\n    <string name=\"disable_nsfw_notifications_summary\">Do not show notifications about NSFW manga updates</string>\n    <string name=\"tracker_debug_info\">Checking for new chapters log</string>\n    <string name=\"tracker_debug_info_summary\">Debug information about background checks for new chapters</string>\n    <!-- In plural, used for filter -->\n    <string name=\"_new\">New</string>\n    <string name=\"all_languages\">All languages</string>\n    <string name=\"screenshots_block_incognito\">Block when incognito mode</string>\n    <string name=\"image_server\">Preferred image server</string>\n    <string name=\"inline_preference_pattern\" translatable=\"false\">%1$s: %2$s</string>\n    <string name=\"crop_pages\">Crop pages</string>\n    <string name=\"pin\">Pin</string>\n    <string name=\"unpin\">Unpin</string>\n    <string name=\"source_pinned\">Source pinned</string>\n    <string name=\"source_unpinned\">Source unpinned</string>\n    <string name=\"sources_unpinned\">Sources unpinned</string>\n    <string name=\"sources_pinned\">Sources pinned</string>\n    <string name=\"recent_sources\">Recent sources</string>\n    <string name=\"percent_read\">Percent read</string>\n    <string name=\"percent_left\">Percent left</string>\n    <string name=\"chapters_read\">Chapters read</string>\n    <string name=\"chapters_left\">Chapters left</string>\n    <string name=\"external_source\">External/plugin</string>\n    <string name=\"plugin_incompatible\">Incompatible plugin or internal error. Make sure you are using the latest version of the plugin and Kotatsu</string>\n    <string name=\"plugin_incompatible_with_cause\">Plugin error: %s\\n Make sure you are using the latest version of the plugin and Kotatsu</string>\n    <string name=\"connection_ok\">Connection is OK</string>\n    <string name=\"invalid_proxy_configuration\">Invalid proxy configuration</string>\n    <string name=\"show_quick_filters\">Show quick filters</string>\n    <string name=\"show_quick_filters_summary\">Provides the ability to filter manga lists by certain parameters</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"skip_all\">Skip all</string>\n    <string name=\"stuck\">Stuck</string>\n    <string name=\"not_in_favorites\">Not in favorites</string>\n    <string name=\"updated_long_ago\">Updated long ago</string>\n    <string name=\"unpopular\">Unpopular</string>\n    <string name=\"low_rating\">Low rating</string>\n    <string name=\"sort_order_asc\">Ascending</string>\n    <string name=\"sort_order_desc\">Descending</string>\n    <string name=\"by_date\">Date</string>\n    <string name=\"popularity\">Popularity</string>\n    <string name=\"scrobbler_auth_required\">Sign in to %s to continue</string>\n    <string name=\"scrobbler_auth_intro\">Sign in to set up integration with %s. This will allow you to track your manga reading progress and status</string>\n    <string name=\"unstable_feature\">Unstable feature</string>\n    <string name=\"unstable_feature_summary\">This function is experimental. Please make sure you have a backup to avoid data loss</string>\n    <string name=\"downloads_background\">Background downloads</string>\n    <string name=\"download_new_chapters\">Download new chapters</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga with downloaded chapters</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) replaced with \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"fixing_manga\">Fixing manga</string>\n    <string name=\"fixed\">Fixed successfully</string>\n    <string name=\"no_fix_required\">No fix required for \\\"%s\\\"</string>\n    <string name=\"no_alternatives_found\">No alternatives found for \\\"%s\\\"</string>\n    <string name=\"manga_fix_prompt\">This function will find alternative sources for the selected manga. The task will take some time and will proceed in the background</string>\n    <string name=\"content_type_novel\">Novel</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"recently_added\">Recently added</string>\n    <string name=\"added_long_ago\">Added long ago</string>\n    <string name=\"popular_in_hour\">Popular this hour</string>\n    <string name=\"popular_today\">Popular today</string>\n    <string name=\"popular_in_week\">Popular this week</string>\n    <string name=\"popular_in_month\">Popular this month</string>\n    <string name=\"popular_in_year\">Popular this year</string>\n    <string name=\"original_language\">Original language</string>\n    <string name=\"year\">Year</string>\n    <string name=\"demographics\">Demographics</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"years\">Years</string>\n    <string name=\"any\">Any</string>\n    <string name=\"filter_search_warning\">This source does not support search with filters. Your filters have been cleared</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_one_shot\">One shot</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"content_type_image_set\">Image set</string>\n    <string name=\"content_type_artist_cg\">Artist CG</string>\n    <string name=\"content_type_game_cg\">Game CG</string>\n    <string name=\"debug\">Debug</string>\n    <string name=\"source_code\">Source code</string>\n    <string name=\"user_manual\">User manual</string>\n    <string name=\"telegram_group\">Telegram group</string>\n    <string name=\"error_image_format\">Unsupported image format: %s</string>\n    <string name=\"error_not_image\">Invalid format: expected image but got %s</string>\n    <string name=\"start_download\">Start download</string>\n    <string name=\"save_manga_confirm\">Save selected manga? This may consume traffic and disk space</string>\n    <string name=\"save_manga\">Save manga</string>\n    <string name=\"genre\">Genre</string>\n    <string name=\"download_added\">Download added</string>\n    <string name=\"more_options\">More options</string>\n    <string name=\"destination_directory\">Destination directory</string>\n    <string name=\"chapter_selection_hint\">You can select chapters to download by long click on item in the chapter list.</string>\n    <!-- For chapters -->\n    <string name=\"chapters_all\">All</string>\n    <string name=\"download_over_cellular\">Downloading over cellular network</string>\n    <string name=\"download_cellular_confirm\">Allow downloads over cellular network?</string>\n    <string name=\"dont_allow\">Don\\'t allow</string>\n    <string name=\"allow_always\">Allow always</string>\n    <string name=\"allow_once\">Allow once</string>\n    <string name=\"ask_every_time\">Ask every time</string>\n    <string name=\"screen_orientation\">Screen orientation</string>\n    <string name=\"portrait\">Portrait</string>\n    <string name=\"landscape\">Landscape</string>\n    <string name=\"breadcrumbs_separator\" translatable=\"false\"><![CDATA[\" > \"]]></string>\n    <string name=\"access_denied_403\">Access denied (403)</string>\n    <string name=\"max_backups_count\">Max number of backups</string>\n    <string name=\"delete_old_backups\">Delete old backups</string>\n    <string name=\"delete_old_backups_summary\">Automatically delete old backup files to save storage space</string>\n    <string name=\"handle_links\">Handle links</string>\n    <string name=\"handle_links_summary\">Handle manga links from external applications (e.g. web browser). You may also need to enable it manually in the application\\'s system settings</string>\n    <string name=\"email\">Email</string>\n    <string name=\"captcha_required_message\">This source requires solving a captcha to continue</string>\n    <string name=\"author\">Author</string>\n    <string name=\"rating\">Rating</string>\n    <string name=\"source\">Source</string>\n    <string name=\"translation\">Translation</string>\n    <string name=\"chapters_time_pattern\" translatable=\"false\">%1$s (%2$s)</string>\n    <string name=\"show_slider\">Show slider</string>\n    <!-- Button label, should be as short as possible -->\n    <string name=\"incognito\">Incognito</string>\n    <string name=\"error_connection_reset\">Connection reset by remote host</string>\n    <string name=\"backup_tg_check\">Check if API works</string>\n    <string name=\"backup_tg_echo\">Test message</string>\n    <string name=\"backup_tg_id_not_set\">Chat ID is not set</string>\n    <string name=\"telegram_chat_id\">Telegram chat ID</string>\n    <string name=\"open_telegram_bot\">Open the Telegram bot</string>\n    <string name=\"send_backups_telegram\">Send backups in Telegram</string>\n    <string name=\"test_connection\">Test connection</string>\n    <string name=\"telegram_chat_id_summary\">Enter the chat ID where backups should be sent</string>\n    <string name=\"open_telegram_bot_summary\">Press to open chat with Kotatsu Backup Bot</string>\n    <string name=\"clear_database\">Clear database</string>\n    <string name=\"clear_database_summary\">Delete information about manga that is not used</string>\n    <string name=\"enable_all_sources\">Enable all manga sources</string>\n    <string name=\"enable_all_sources_summary\">All available manga sources will be enabled permanently</string>\n    <string name=\"all_sources_enabled\">All sources are enabled</string>\n    <string name=\"reader_chapter_toast\">Show chapter change popup</string>\n    <string name=\"reader_chapter_toast_summary\">Show a pop-up message with a chapter title when it is changed</string>\n    <string name=\"reader_info_bar_transparent\">Transparent reader information bar</string>\n    <string name=\"backup_restored_background\">The backup will be restored in the background</string>\n    <string name=\"restoring_backup\">Restoring backup</string>\n    <string name=\"reader_controls_in_bottom_bar\">Reader controls in bottom bar</string>\n    <string name=\"chapters_and_pages\">Chapters and pages</string>\n    <string name=\"pages_slider\">Page switch slider</string>\n    <string name=\"screen_rotation_locked\">Screen rotation has been locked</string>\n    <string name=\"screen_rotation_unlocked\">Screen rotation has been unlocked</string>\n    <string name=\"badges_in_lists\">Badges in lists</string>\n    <string name=\"search_everywhere\">Search everywhere</string>\n    <string name=\"simple\">Simple</string>\n    <string name=\"global_search\">Global search</string>\n    <string name=\"disable_captcha_notifications\">Disable captcha notifications</string>\n    <string name=\"disable_captcha_notifications_summary\">You will not receive notifications about solving CAPTCHA for this source but this can lead to breaking background operations (checking for new chapters, obtaining recommendations, etc)</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Chapter %2$s</string>\n    <string name=\"chapter_number\">Chapter %s</string>\n    <string name=\"unnamed_chapter\">Unnamed chapter</string>\n    <string name=\"search_disabled_sources\">Search through disabled sources</string>\n    <string name=\"error_details\">Error details</string>\n    <string name=\"error_disclaimer_manga\">Try to open manga in a web browser to ensure it is available on its source.</string>\n    <string name=\"error_disclaimer_app_outdated\">It looks like your version of Kotatsu is out of date. Please install the latest version to get all available fixes.</string>\n    <string name=\"error_disclaimer_report\">You can submit a bug report to the developers. This will help us investigate and fix the issue.</string>\n    <string name=\"link_to_manga_on_s\">Link to manga on %s</string>\n    <string name=\"link_to_manga_in_app\">Link to manga in Kotatsu</string>\n    <string name=\"clear_browser_data\">Clear browser data</string>\n    <string name=\"clear_browser_data_summary\">Clear browser data such as cache and cookies. Warning: Authorization in manga sources may become invalid</string>\n    <string name=\"no_write_permission_to_file\">Does not have permission to write a file</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Adult manga will not be shown in suggestions. This option may work inaccurate with some sources</string>\n    <string name=\"include_disabled_sources\">Include disabled sources</string>\n    <string name=\"suggestions_disabled_sources_summary\">Show suggestions from all manga sources, including disabled ones</string>\n    <string name=\"tags_warnings\">Highlight dangerous genres</string>\n    <string name=\"tags_warnings_summary\">Highlight genres that may be inappropriate for most users</string>\n    <string name=\"error_non_file_uri\">The selected path cannot be used because it does not denote a file or directory</string>\n    <string name=\"manga_override_hint\">These changes will affect how manga is displayed in the app</string>\n    <string name=\"use_default_cover\">Use default cover</string>\n    <string name=\"pick_manga_page\">Pick manga page</string>\n    <string name=\"pick_custom_file\">Pick custom file</string>\n    <string name=\"change_cover\">Change cover</string>\n    <string name=\"page_switch_timer\">The page will switch every ~%d seconds</string>\n    <string name=\"dont_ask_again\">Don\\'t ask again</string>\n    <string name=\"incognito_mode_hint_nsfw\">This manga may contain adult content. Do you want to use incognito mode?</string>\n    <string name=\"incognito_for_nsfw\">Incognito mode for NSFW manga</string>\n    <string name=\"additional_action_required\">Additional action is required</string>\n    <string name=\"hide_from_main_screen\">Hide from main screen</string>\n    <string name=\"changelog\">Changelog</string>\n    <string name=\"changelog_summary\">Changes history for recently released versions</string>\n    <string name=\"collapse\">Collapse</string>\n    <string name=\"expand\">Expand</string>\n    <string name=\"adblock\">Block ads in browser</string>\n    <string name=\"adblock_summary\">Block advertisement in the built-in browser (beta)</string>\n    <string name=\"collapse_long_description\">Collapse long description</string>\n    <string name=\"creating_backup\">Creating backup</string>\n    <string name=\"share_backup\">Share backup</string>\n    <string name=\"reader_multitask\">Open reader in a separate task</string>\n    <string name=\"reader_multitask_summary\">Allows you to keep multiple readers with different manga open at the same time</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"book_effect\">Yellowish background (blue filter)</string>\n    <string name=\"local_storage_cleanup\">Local storage cleanup</string>\n    <string name=\"packup_creation_failed\">Failed to create backup</string>\n    <string name=\"main_screen\">Main screen</string>\n    <string name=\"main_screen_fab\">Show floating Continue button</string>\n    <string name=\"main_screen_fab_summary\">Allows to continue reading in a one click. This button will not appear in incognito mode or when the history is empty</string>\n    <string name=\"error_corrupted_zip\">Corrupted ZIP archive (%s)</string>\n    <string name=\"discord\" translatable=\"false\">Discord</string>\n    <string name=\"discord_rpc\">Discord Rich Presence</string>\n    <string name=\"discord_token\">Discord Token</string>\n    <string name=\"discord_token_summary\">Enter your Discord Token to enable Rich Presence</string>\n    <string name=\"discord_token_description\">Enter your Discord Token or click %s to get it using browser</string>\n    <string name=\"discord_token_hint\">Paste your Discord Token here</string>\n    <string name=\"discord_rpc_summary\">Show your reading status on Discord</string>\n    <string name=\"obtain\">Obtain</string>\n    <string name=\"discord_rpc_description\">Reading manga on Kotatsu - a manga reader app</string>\n    <string name=\"reading_s\">Reading %s</string>\n    <string name=\"read_on_s\">Read on %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Do not use RPC for adult content</string>\n    <string name=\"invalid_token\">Invalid token: %s</string>\n    <string name=\"show_floating_control_button\">Show floating control button</string>\n    <string name=\"unavailable\">Unavailable</string>\n    <string name=\"manga_restricted_description\">This manga is not available to read in this source. Try searching for it in other sources or opening it in a browser for more information</string>\n    <string name=\"no_chapters_in_manga\">This manga does not contain any chapters</string>\n    <string name=\"chapters_load_failed\">Failed to load chapter list</string>\n    <string name=\"telegram_integration\">Telegram integration</string>\n    <string name=\"test_parser\">Test manga source</string>\n    <string name=\"rename\">Rename</string>\n    <string name=\"save_filter\">Save filter</string>\n    <string name=\"overwrite\">Overwrite</string>\n    <string name=\"filter_overwrite_confirm\">A filter named \\\"%s\\\" already exists. Do you want to overwrite it?</string>\n    <string name=\"storage_and_network\">Storage and network</string>\n    <string name=\"create_or_restore_backup\">Create or restore a backup</string>\n    <string name=\"data_removal\">Data removal</string>\n    <string name=\"privacy\">Privacy</string>\n    <string name=\"source_broken_warning\">This manga source has been marked as broken. Some features may not work</string>\n    <string name=\"download_default_directory\">Default directory for downloading manga</string>\n    <string name=\"private_app_directory_warning\">This directory with all data will be deleted if you uninstall the application</string>\n    <string name=\"available_pattern\">%1$s available</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n\t<!--AlertDialog-->\n\n\t<style name=\"ThemeOverlay.Kotatsu.AlertDialog\" parent=\"ThemeOverlay.Material3.MaterialAlertDialog\">\n\t\t<item name=\"alertDialogStyle\">@style/AlertDialog.Kotatsu</item>\n\t\t<item name=\"dialogCornerRadius\">@dimen/m3_alert_dialog_corner_size</item>\n\t</style>\n\n\t<style name=\"AlertDialog.Kotatsu\" parent=\"MaterialAlertDialog.Material3\">\n\t\t<item name=\"android:background\">@drawable/m3_popup_background</item>\n\t</style>\n\n\t<!-- Bottom sheet -->\n\n\t<style name=\"ThemeOverlay.Kotatsu.BottomSheetDialog\" parent=\"ThemeOverlay.Material3.BottomSheetDialog\">\n\t\t<item name=\"android:windowAnimationStyle\">@style/Animation.Kotatsu.BottomSheetDialog</item>\n\t\t<item name=\"bottomSheetStyle\">@style/Widget.Kotatsu.BottomSheet.Modal</item>\n\t</style>\n\n\t<style name=\"ThemeOverlay.Kotatsu.SideSheetDialog\" parent=\"ThemeOverlay.Material3.SideSheetDialog\" />\n\n\t<style name=\"Widget.Kotatsu.BottomSheet.Modal\" parent=\"Widget.Material3.BottomSheet.Modal\">\n\t\t<item name=\"paddingBottomSystemWindowInsets\">false</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.BottomSheet.DragHandle\" parent=\"Widget.Material3.BottomSheet.DragHandle\">\n\t\t<!-- Fix weird bottom padding -->\n\t\t<item name=\"android:paddingBottom\">0dp</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.Spinner.DropDown\" parent=\"Widget.AppCompat.Spinner.DropDown\">\n\t\t<item name=\"android:popupBackground\">@drawable/m3_spinner_popup_background</item>\n\t\t<item name=\"android:elevation\">8dp</item>\n\t\t<item name=\"android:popupElevation\">3dp</item>\n\t</style>\n\n\t<style name=\"Animation.Kotatsu.BottomSheetDialog\" parent=\"Animation.AppCompat.Dialog\">\n\t\t<item name=\"android:windowEnterAnimation\">@anim/bottom_sheet_slide_in</item>\n\t\t<item name=\"android:windowExitAnimation\">@anim/bottom_sheet_slide_out</item>\n\t</style>\n\n\t<!-- Widget styles -->\n\n\t<style name=\"Widget.Kotatsu.SearchView\" parent=\"@style/Widget.AppCompat.SearchView\">\n\t\t<item name=\"iconifiedByDefault\">false</item>\n\t\t<item name=\"searchIcon\">@null</item>\n\t\t<item name=\"queryBackground\">@null</item>\n\t\t<item name=\"android:textAppearance\">@style/TextAppearance.Kotatsu.SearchView</item>\n\t\t<item name=\"android:textSize\">18sp</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.CircularProgressIndicator\" parent=\"Widget.Material3.CircularProgressIndicator\">\n\t\t<item name=\"trackCornerRadius\">@dimen/mtrl_progress_indicator_full_rounded_corner_radius</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.LinearProgressIndicator\" parent=\"Widget.Material3.LinearProgressIndicator\">\n\t\t<item name=\"trackCornerRadius\">@dimen/mtrl_progress_indicator_full_rounded_corner_radius</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.Chip\" parent=\"Widget.Material3.Chip.Suggestion\" />\n\n\t<style name=\"Widget.Kotatsu.Chip.Filter\" parent=\"Widget.Material3.Chip.Filter\" />\n\n\t<style name=\"Widget.Kotatsu.Chip.Assist\" parent=\"Widget.Material3.Chip.Assist\" />\n\n\t<style name=\"Widget.Kotatsu.Chip.Dropdown\" parent=\"Widget.Material3.Chip.Assist\">\n\t\t<item name=\"closeIconVisible\">true</item>\n\t\t<item name=\"closeIcon\">@drawable/ic_expand_more</item>\n\t\t<item name=\"chipIconVisible\">true</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.Button.More\" parent=\"Widget.Material3.Button.TextButton\">\n\t\t<item name=\"android:minWidth\">48dp</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.Button.More.Small\">\n\t\t<item name=\"android:insetTop\">0dp</item>\n\t\t<item name=\"android:insetBottom\">0dp</item>\n\t\t<item name=\"android:minHeight\">42dp</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.ToggleButton\" parent=\"Widget.Material3.Button.OutlinedButton\">\n\t\t<item name=\"android:checkable\">true</item>\n\t\t<item name=\"android:textAlignment\">textStart</item>\n\t\t<item name=\"iconPadding\">16dp</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.ToggleButton.Vertical\">\n\t\t<item name=\"android:textAlignment\">center</item>\n\t\t<item name=\"iconPadding\">2dp</item>\n\t\t<item name=\"android:singleLine\">false</item>\n\t\t<item name=\"iconGravity\">top</item>\n\t\t<item name=\"android:paddingTop\">12dp</item>\n\t\t<item name=\"android:paddingBottom\">10dp</item>\n\t\t<item name=\"android:paddingStart\">6dp</item>\n\t\t<item name=\"android:paddingEnd\">6dp</item>\n\t\t<item name=\"android:elegantTextHeight\">false</item>\n\t\t<item name=\"shapeAppearance\">?shapeAppearanceCornerMedium</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.Slider.Wide\" parent=\"Widget.Material3.Slider\">\n\t\t<item name=\"thumbHeight\">48dp</item>\n\t\t<item name=\"trackCornerSize\">12dp</item>\n\t\t<item name=\"trackHeight\">40dp</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.RecyclerView\" parent=\"\">\n\t\t<item name=\"android:scrollbarStyle\">outsideOverlay</item>\n\t\t<item name=\"android:clipToPadding\">false</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.FastScroller\" parent=\"\">\n\t\t<item name=\"thumbColor\">?colorTertiary</item>\n\t\t<item name=\"bubbleColor\">?colorTertiary</item>\n\t\t<item name=\"bubbleTextColor\">?colorOnTertiary</item>\n\t\t<item name=\"trackColor\">?colorOutline</item>\n\t\t<item name=\"bubbleSize\">small</item>\n\t\t<item name=\"scrollerOffset\">@dimen/grid_spacing_outer</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.DotIndicator\" parent=\"\" />\n\n\t<style name=\"Widget.Kotatsu.BadgeView\" parent=\"\">\n\t\t<item name=\"android:textColor\">@macro/m3_comp_badge_large_label_text_color</item>\n\t\t<item name=\"android:minWidth\">@dimen/m3_badge_with_text_size</item>\n\t\t<item name=\"android:minHeight\">@dimen/m3_badge_with_text_size</item>\n\t\t<item name=\"android:textAppearance\">@macro/m3_comp_badge_large_label_text_type</item>\n\t\t<item name=\"android:gravity\">center</item>\n\t\t<item name=\"shapeAppearance\">@style/ShapeAppearance.M3.Comp.Badge.Shape</item>\n\t\t<item name=\"backgroundColor\">?attr/colorError</item>\n\t\t<item name=\"android:elegantTextHeight\">false</item>\n\t\t<item name=\"singleLine\">true</item>\n\t\t<item name=\"android:outlineProvider\">background</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.ListItemTextView\" parent=\"\">\n\t\t<item name=\"android:textColor\">@color/list_item_text_color</item>\n\t\t<item name=\"backgroundFillColor\">@color/list_item_background_color</item>\n\t\t<item name=\"checkedDrawableStart\">@drawable/ic_check</item>\n\t\t<item name=\"shapeAppearanceOverlay\">@style/ShapeAppearanceOverlay.Material3.NavigationView.Item</item>\n\t\t<item name=\"android:gravity\">center_vertical|start</item>\n\t\t<item name=\"android:insetRight\">6dp</item>\n\t\t<item name=\"android:insetLeft\">6dp</item>\n\t\t<item name=\"android:insetTop\">2dp</item>\n\t\t<item name=\"android:insetBottom\">2dp</item>\n\t\t<item name=\"android:textAppearance\">?textAppearanceListItem</item>\n\t\t<item name=\"android:minHeight\">?android:listPreferredItemHeightSmall</item>\n\t\t<item name=\"android:drawablePadding\">?android:listPreferredItemPaddingStart</item>\n\t\t<item name=\"android:paddingStart\">?android:listPreferredItemPaddingStart</item>\n\t\t<item name=\"android:paddingEnd\">?android:listPreferredItemPaddingEnd</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.TwoLinesItemView\" parent=\"\">\n\t\t<item name=\"backgroundFillColor\">@color/list_item_background_color</item>\n\t\t<item name=\"shapeAppearance\">?attr/shapeAppearanceCornerLarge</item>\n\t\t<item name=\"android:insetRight\">6dp</item>\n\t\t<item name=\"android:insetLeft\">6dp</item>\n\t\t<item name=\"android:insetTop\">2dp</item>\n\t\t<item name=\"android:insetBottom\">2dp</item>\n\t\t<item name=\"android:orientation\">horizontal</item>\n\t\t<item name=\"android:textColor\">@color/list_item_text_color</item>\n\t\t<item name=\"titleTextAppearance\">?attr/textAppearanceTitleSmall</item>\n\t\t<item name=\"subtitleTextAppearance\">?attr/textAppearanceBodySmall</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.ExploreButton\" parent=\"Widget.Material3.Button.ElevatedButton.Icon\">\n\t\t<item name=\"android:minHeight\">56dp</item>\n\t\t<item name=\"android:singleLine\">true</item>\n\t\t<item name=\"shapeAppearance\">?shapeAppearanceCornerExtraLarge</item>\n\t\t<item name=\"android:ellipsize\">marquee</item>\n\t\t<item name=\"android:marqueeRepeatLimit\">marquee_forever</item>\n\t\t<item name=\"iconSize\">22dp</item>\n\t\t<item name=\"iconPadding\">16dp</item>\n\t\t<item name=\"iconGravity\">start</item>\n\t\t<item name=\"iconTint\">?attr/colorPrimary</item>\n\t\t<item name=\"android:textColor\">?attr/colorPrimary</item>\n\t\t<item name=\"android:insetTop\">2dp</item>\n\t\t<item name=\"android:insetBottom\">2dp</item>\n\t\t<item name=\"android:gravity\">start|center_vertical</item>\n\t\t<item name=\"backgroundTint\">?attr/colorSurfaceContainerHigh</item>\n\t\t<!-- Hack. Removing shadow. For AMOLED -->\n\t\t<item name=\"android:outlineProvider\">none</item>\n\t\t<item name=\"elevation\">24dp</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.TextView.Indicator\" parent=\"Widget.MaterialComponents.TextView\">\n\t\t<item name=\"android:drawablePadding\">12dp</item>\n\t\t<item name=\"android:gravity\">center_vertical</item>\n\t\t<item name=\"android:textAppearance\">?textAppearanceLabelMedium</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.TextView.Badge\" parent=\"Widget.MaterialComponents.TextView\">\n\t\t<item name=\"android:background\">@drawable/bg_chip</item>\n\t\t<item name=\"android:gravity\">center</item>\n\t\t<item name=\"android:textAlignment\">center</item>\n\t\t<item name=\"android:paddingStart\">4dp</item>\n\t\t<item name=\"android:paddingEnd\">4dp</item>\n\t\t<item name=\"android:paddingTop\">2dp</item>\n\t\t<item name=\"android:paddingBottom\">2dp</item>\n\t\t<item name=\"android:textAppearance\">?textAppearanceLabelMedium</item>\n\t\t<item name=\"android:textColor\">?colorOnBackground</item>\n\t</style>\n\n\t<style name=\"Widget.Kotatsu.ImageView.Cover\" parent=\"Widget.MaterialComponents.ShapeableImageView\">\n\t\t<item name=\"aspectRationHeight\">18</item>\n\t\t<item name=\"aspectRationWidth\">13</item>\n\t\t<item name=\"android:scaleType\">centerCrop</item>\n\t\t<item name=\"allowRgb565\">true</item>\n\t\t<item name=\"trimImage\">true</item>\n\t</style>\n\n\t<style name=\"ThemeOverlay.Kotatsu.MainToolbar\" parent=\"\">\n\t\t<item name=\"colorControlHighlight\">@color/selector_overlay</item>\n\t</style>\n\n\t<style name=\"ThemeOverlay.Kotatsu.ActionMode\" parent=\"\">\n\t\t<item name=\"colorOnSurface\">?colorPrimary</item>\n\t\t<item name=\"colorControlNormal\">?colorPrimary</item>\n\t</style>\n\n\t<!-- TextAppearance -->\n\n\t<style name=\"TextAppearance.Kotatsu.SearchView\" parent=\"TextAppearance.Material3.SearchBar\">\n\t\t<item name=\"android:textColor\">?attr/colorOnSurfaceVariant</item>\n\t</style>\n\n\t<style name=\"TextAppearance.Widget.Menu\" parent=\"TextAppearance.AppCompat.Menu\">\n\t\t<item name=\"android:textColor\">?attr/colorOnBackground</item>\n\t</style>\n\n\t<style name=\"TextAppearance.Kotatsu.Preference.Secondary\" parent=\"TextAppearance.Material3.BodySmall\" />\n\n\t<style name=\"TextAppearance.Kotatsu.GridTitle\" parent=\"TextAppearance.Material3.TitleSmall\" />\n\n\t<style name=\"TextAppearance.Kotatsu.GridTitle.Small\" parent=\"TextAppearance.Material3.TitleSmall\">\n\t\t<item name=\"android:textSize\">12sp</item>\n\t\t<item name=\"android:letterSpacing\">0.00714286</item>\n\t\t<item name=\"lineHeight\">14sp</item>\n\t\t<item name=\"android:lineHeight\" tools:ignore=\"NewApi\">14sp</item>\n\t</style>\n\n\t<!-- Shapes -->\n\n\t<style name=\"ShapeAppearanceOverlay.Kotatsu.Cover\" parent=\"\">\n\t\t<item name=\"cornerSize\">12dp</item>\n\t</style>\n\n\t<style name=\"ShapeAppearanceOverlay.Kotatsu.Cover.Medium\" parent=\"\">\n\t\t<item name=\"cornerSize\">8dp</item>\n\t</style>\n\n\t<style name=\"ShapeAppearanceOverlay.Kotatsu.Cover.Small\" parent=\"\">\n\t\t<item name=\"cornerSize\">4dp</item>\n\t</style>\n\n\t<style name=\"ShapeAppearanceOverlay.Kotatsu.Circle\" parent=\"\">\n\t\t<item name=\"cornerFamily\">rounded</item>\n\t\t<item name=\"cornerSize\">50%</item>\n\t</style>\n\n\t<style name=\"ShapeAppearanceOverlay.Material3.Corner.None\" parent=\"\">\n\t\t<item name=\"cornerSizeBottomLeft\">0dp</item>\n\t\t<item name=\"cornerSizeBottomRight\">0dp</item>\n\t\t<item name=\"cornerSizeTopLeft\">0dp</item>\n\t\t<item name=\"cornerSizeTopRight\">0dp</item>\n\t</style>\n\n\t<!--Preferences-->\n\n\t<style name=\"PreferenceThemeOverlay.Kotatsu\">\n\t\t<item name=\"singleLineTitle\">false</item>\n\t</style>\n\n\t<style name=\"Preference.Slider\" parent=\"Preference.SeekBarPreference.Material\">\n\t\t<item name=\"android:layout\">@layout/preference_slider</item>\n\t</style>\n\n\t<style name=\"Preference.MultiAutoCompleteTextView\" parent=\"Preference.DialogPreference.EditTextPreference.Material\">\n\t\t<item name=\"android:dialogLayout\">@layout/preference_dialog_multiautocompletetextview</item>\n\t</style>\n\n\t<style name=\"Preference.AutoCompleteTextView\" parent=\"Preference.DialogPreference.EditTextPreference.Material\">\n\t\t<item name=\"android:dialogLayout\">@layout/preference_dialog_autocompletetextview</item>\n\t</style>\n\n\t<style name=\"Preference.SwitchPreferenceCompat.M3\" parent=\"Preference.SwitchPreferenceCompat.Material\">\n\t\t<item name=\"android:widgetLayout\">@layout/preference_widget_material_switch</item>\n\t</style>\n\n\t<style name=\"Preference.ThemeChooser\" parent=\"Preference.Material\">\n\t\t<item name=\"android:layout\">@layout/preference_theme</item>\n\t\t<item name=\"android:selectable\">false</item>\n\t</style>\n\n\t<!-- Drawable -->\n\n\t<style name=\"ProgressDrawable\">\n\t\t<item name=\"android:fillAlpha\">1</item>\n\t\t<item name=\"android:fillColor\">?attr/colorPrimary</item>\n\t\t<item name=\"android:strokeColor\">?attr/colorSurfaceVariant</item>\n\t\t<item name=\"android:textColor\">?attr/colorOnPrimary</item>\n\t\t<item name=\"strokeWidth\">4dp</item>\n\t\t<item name=\"android:textSize\">9sp</item>\n\t\t<item name=\"autoFitTextSize\">true</item>\n\t</style>\n\n\t<style name=\"FaviconDrawable\">\n\t\t<item name=\"backgroundColor\">?colorBackgroundFloating</item>\n\t\t<item name=\"strokeColor\">?colorOutline</item>\n\t</style>\n\n\t<style name=\"FaviconDrawable.Small\">\n\t\t<item name=\"strokeWidth\">1dp</item>\n\t\t<item name=\"cornerSize\">8dp</item>\n\t</style>\n\n\t<style name=\"FaviconDrawable.Large\">\n\t\t<item name=\"strokeWidth\">1dp</item>\n\t\t<item name=\"cornerSize\">12dp</item>\n\t</style>\n\n\t<style name=\"FaviconDrawable.Chip\">\n\t\t<item name=\"strokeWidth\">1px</item>\n\t\t<item name=\"cornerSize\">@dimen/chip_icon_corner</item>\n\t\t<item name=\"drawableSize\">@dimen/m3_chip_icon_size</item>\n\t</style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n\t<!-- Base application theme. -->\n\t<style name=\"Base.Theme.Kotatsu\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n\n\t\t<!-- Theme Colors -->\n\t\t<item name=\"colorPrimary\">@color/kotatsu_primary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/kotatsu_primaryContainer</item>\n\t\t<item name=\"colorOnPrimary\">@color/kotatsu_onPrimary</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/kotatsu_onPrimaryContainer</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/kotatsu_inversePrimary</item>\n\t\t<item name=\"colorSecondary\">@color/kotatsu_secondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/kotatsu_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondary\">@color/kotatsu_onSecondary</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/kotatsu_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/kotatsu_tertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/kotatsu_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiary\">@color/kotatsu_onTertiary</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/kotatsu_onTertiaryContainer</item>\n\t\t<item name=\"colorSurface\">@color/kotatsu_surface</item>\n\t\t<item name=\"colorSurfaceDim\">@color/kotatsu_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/kotatsu_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/kotatsu_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/kotatsu_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/kotatsu_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/kotatsu_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/kotatsu_surfaceContainerHighest</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/kotatsu_surfaceVariant</item>\n\t\t<item name=\"colorOnSurface\">@color/kotatsu_onSurface</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/kotatsu_onSurfaceVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/kotatsu_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/kotatsu_inverseOnSurface</item>\n\t\t<item name=\"colorOnBackground\">@color/kotatsu_onSurface</item>\n\t\t<item name=\"colorError\">@color/error</item>\n\t\t<item name=\"colorErrorContainer\">@color/errorContainer</item>\n\t\t<item name=\"colorOnError\">@color/onError</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/onErrorContainer</item>\n\t\t<item name=\"colorOutline\">@color/kotatsu_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/kotatsu_outline</item>\n\n\t\t<!-- Drawables (icons) -->\n\t\t<item name=\"actionModeCloseDrawable\">@drawable/abc_ic_clear_material</item>\n\t\t<item name=\"actionModeWebSearchDrawable\">@drawable/abc_ic_search_api_material</item>\n\t\t<item name=\"expandCollapseIndicator\">@drawable/m3_split_button_chevron_avd</item>\n\n\t\t<!-- Base attributes -->\n\t\t<item name=\"android:windowLightStatusBar\">@bool/light_status_bar</item>\n\t\t<item name=\"android:statusBarColor\">@android:color/transparent</item>\n\t\t<item name=\"android:navigationBarColor\">@color/dim</item>\n\t\t<item name=\"android:navigationBarDividerColor\" tools:targetApi=\"o_mr1\">@null</item>\n\t\t<item name=\"android:enforceNavigationBarContrast\" tools:targetApi=\"Q\">false</item>\n\t\t<item name=\"android:enforceStatusBarContrast\" tools:targetApi=\"Q\">false</item>\n\t\t<item name=\"windowActionModeOverlay\">true</item>\n\n\t\t<!-- System components styles -->\n\t\t<item name=\"android:alertDialogTheme\">@style/ThemeOverlay.Kotatsu.AlertDialog</item>\n\t\t<item name=\"alertDialogTheme\">@style/ThemeOverlay.Kotatsu.AlertDialog</item>\n\t\t<item name=\"bottomSheetDialogTheme\">@style/ThemeOverlay.Kotatsu.BottomSheetDialog</item>\n\t\t<item name=\"sideSheetDialogTheme\">@style/ThemeOverlay.Kotatsu.SideSheetDialog</item>\n\t\t<item name=\"recyclerViewStyle\">@style/Widget.Kotatsu.RecyclerView</item>\n\t\t<item name=\"android:dropDownSpinnerStyle\">@style/Widget.Kotatsu.Spinner.DropDown</item>\n\t\t<item name=\"switchPreferenceCompatStyle\">@style/Preference.SwitchPreferenceCompat.M3</item>\n\t\t<item name=\"circularProgressIndicatorStyle\">@style/Widget.Kotatsu.CircularProgressIndicator</item>\n\t\t<item name=\"linearProgressIndicatorStyle\">@style/Widget.Kotatsu.LinearProgressIndicator</item>\n\t\t<item name=\"bottomSheetDragHandleStyle\">@style/Widget.Kotatsu.BottomSheet.DragHandle</item>\n\t\t<item name=\"preferenceTheme\">@style/PreferenceThemeOverlay.Kotatsu</item>\n\t\t<!-- Fix crash in A15 -->\n\t\t<item name=\"actionButtonStyle\">@style/Widget.AppCompat.ActionButton</item>\n\n\t\t<!-- Custom views -->\n\t\t<item name=\"fastScrollerStyle\">@style/Widget.Kotatsu.FastScroller</item>\n\t\t<item name=\"listItemTextViewStyle\">@style/Widget.Kotatsu.ListItemTextView</item>\n\t\t<item name=\"dotIndicatorStyle\">@style/Widget.Kotatsu.DotIndicator</item>\n\t\t<item name=\"badgeViewStyle\">@style/Widget.Kotatsu.BadgeView</item>\n\t\t<item name=\"coverImageViewStyle\">@style/Widget.Kotatsu.ImageView.Cover</item>\n\t</style>\n\n\t<!--== Default Theme ==-->\n\t<style name=\"Theme.Kotatsu\" parent=\"Base.Theme.Kotatsu\" />\n\n\t<!-- Base ThemeOverlay -->\n\t<style name=\"ThemeOverlay.Kotatsu\" parent=\"ThemeOverlay.Material3.Light\" />\n\n\t<!-- Monet theme only support S+ -->\n\t<style name=\"ThemeOverlay.Kotatsu.Monet\" parent=\"ThemeOverlay.Material3.DynamicColors.Light\" />\n\n\t<!-- Expressive Monet theme -->\n\t<style name=\"ThemeOverlay.Kotatsu.Expressive\" parent=\"ThemeOverlay.Material3Expressive.DynamicColors.DayNight\">\n\t\t<item name=\"alertDialogTheme\">@style/ThemeOverlay.Material3Expressive.Dialog.Alert</item>\n\t\t<item name=\"materialAlertDialogTheme\">@style/ThemeOverlay.Material3Expressive.MaterialAlertDialog</item>\n\n\t\t<item name=\"appBarLayoutStyle\">@style/Widget.Material3Expressive.AppBarLayout</item>\n\t\t<item name=\"collapsingToolbarLayoutStyle\">@style/Widget.Material3Expressive.CollapsingToolbar</item>\n\t\t<item name=\"collapsingToolbarLayoutMediumStyle\">@style/Widget.Material3Expressive.CollapsingToolbar.Medium\n\t\t</item>\n\t\t<item name=\"collapsingToolbarLayoutLargeStyle\">@style/Widget.Material3Expressive.CollapsingToolbar.Large</item>\n\n\t\t<item name=\"materialSearchBarStyle\">@style/Widget.Material3Expressive.SearchBar</item>\n\n\t\t<item name=\"floatingActionButtonStyle\">@style/Widget.Material3Expressive.FloatingActionButton</item>\n\t\t<item name=\"floatingActionButtonMediumStyle\">@style/Widget.Material3Expressive.FloatingActionButton.Medium\n\t\t</item>\n\t\t<item name=\"floatingActionButtonLargeStyle\">@style/Widget.Material3Expressive.FloatingActionButton.Large</item>\n\n\t\t<item name=\"extendedFloatingActionButtonSmallStyle\">\n\t\t\t@style/Widget.Material3Expressive.ExtendedFloatingActionButton.Small\n\t\t</item>\n\t\t<item name=\"extendedFloatingActionButtonMediumStyle\">\n\t\t\t@style/Widget.Material3Expressive.ExtendedFloatingActionButton.Medium\n\t\t</item>\n\t\t<item name=\"extendedFloatingActionButtonLargeStyle\">\n\t\t\t@style/Widget.Material3Expressive.ExtendedFloatingActionButton.Large\n\t\t</item>\n\n\t\t<item name=\"materialButtonStyle\">@style/Widget.Material3Expressive.Button</item>\n\t\t<item name=\"materialButtonTonalStyle\">@style/Widget.Material3Expressive.Button.TonalButton</item>\n\t\t<item name=\"materialButtonOutlinedStyle\">@style/Widget.Material3Expressive.Button.OutlinedButton</item>\n\t\t<item name=\"materialButtonElevatedStyle\">@style/Widget.Material3Expressive.Button.ElevatedButton</item>\n\t\t<item name=\"borderlessButtonStyle\">@style/Widget.Material3Expressive.Button.TextButton</item>\n\t\t<item name=\"buttonBarButtonStyle\">@style/Widget.Material3Expressive.Button.TextButton.Dialog</item>\n\t\t<item name=\"snackbarButtonStyle\">@style/Widget.Material3Expressive.Button.TextButton.Snackbar</item>\n\t\t<item name=\"materialIconButtonStyle\">@style/Widget.Material3Expressive.Button.IconButton.Standard</item>\n\t\t<item name=\"materialIconButtonOutlinedStyle\">@style/Widget.Material3Expressive.Button.IconButton.Outlined</item>\n\t\t<item name=\"materialIconButtonFilledStyle\">@style/Widget.Material3Expressive.Button.IconButton.Filled</item>\n\t\t<item name=\"materialIconButtonFilledTonalStyle\">@style/Widget.Material3Expressive.Button.IconButton.Tonal</item>\n\n\t\t<item name=\"materialButtonGroupStyle\">@style/Widget.Material3Expressive.MaterialButtonGroup</item>\n\t\t<item name=\"materialButtonToggleGroupStyle\">@style/Widget.Material3Expressive.MaterialButtonToggleGroup</item>\n\n\t\t<item name=\"bottomNavigationStyle\">@style/Widget.Material3Expressive.BottomNavigationView</item>\n\t\t<item name=\"navigationRailStyle\">@style/Widget.Material3Expressive.NavigationRailView</item>\n\n\t\t<item name=\"toolbarStyle\">@style/Widget.Material3Expressive.Toolbar</item>\n\t\t<item name=\"toolbarSurfaceStyle\">@style/Widget.Material3Expressive.Toolbar.Surface</item>\n\n\t\t<item name=\"linearProgressIndicatorStyle\">@style/Widget.Material3Expressive.LinearProgressIndicator</item>\n\t\t<item name=\"circularProgressIndicatorStyle\">@style/Widget.Material3Expressive.CircularProgressIndicator</item>\n\n\t\t<item name=\"sliderStyle\">@style/Widget.Material3Expressive.Slider.Xsmall</item>\n\n\t\t<item name=\"collapsingToolbarLayoutMediumSize\">@dimen/m3_comp_app_bar_medium_flexible_container_height</item>\n\t\t<item name=\"collapsingToolbarLayoutLargeSize\">@dimen/m3_comp_app_bar_large_flexible_container_height</item>\n\n\t\t<item name=\"actionBarSize\">@dimen/m3_comp_app_bar_small_container_height</item>\n\n\t\t<item name=\"dynamicColorThemeOverlay\">@style/ThemeOverlay.Material3Expressive.DynamicColors.DayNight</item>\n\t</style>\n\n\t<!-- Changes only for night -->\n\t<style name=\"ThemeOverlay.Kotatsu.Amoled\" parent=\"\" />\n\n\t<!-- App Widgets -->\n\n\t<style name=\"Theme.Kotatsu.AppWidgetContainer\" parent=\"@style/Theme.MaterialComponents.Light\">\n\t\t<item name=\"android:colorBackground\">@color/kotatsu_background</item>\n\t\t<item name=\"android:panelColorBackground\">@color/kotatsu_primaryContainer</item>\n\t</style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/themes_colored.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<!-- Totoro -->\n\t<style name=\"ThemeOverlay.Kotatsu.Totoro\">\n\t\t<item name=\"colorPrimary\">@color/totoro_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/totoro_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/totoro_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/totoro_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/totoro_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/totoro_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/totoro_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/totoro_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/totoro_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/totoro_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/totoro_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/totoro_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/totoro_error</item>\n\t\t<item name=\"colorOnError\">@color/totoro_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/totoro_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/totoro_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/totoro_background</item>\n\t\t<item name=\"colorOnBackground\">@color/totoro_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/totoro_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/totoro_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/totoro_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/totoro_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/totoro_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/totoro_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/totoro_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/totoro_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/totoro_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/totoro_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/totoro_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/totoro_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/totoro_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/totoro_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/totoro_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/totoro_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/totoro_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/totoro_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/totoro_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/totoro_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/totoro_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/totoro_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/totoro_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/totoro_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/totoro_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/totoro_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/totoro_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/totoro_surfaceContainerHighest</item>\n\t</style>\n\t<!-- Asuka -->\n\t<style name=\"ThemeOverlay.Kotatsu.Asuka\">\n\t\t<item name=\"colorPrimary\">@color/asuka_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/asuka_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/asuka_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/asuka_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/asuka_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/asuka_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/asuka_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/asuka_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/asuka_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/asuka_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/asuka_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/asuka_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/asuka_error</item>\n\t\t<item name=\"colorOnError\">@color/asuka_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/asuka_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/asuka_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/asuka_background</item>\n\t\t<item name=\"colorOnBackground\">@color/asuka_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/asuka_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/asuka_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/asuka_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/asuka_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/asuka_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/asuka_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/asuka_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/asuka_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/asuka_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/asuka_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/asuka_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/asuka_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/asuka_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/asuka_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/asuka_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/asuka_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/asuka_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/asuka_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/asuka_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/asuka_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/asuka_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/asuka_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/asuka_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/asuka_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/asuka_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/asuka_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/asuka_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/asuka_surfaceContainerHighest</item>\n\t</style>\n\n\t<!-- Itsuka -->\n\t<style name=\"ThemeOverlay.Kotatsu.Itsuka\">\n\t\t<item name=\"colorPrimary\">@color/itsuka_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/itsuka_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/itsuka_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/itsuka_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/itsuka_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/itsuka_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/itsuka_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/itsuka_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/itsuka_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/itsuka_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/itsuka_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/itsuka_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/itsuka_error</item>\n\t\t<item name=\"colorOnError\">@color/itsuka_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/itsuka_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/itsuka_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/itsuka_background</item>\n\t\t<item name=\"colorOnBackground\">@color/itsuka_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/itsuka_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/itsuka_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/itsuka_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/itsuka_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/itsuka_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/itsuka_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/itsuka_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/itsuka_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/itsuka_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/itsuka_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/itsuka_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/itsuka_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/itsuka_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/itsuka_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/itsuka_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/itsuka_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/itsuka_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/itsuka_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/itsuka_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/itsuka_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/itsuka_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/itsuka_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/itsuka_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/itsuka_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/itsuka_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/itsuka_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/itsuka_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/itsuka_surfaceContainerHighest</item>\n\t</style>\n\t<!-- Kanade -->\n\t<style name=\"ThemeOverlay.Kotatsu.Kanade\">\n\t\t<item name=\"colorPrimary\">@color/kanade_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/kanade_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/kanade_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/kanade_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/kanade_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/kanade_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/kanade_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/kanade_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/kanade_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/kanade_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/kanade_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/kanade_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/kanade_error</item>\n\t\t<item name=\"colorOnError\">@color/kanade_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/kanade_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/kanade_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/kanade_background</item>\n\t\t<item name=\"colorOnBackground\">@color/kanade_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/kanade_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/kanade_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/kanade_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/kanade_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/kanade_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/kanade_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/kanade_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/kanade_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/kanade_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/kanade_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/kanade_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/kanade_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/kanade_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/kanade_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/kanade_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/kanade_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/kanade_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/kanade_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/kanade_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/kanade_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/kanade_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/kanade_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/kanade_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/kanade_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/kanade_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/kanade_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/kanade_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/kanade_surfaceContainerHighest</item>\n\t</style>\n\t<!-- Mamimi -->\n\t<style name=\"ThemeOverlay.Kotatsu.Mamimi\">\n\t\t<item name=\"colorPrimary\">@color/mamimi_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/mamimi_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/mamimi_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/mamimi_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/mamimi_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/mamimi_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/mamimi_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/mamimi_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/mamimi_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/mamimi_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/mamimi_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/mamimi_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/mamimi_error</item>\n\t\t<item name=\"colorOnError\">@color/mamimi_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/mamimi_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/mamimi_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/mamimi_background</item>\n\t\t<item name=\"colorOnBackground\">@color/mamimi_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/mamimi_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/mamimi_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/mamimi_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/mamimi_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/mamimi_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/mamimi_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/mamimi_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/mamimi_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/mamimi_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/mamimi_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/mamimi_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/mamimi_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/mamimi_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/mamimi_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/mamimi_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/mamimi_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/mamimi_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/mamimi_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/mamimi_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/mamimi_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/mamimi_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/mamimi_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/mamimi_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/mamimi_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/mamimi_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/mamimi_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/mamimi_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/mamimi_surfaceContainerHighest</item>\n\t</style>\n\t<!-- Miku -->\n\t<style name=\"ThemeOverlay.Kotatsu.Miku\">\n\t\t<item name=\"colorPrimary\">@color/miku_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/miku_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/miku_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/miku_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/miku_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/miku_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/miku_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/miku_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/miku_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/miku_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/miku_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/miku_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/miku_error</item>\n\t\t<item name=\"colorOnError\">@color/miku_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/miku_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/miku_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/miku_background</item>\n\t\t<item name=\"colorOnBackground\">@color/miku_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/miku_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/miku_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/miku_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/miku_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/miku_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/miku_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/miku_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/miku_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/miku_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/miku_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/miku_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/miku_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/miku_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/miku_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/miku_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/miku_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/miku_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/miku_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/miku_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/miku_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/miku_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/miku_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/miku_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/miku_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/miku_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/miku_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/miku_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/miku_surfaceContainerHighest</item>\n\t</style>\n\t<!-- Mion -->\n\t<style name=\"ThemeOverlay.Kotatsu.Mion\">\n\t\t<item name=\"colorPrimary\">@color/mion_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/mion_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/mion_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/mion_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/mion_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/mion_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/mion_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/mion_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/mion_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/mion_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/mion_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/mion_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/mion_error</item>\n\t\t<item name=\"colorOnError\">@color/mion_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/mion_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/mion_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/mion_background</item>\n\t\t<item name=\"colorOnBackground\">@color/mion_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/mion_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/mion_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/mion_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/mion_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/mion_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/mion_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/mion_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/mion_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/mion_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/mion_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/mion_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/mion_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/mion_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/mion_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/mion_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/mion_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/mion_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/mion_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/mion_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/mion_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/mion_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/mion_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/mion_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/mion_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/mion_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/mion_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/mion_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/mion_surfaceContainerHighest</item>\n\t</style>\n\t<!-- Rikka -->\n\t<style name=\"ThemeOverlay.Kotatsu.Rikka\">\n\t\t<item name=\"colorPrimary\">@color/rikka_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/rikka_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/rikka_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/rikka_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/rikka_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/rikka_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/rikka_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/rikka_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/rikka_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/rikka_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/rikka_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/rikka_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/rikka_error</item>\n\t\t<item name=\"colorOnError\">@color/rikka_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/rikka_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/rikka_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/rikka_background</item>\n\t\t<item name=\"colorOnBackground\">@color/rikka_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/rikka_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/rikka_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/rikka_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/rikka_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/rikka_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/rikka_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/rikka_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/rikka_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/rikka_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/rikka_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/rikka_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/rikka_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/rikka_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/rikka_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/rikka_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/rikka_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/rikka_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/rikka_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/rikka_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/rikka_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/rikka_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/rikka_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/rikka_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/rikka_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/rikka_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/rikka_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/rikka_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/rikka_surfaceContainerHighest</item>\n\t</style>\n\t<!-- Sakura -->\n\t<style name=\"ThemeOverlay.Kotatsu.Sakura\">\n\t\t<item name=\"colorPrimary\">@color/sakura_primary</item>\n\t\t<item name=\"colorOnPrimary\">@color/sakura_onPrimary</item>\n\t\t<item name=\"colorPrimaryContainer\">@color/sakura_primaryContainer</item>\n\t\t<item name=\"colorOnPrimaryContainer\">@color/sakura_onPrimaryContainer</item>\n\t\t<item name=\"colorSecondary\">@color/sakura_secondary</item>\n\t\t<item name=\"colorOnSecondary\">@color/sakura_onSecondary</item>\n\t\t<item name=\"colorSecondaryContainer\">@color/sakura_secondaryContainer</item>\n\t\t<item name=\"colorOnSecondaryContainer\">@color/sakura_onSecondaryContainer</item>\n\t\t<item name=\"colorTertiary\">@color/sakura_tertiary</item>\n\t\t<item name=\"colorOnTertiary\">@color/sakura_onTertiary</item>\n\t\t<item name=\"colorTertiaryContainer\">@color/sakura_tertiaryContainer</item>\n\t\t<item name=\"colorOnTertiaryContainer\">@color/sakura_onTertiaryContainer</item>\n\t\t<item name=\"colorError\">@color/sakura_error</item>\n\t\t<item name=\"colorOnError\">@color/sakura_onError</item>\n\t\t<item name=\"colorErrorContainer\">@color/sakura_errorContainer</item>\n\t\t<item name=\"colorOnErrorContainer\">@color/sakura_onErrorContainer</item>\n\t\t<item name=\"android:colorBackground\">@color/sakura_background</item>\n\t\t<item name=\"colorOnBackground\">@color/sakura_onBackground</item>\n\t\t<item name=\"colorSurface\">@color/sakura_surface</item>\n\t\t<item name=\"colorOnSurface\">@color/sakura_onSurface</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/sakura_surfaceVariant</item>\n\t\t<item name=\"colorOnSurfaceVariant\">@color/sakura_onSurfaceVariant</item>\n\t\t<item name=\"colorOutline\">@color/sakura_outline</item>\n\t\t<item name=\"colorOutlineVariant\">@color/sakura_outlineVariant</item>\n\t\t<item name=\"colorSurfaceInverse\">@color/sakura_inverseSurface</item>\n\t\t<item name=\"colorOnSurfaceInverse\">@color/sakura_inverseOnSurface</item>\n\t\t<item name=\"colorPrimaryInverse\">@color/sakura_inversePrimary</item>\n\t\t<item name=\"colorPrimaryFixed\">@color/sakura_primaryFixed</item>\n\t\t<item name=\"colorOnPrimaryFixed\">@color/sakura_onPrimaryFixed</item>\n\t\t<item name=\"colorPrimaryFixedDim\">@color/sakura_primaryFixedDim</item>\n\t\t<item name=\"colorOnPrimaryFixedVariant\">@color/sakura_onPrimaryFixedVariant</item>\n\t\t<item name=\"colorSecondaryFixed\">@color/sakura_secondaryFixed</item>\n\t\t<item name=\"colorOnSecondaryFixed\">@color/sakura_onSecondaryFixed</item>\n\t\t<item name=\"colorSecondaryFixedDim\">@color/sakura_secondaryFixedDim</item>\n\t\t<item name=\"colorOnSecondaryFixedVariant\">@color/sakura_onSecondaryFixedVariant</item>\n\t\t<item name=\"colorTertiaryFixed\">@color/sakura_tertiaryFixed</item>\n\t\t<item name=\"colorOnTertiaryFixed\">@color/sakura_onTertiaryFixed</item>\n\t\t<item name=\"colorTertiaryFixedDim\">@color/sakura_tertiaryFixedDim</item>\n\t\t<item name=\"colorOnTertiaryFixedVariant\">@color/sakura_onTertiaryFixedVariant</item>\n\t\t<item name=\"colorSurfaceDim\">@color/sakura_surfaceDim</item>\n\t\t<item name=\"colorSurfaceBright\">@color/sakura_surfaceBright</item>\n\t\t<item name=\"colorSurfaceContainerLowest\">@color/sakura_surfaceContainerLowest</item>\n\t\t<item name=\"colorSurfaceContainerLow\">@color/sakura_surfaceContainerLow</item>\n\t\t<item name=\"colorSurfaceContainer\">@color/sakura_surfaceContainer</item>\n\t\t<item name=\"colorSurfaceContainerHigh\">@color/sakura_surfaceContainerHigh</item>\n\t\t<item name=\"colorSurfaceContainerHighest\">@color/sakura_surfaceContainerHighest</item>\n\t</style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ab/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-ar/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"zero\">لا يوجد فصل جديد</item>\n        <item quantity=\"one\">فصل جديد</item>\n        <item quantity=\"two\">فصلين جديدين</item>\n        <item quantity=\"few\">%1$d فصول جديدة</item>\n        <item quantity=\"many\">%1$d فصل جديد</item>\n        <item quantity=\"other\">%1$d فصل جديد</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"zero\">لا يوجد فصول</item>\n        <item quantity=\"one\">فصل واحد</item>\n        <item quantity=\"two\">فصلين</item>\n        <item quantity=\"few\">%1$d فصول</item>\n        <item quantity=\"many\">%1$d فصل</item>\n        <item quantity=\"other\">%1$d فصل</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"zero\">٠ دقائق مضت</item>\n        <item quantity=\"one\">منذ دقيقة مضت</item>\n        <item quantity=\"two\">منذ دقيقتان مضت</item>\n        <item quantity=\"few\">%1$dدقائق مضت</item>\n        <item quantity=\"many\">%1$dدقيقة مضت</item>\n        <item quantity=\"other\">%1$dدقيقة مضت</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"zero\">%1$d عنصر</item>\n        <item quantity=\"one\">عنصر واحد</item>\n        <item quantity=\"two\">عنصران</item>\n        <item quantity=\"few\">%1$d عناصر</item>\n        <item quantity=\"many\">%1$d عنصر</item>\n        <item quantity=\"other\">%1$d عنصر</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"zero\">٠ شهر مضى</item>\n        <item quantity=\"one\">شهر مضى</item>\n        <item quantity=\"two\">شهرين مضت</item>\n        <item quantity=\"few\">%1$d أشهر مضت</item>\n        <item quantity=\"many\">%1$d شهر مضت</item>\n        <item quantity=\"other\">%1$d شهر مضت</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"zero\">٠ يوم مضى</item>\n        <item quantity=\"one\">يوم مضى</item>\n        <item quantity=\"two\">يومين مضت</item>\n        <item quantity=\"few\">%1$d أيام مضت</item>\n        <item quantity=\"many\">%1$d يوم مضت</item>\n        <item quantity=\"other\">%1$d يوم مضت</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"zero\">٠ ساعة مضت</item>\n        <item quantity=\"one\">ساعة مضت</item>\n        <item quantity=\"two\">ساعتين مضت</item>\n        <item quantity=\"few\">%1$d ساعات مضت</item>\n        <item quantity=\"many\">%1$d ساعة مضت</item>\n        <item quantity=\"other\">%1$d ساعة مضت</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"zero\">%1$d دقيقة</item>\n        <item quantity=\"one\">%1$d دقيقة</item>\n        <item quantity=\"two\">%1$d دقيقتان</item>\n        <item quantity=\"few\">%1$d دقائق</item>\n        <item quantity=\"many\">%1$d دقيقة</item>\n        <item quantity=\"other\">%1$d ساعة</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"zero\">%1$d ساعة</item>\n        <item quantity=\"one\">ساعة</item>\n        <item quantity=\"two\">ساعتان</item>\n        <item quantity=\"few\">%1$d ساعات</item>\n        <item quantity=\"many\">%1$d ساعة</item>\n        <item quantity=\"other\">%1$d ساعة</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"detailed_list\">قائمة مفصلة</string>\n    <string name=\"error_occurred\">حدث خطأ</string>\n    <string name=\"details\">تفاصيل</string>\n    <string name=\"grid\">شبكة</string>\n    <string name=\"list_mode\">وضع القائمة</string>\n    <string name=\"settings\">الإعدادات</string>\n    <string name=\"remote_sources\">مصادر المانجا</string>\n    <string name=\"chapters\">الفصول</string>\n    <string name=\"favourites\">المُفضلة</string>\n    <string name=\"network_error\">‌خطاء في الشبكة</string>\n    <string name=\"loading_\">جار التحميل…</string>\n    <string name=\"chapter_d_of_d\">فصل %1$d من %2$d</string>\n    <string name=\"close\">إغلاق</string>\n    <string name=\"try_again\">حاول مجدداً</string>\n    <string name=\"computing_\">جاري الحوسبة …</string>\n    <string name=\"local_storage\">التخزين المحلي</string>\n    <string name=\"history\">السجل</string>\n    <string name=\"list\">اللائحة</string>\n    <string name=\"clear_history\">محو سجل</string>\n    <string name=\"add_to_favourites\">أضف للمفضلة</string>\n    <string name=\"add\">أضف</string>\n    <string name=\"save\">حفظ</string>\n    <string name=\"history_is_empty\">لا سجل بعد</string>\n    <string name=\"downloads\">التحميلات</string>\n    <string name=\"by_name\">اسم</string>\n    <string name=\"newest\">الأحدث</string>\n    <string name=\"by_rating\">تقييم</string>\n    <string name=\"pages\">صفحات</string>\n    <string name=\"read\">إقرأ</string>\n    <string name=\"share\">شارك</string>\n    <string name=\"nothing_found\">لا شيء موجود</string>\n    <string name=\"you_have_not_favourites_yet\">لا مفضلة بعد</string>\n    <string name=\"search\">بحث</string>\n    <string name=\"search_manga\">البحث في المانجا</string>\n    <string name=\"manga_downloading_\">جاري التنزيل…</string>\n    <string name=\"create_shortcut\">انشاء اختصار…</string>\n    <string name=\"theme\">مظهر</string>\n    <string name=\"follow_system\">حسب النظام</string>\n    <string name=\"share_s\">شارك %s</string>\n    <string name=\"processing_\">في طور المعالجة…</string>\n    <string name=\"updated\">محدث</string>\n    <string name=\"filter\">تصفية</string>\n    <string name=\"sort_order\">ترتيب الفرز</string>\n    <string name=\"light\">فاتح</string>\n    <string name=\"dark\">داكن</string>\n    <string name=\"clear\">مسح</string>\n    <string name=\"remove\">حذف</string>\n    <string name=\"popular\">شائع</string>\n    <string name=\"add_new_category\">قائمة جديدة</string>\n    <string name=\"download_complete\">تم التنزيل</string>\n    <string name=\"save_page\">احفظ الصفحة</string>\n    <string name=\"page_saved\">تم الحفظ</string>\n    <string name=\"standard\">اساسي</string>\n    <string name=\"no_description\">لا يوجد وصف</string>\n    <string name=\"clear_pages_cache\">مسح ذاكرة التخزين المؤقت للصفحة</string>\n    <string name=\"webtoon\">ويبتون</string>\n    <string name=\"read_mode\">وضع القراءة</string>\n    <string name=\"search_on_s\">بحث على %s</string>\n    <string name=\"delete_manga\">حذف المانغا</string>\n    <string name=\"text_delete_local_manga\">حذف \\\"%s\\\" من الجهاز نهائيا؟</string>\n    <string name=\"reader_settings\">إعدادات القارئ</string>\n    <string name=\"switch_pages\">تغییر صفحات</string>\n    <string name=\"delete\">حذف</string>\n    <string name=\"share_image\">شارك الصورة</string>\n    <string name=\"text_file_not_supported\">إما أن تختار ملف ZIP أو CBZ.</string>\n    <string name=\"_import\">استورد</string>\n    <string name=\"operation_not_supported\">هذا الخيار غير مدعوم</string>\n    <string name=\"grid_size\">حجم الشبكة</string>\n    <string name=\"_continue\">أكمل</string>\n    <string name=\"error\">خطأ</string>\n    <string name=\"clear_search_history\">مسح تاريخ البحث</string>\n    <string name=\"disable_nsfw\">تعطيل NSFW</string>\n    <string name=\"updates\">التحديثات</string>\n    <string name=\"sync_host_description\">يمكنك استخدام سيرفر التزامن ذاتياً أو السيرفر الافتراضي. لا تغير هذا إن لم تكن متأكداً مما تفعله.</string>\n    <string name=\"text_clear_cookies_prompt\">سيتم تسجيل خروجك من جميع المصادر</string>\n    <string name=\"clear_cookies\">مسح ملفات تعريف الارتباط</string>\n    <string name=\"favourites_categories\">قوائم المُفضلة</string>\n    <string name=\"clear_thumbs_cache\">مسح ذاكرة التخزين المؤقت للصور المصغرة</string>\n    <string name=\"rotate_screen\">تدوير الشاشة</string>\n    <string name=\"text_clear_updates_feed_prompt\">هل تريد مسح سجل التحديث بشكل دائم؟</string>\n    <string name=\"suggestions_enable\">تفعيل الاقتراحات</string>\n    <string name=\"clear_feed\">مسح الموجز</string>\n    <string name=\"welcome\">مرحبا</string>\n    <string name=\"about_app_translation_summary\">ترجمة هذا التطبيق</string>\n    <string name=\"vibration\">اهتزاز</string>\n    <string name=\"no_update_available\">لا يوجد تحديثات</string>\n    <string name=\"remove_category\">حذف</string>\n    <string name=\"internal_storage\">التخزين الداخلي</string>\n    <string name=\"read_later\">اقرأ لاحقا</string>\n    <string name=\"backup_saved\">تم حفظ النسخة الاحتياطية</string>\n    <string name=\"create_backup\">إنشاء نسخة احتياطية</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">تمكين %1$d من %2$d</string>\n    <string name=\"tap_to_try_again\">انقر لإعادة المحاولة</string>\n    <string name=\"ignore_ssl_errors\">تجاهل أخطاء SSL</string>\n    <string name=\"auth_required\">سجل الدخول لمشاهدة المحتوى</string>\n    <string name=\"next\">التالي</string>\n    <string name=\"restore_backup\">استعادة من نسخة احتياطية</string>\n    <string name=\"password_length_hint\">كلمة السر يجب أن تكون 4 أحرف أو أكثر</string>\n    <string name=\"server_address\">عنوان السيرفر</string>\n    <string name=\"text_feed_holder\">فصول جديدة من ما تقرأه تظهر هنا</string>\n    <string name=\"text_suggestion_holder\">ابدأ بقراءة المانجا وستحصل على اقتراحات مخصصة</string>\n    <string name=\"find_similar\">ابحث عن متشابه</string>\n    <string name=\"data_restored\">تم الاستعادة</string>\n    <string name=\"protect_application_subtitle\">أدخل كلمة السر لبدء التطبيق</string>\n    <string name=\"suggestions\">الاقتراحات</string>\n    <string name=\"enabled\">مفعل</string>\n    <string name=\"text_clear_search_history_prompt\">هل تريد إزالة استعلامات البحث الأخيرة نهائيًا؟</string>\n    <string name=\"updates_feed_cleared\">تم المسح</string>\n    <string name=\"update\">تحديث</string>\n    <string name=\"feed_will_update_soon\">سيبدأ تحديث الموجز قريبًا</string>\n    <string name=\"app_update_available\">تتوفر نسخة جديدة من التطبيق</string>\n    <string name=\"new_version_s\">نسخة جديدة: %s</string>\n    <string name=\"sync_settings\">إعدادات التزامن</string>\n    <string name=\"create_category\">قائمة جديدة</string>\n    <string name=\"notification_sound\">صوت الإشعار</string>\n    <string name=\"backup_restore\">النسخ الاحتياطي و الاستعادة</string>\n    <string name=\"show_pages_numbers\">إظهار أرقام الصفحات</string>\n    <string name=\"search_history_cleared\">تم المسح</string>\n    <string name=\"_s_deleted_from_local_storage\">تم حذف \\\"%s\\\" من التخزين المحلي</string>\n    <string name=\"saved_manga\">المانجا المحفوظة</string>\n    <string name=\"open_in_browser\">الفتح في المتصفح</string>\n    <string name=\"about_app_translation\">ترجمة</string>\n    <string name=\"notifications\">الإشعارات</string>\n    <string name=\"reverse\">العكس</string>\n    <string name=\"track_sources\">البحث عن تحديثات</string>\n    <string name=\"wrong_password\">كلمة سر خاطئة</string>\n    <string name=\"group\">المجموعة</string>\n    <string name=\"just_now\">الآن</string>\n    <string name=\"download\">تنزيل</string>\n    <string name=\"chapter_is_missing\">الفصل مفقود</string>\n    <string name=\"size_s\">الحجم: %s</string>\n    <string name=\"about\">حول</string>\n    <string name=\"check_for_new_chapters\">التحقق من الفصول الجديدة</string>\n    <string name=\"captcha_solve\">حل</string>\n    <string name=\"data_restored_with_errors\">أستعيدت البيانات، لكن هناك أخطاء</string>\n    <string name=\"new_chapters\">فصول جديدة</string>\n    <string name=\"exit_confirmation\">تأكيد الخروج</string>\n    <string name=\"protect_application\">حماية التطبيق</string>\n    <string name=\"passwords_mismatch\">كلمة السر غير مطابقة</string>\n    <string name=\"yesterday\">أمس</string>\n    <string name=\"check_for_updates\">تحقق من وجود تحديثات</string>\n    <string name=\"protect_application_summary\">اطلب كلمة السر عند تشغيل التطبيق</string>\n    <string name=\"right_to_left\">من اليمين الى اليسار</string>\n    <string name=\"reader_mode_hint\">سيتم تذكر الاعدادات المختارة لهذه المانجا</string>\n    <string name=\"default_s\">الافتراضي: %s</string>\n    <string name=\"confirm\">تأكد</string>\n    <string name=\"clear_updates_feed\">مسح موجز التحديثات</string>\n    <string name=\"disabled\">معطل</string>\n    <string name=\"long_ago\">منذ فترة</string>\n    <string name=\"notifications_settings\">إعدادات الإشعارات</string>\n    <string name=\"read_more\">اقرأ المزيد</string>\n    <string name=\"search_results\">نتائج البحث</string>\n    <string name=\"file_not_found\">الملف غير موجود</string>\n    <string name=\"app_version\">نسخة %s</string>\n    <string name=\"cookies_cleared\">تمت إزالة جميع ملفات تعريف الارتباط</string>\n    <string name=\"state_finished\">انتهت</string>\n    <string name=\"state_ongoing\">مستمرة</string>\n    <string name=\"suggestions_summary\">أقترح المانجا بناء على تفضيلاتك</string>\n    <string name=\"preparing_\">جارٍ التحضير…</string>\n    <string name=\"exit_confirmation_summary\">اضغط مرتين للخروج من التطبيق</string>\n    <string name=\"enter_password\">ادخل كلمة السر</string>\n    <string name=\"repeat_password\">كرر كلمة السر</string>\n    <string name=\"data_restored_success\">استعيدت جميع البيانات</string>\n    <string name=\"backup_information\">يمكنك إنشاء نسخة احتياطية من السجل الخاص بك والمُفضلة واستعادتها</string>\n    <string name=\"external_storage\">تخزين خارجي</string>\n    <string name=\"silent\">صامت</string>\n    <string name=\"today\">اليوم</string>\n    <string name=\"system_default\">الافتراضي</string>\n    <string name=\"sign_in\">تسجبل الدخول</string>\n    <string name=\"domain\">المجال</string>\n    <string name=\"text_history_holder_primary\">كل ما تقرأه سيعرض هنا</string>\n    <string name=\"text_search_holder_secondary\">حاول إعادة صياغة الكلمات.</string>\n    <string name=\"text_empty_holder_primary\">يبدو أنه فارغ…</string>\n    <string name=\"status_re_reading\">إعادة القراءة</string>\n    <string name=\"detect_reader_mode\">وضع القارئ التلقائي</string>\n    <string name=\"manga_shelf\">رف</string>\n    <string name=\"tracking\">تتبع</string>\n    <string name=\"text_history_holder_secondary\">ابحث عن ما تقرأه في قسم «استكشاف»</string>\n    <string name=\"all_favourites\">كل المُفضلة</string>\n    <string name=\"email_enter_hint\">أدخل بريدك الإلكتروني للمتابعة</string>\n    <string name=\"disable_all\">تعطيل الجميع</string>\n    <string name=\"chapters_empty\">لا توجد فصول في هذه المانجا</string>\n    <string name=\"preload_pages\">إعادة تحميل الصفحات</string>\n    <string name=\"show_reading_indicators\">إظهار مؤشرات التقدم في القراءة</string>\n    <string name=\"local_manga_processing\">معالجة المانجا المحفوظة</string>\n    <string name=\"cannot_find_available_storage\">لاتوجد مساحة تخزين كافية</string>\n    <string name=\"show_notification_new_chapters_off\">لن تتلقى إشعارات ولكن سيتم تمييز الفصول الجديدة في القوائم</string>\n    <string name=\"favourites_category_empty\">قائمة فارغة</string>\n    <string name=\"show_notification_new_chapters_on\">ستتلقى إشعارات حول تحديثات المانجا التي تقرأها</string>\n    <string name=\"manga_save_location\">مجلد التحميلات</string>\n    <string name=\"status_reading\">أقرأها</string>\n    <string name=\"auth_complete\">مصرح له</string>\n    <string name=\"various_languages\">لغات مختلفة</string>\n    <string name=\"removal_completed\">اكتملت عملية الإزالة</string>\n    <string name=\"edit\">تعديل</string>\n    <string name=\"captcha_required\">مطلوب التحقق من الCAPTCHA</string>\n    <string name=\"removed_from_history\">تم الحذف من السجل</string>\n    <string name=\"crash_text\">حدث خطأ ما. يرجى إرسال تقرير بالخطأ إلى المطورين لمساعدتنا في إصلاحه.</string>\n    <string name=\"detect_reader_mode_summary\">اكتشف تلقائيًا ما إذا كانت المانجا عبارة عن webtoon</string>\n    <string name=\"appwidget_recent_description\">المانجا التي قرأتها مؤخرًا</string>\n    <string name=\"appearance\">مظهر</string>\n    <string name=\"bookmark_remove\">حذف الإشارة المرجعية</string>\n    <string name=\"disable_battery_optimization_summary\">يساعد في فحص التحديثات في الخلفية</string>\n    <string name=\"auth_not_supported_by\">تسجيل الدخول على %s غير مدعوم</string>\n    <string name=\"status_on_hold\">معلقَّة</string>\n    <string name=\"name\">اسم</string>\n    <string name=\"edit_category\">تعديل القائمة</string>\n    <string name=\"tracker_warning\">تتميز بعض الأجهزة بسلوك نظام مختلف، مما قد يؤدي إلى تعطيل مهام الخلفية.</string>\n    <string name=\"suggestions_excluded_genres_summary\">حدد التصنيفات التي لا تريد رؤيتها في الاقتراحات</string>\n    <string name=\"scale_mode\">وضع القياس</string>\n    <string name=\"only_using_wifi\">فقط على Wi-Fi</string>\n    <string name=\"black_dark_theme\">أسود</string>\n    <string name=\"back\">رجوع</string>\n    <string name=\"screenshots_allow\">السماح</string>\n    <string name=\"dns_over_https\">DNS مع HTTPS</string>\n    <string name=\"sync_title\">مزامنة بياناتك</string>\n    <string name=\"appwidget_shelf_description\">مانجا من المُفضلة لديك</string>\n    <string name=\"send\">إرسال</string>\n    <string name=\"bookmark_add\">اضافة إشارة مرجعية</string>\n    <string name=\"screenshots_block_all\">احظر دائما</string>\n    <string name=\"new_sources_text\">تتوفر مصادر مانغا جديدة</string>\n    <string name=\"zoom_mode_fit_height\">مناسب للارتفاع</string>\n    <string name=\"not_available\">غير متاح</string>\n    <string name=\"check_new_chapters_title\">التحقق من وجود فصول جديدة مع تلقي الاشعارات</string>\n    <string name=\"logged_in_as\">تم تسجيل الدخول كـ %s</string>\n    <string name=\"suggestions_info\">يتم تحليل جميع البيانات محليًا فقط على هذا الجهاز ولا يتم إرسالها إلى أي مكان.</string>\n    <string name=\"undo\">تراجع</string>\n    <string name=\"zoom_mode_fit_center\">مناسب للمركز</string>\n    <string name=\"exclude_nsfw_from_history\">استبعد المانجات NSFW من سجل التصفح</string>\n    <string name=\"download_slowdown_summary\">يساعد في تجنب حظر عنوان IP الخاص بك</string>\n    <string name=\"text_delete_local_manga_batch\">هل تريد حذف العناصر المحددة من الجهاز نهائيًا؟</string>\n    <string name=\"queued\">في قائمة الانتظار</string>\n    <string name=\"report\">تبليغ</string>\n    <string name=\"download_slowdown\">تبطيء التحميل</string>\n    <string name=\"sync\">التزامن</string>\n    <string name=\"search_chapters\">البحث عن الفصل</string>\n    <string name=\"always\">دائما</string>\n    <string name=\"suggestions_excluded_genres\">أقصاء التصنيفات</string>\n    <string name=\"canceled\">ألغيت</string>\n    <string name=\"account_already_exists\">الحساب موجود بالفعل</string>\n    <string name=\"hide\">أخفِ</string>\n    <string name=\"use_fingerprint\">استخدم المقاييس الحيوية إذا كانت متاحة</string>\n    <string name=\"onboard_text\">حدد اللغات التي تريد قراءة المانجا بها. ويمكنك تغيير الخيار لاحقًا في الإعدادات.</string>\n    <string name=\"suggestions_updating\">تحديث الاقتراحات</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"chapters_will_removed_background\">ستتم إزالة الفصول في الخلفية</string>\n    <string name=\"default_mode\">الوضع الافتراضي</string>\n    <string name=\"logout\">تسجيل الخروج</string>\n    <string name=\"status_completed\">مكتملة</string>\n    <string name=\"recent_manga\">آخر التحديثات</string>\n    <string name=\"dont_check\">لا تحدد</string>\n    <string name=\"zoom_mode_fit_width\">مناسب للعرض</string>\n    <string name=\"reset_filter\">إعادة تعيين الترتيب حسب</string>\n    <string name=\"status_dropped\">متروكة</string>\n    <string name=\"nsfw\">+18</string>\n    <string name=\"black_dark_theme_summary\">يستهلك طاقة بطارية أقل على شاشات AMOLED</string>\n    <string name=\"notifications_enable\">تمكين الإشعارات</string>\n    <string name=\"exclude_nsfw_from_suggestions\">لا تقترح مانجات NSFW</string>\n    <string name=\"never\">أبداً</string>\n    <string name=\"disable_battery_optimization\">تعطيل \\\"إستهلاك أقل للبطارية\\\"</string>\n    <string name=\"status_planned\">أنوي قرأتها</string>\n    <string name=\"pages_animation\">انيميشن الصفحة</string>\n    <string name=\"genres\">الأنواع</string>\n    <string name=\"other_storage\">خيارات تخزين أخرى</string>\n    <string name=\"screenshots_block_nsfw\">حظر على NSFW</string>\n    <string name=\"zoom_mode_keep_start\">إبقاء في البداية</string>\n    <string name=\"text_local_holder_secondary\">احفظ شيئًا أو قم باستيراده من ملف.</string>\n    <string name=\"text_local_holder_primary\">قم بحفظ شيءٍ أولاً</string>\n    <string name=\"bookmarks\">المحفوظات</string>\n    <string name=\"empty_favourite_categories\">لا توجد قوائم مُفضلة</string>\n    <string name=\"screenshots_policy\">سياسة لقطة الشاشة</string>\n    <string name=\"done\">تم</string>\n    <string name=\"no_thanks\">لا شكرا</string>\n    <string name=\"enable\">تفعيل</string>\n    <string name=\"cancel_all_downloads_confirm\">سيتم إلغاء جميع التنزيلات النشطة، وستُفقَد البيانات المحملة جزئيًا</string>\n    <string name=\"text_downloads_list_holder\">ليس لديك أية تنزيلات</string>\n    <string name=\"bookmark_added\">تم الإضافة للمحفظة</string>\n    <string name=\"data_deletion\">حذف البيانات</string>\n    <string name=\"show_reading_indicators_summary\">اظهر نسبة القراءة في السجل و المُفضلة</string>\n    <string name=\"show_all\">اظهر الكل</string>\n    <string name=\"invalid_domain_message\">مجال غير صالح</string>\n    <string name=\"history_cleared\">تم مسح سجل التصفح</string>\n    <string name=\"bookmarks_removed\">تم الحذف من المحفظة</string>\n    <string name=\"no_manga_sources_text\">فعّل مصادر المانغا لقراءة المانغا عبر الإنترنت</string>\n    <string name=\"random\">عشوائي</string>\n    <string name=\"services\">خدمات</string>\n    <string name=\"download_started\">بدأ التحميل</string>\n    <string name=\"sources_reorder_tip\">اضغط باستمرار على العناصر لإعادة ترتيبه.\\\"</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"bookmark_removed\">تم الحذف من المحفظة</string>\n    <string name=\"select_range\">اختر نطاق</string>\n    <string name=\"clear_all_history\">امسح كل السجل</string>\n    <string name=\"last_2_hours\">آخر 2 سا</string>\n    <string name=\"manage\">إدارة</string>\n    <string name=\"no_bookmarks_yet\">لا يوجد شيء في المحفظة</string>\n    <string name=\"no_bookmarks_summary\">يمكنك إضافة المانجا إلى المحفظة أثناء قراءتها</string>\n    <string name=\"no_manga_sources\">لا توجد مصادر للمانجا</string>\n    <string name=\"reorder\">إعادة ترتيب</string>\n    <string name=\"empty\">فارغ</string>\n    <string name=\"explore\">استكشف</string>\n    <string name=\"other_cache\">بيانات تخزين مؤقت أخرى</string>\n    <string name=\"storage_usage\">استهلاك التخزين</string>\n    <string name=\"available\">متوفر</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"options\">خيارات</string>\n    <string name=\"incognito_mode\">الوضع الخفي</string>\n    <string name=\"automatic_scroll\">تمرير تلقائي</string>\n    <string name=\"reader_info_pattern\">فصل %1$d/%2$d صفحة %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">عرض شريط المعلومات في قارئ الصفحات</string>\n    <string name=\"folder_with_images\">مجلد مع صور</string>\n    <string name=\"exclude_nsfw_from_history_summary\">المانجات المعلمة كـ(NSFW) لن يتم إضافتها إلى السجل، ولن يتم حفظ تقدمك فيها</string>\n    <string name=\"clear_cookies_summary\">يمكنها المساعدة في حالة حدوث مشاكل. سيتم إلغاء جميع التفويضات</string>\n    <string name=\"confirm_exit\">اضغط زر العود للخلف مرة أخرى للخروج</string>\n    <string name=\"scrobbling_empty_hint\">لتتبع تقدم القراءة، اختر القائمة ← \\\"تتبع\\\" على شاشة تفاصيل المانجا.</string>\n    <string name=\"user_agent\">موجه UserAgent</string>\n    <string name=\"settings_apply_restart_required\">يرجى إعادة تشغيل التطبيق لرؤية التغييرات.\\\"</string>\n    <string name=\"removed_from_favourites\">تم الحذف من المُفضلة</string>\n    <string name=\"no_chapters\">لا توجد فصول</string>\n    <string name=\"allow_unstable_updates\">السماح بالتحديثات غير مستقرة</string>\n    <string name=\"allow_unstable_updates_summary\">تلقي إشعارات حول الإصدارات الغير مستقرة</string>\n    <string name=\"categories_delete_confirm\">هل أنت متأكد أنك تريد حذف قوائم المُفضلة المحددة \\n؟ سيتم فقدان جميع المانجا فيها ولا يمكن التراجع عن هذا.</string>\n    <string name=\"pages_cache\">بيانات التخزين المؤقت للصفحات</string>\n    <string name=\"not_found_404\">المحتوى غير موجود أو تمت إزالته</string>\n    <string name=\"comics_archive_import_description\">يمكنك اختيار ملف أو أكثر بتنسيق cbz أو zip ، سيتم التعرف على كل ملف على أنه مانغا منفصلة.</string>\n    <string name=\"folder_with_images_import_description\">يمكنك اختيار مكان في الذاكرة يحتوي على أرشيفات أو صور. سيتم التعرف على كل أرشيف (أو مجلد فرعي) على أنه فصل.</string>\n    <string name=\"speed\">السرعة</string>\n    <string name=\"feed\">الموجز</string>\n    <string name=\"light_indicator\">مؤشر إل إي دي</string>\n    <string name=\"comics_archive\">أرشيف القصص المصورة</string>\n    <string name=\"importing_manga\">استيراد المانجا</string>\n    <string name=\"import_completed\">تم الإستيراد</string>\n    <string name=\"import_completed_hint\">يمكنك حذف الملف الأصلي من التخزين لتوفير مساحة</string>\n    <string name=\"import_will_start_soon\">الإستيراد سيبدأ عن قريب</string>\n    <string name=\"history_shortcuts\">إظهار اختصارات المانجا الحديثة</string>\n    <string name=\"network_unavailable_hint\">قم بتشغيل الواي فاي أو شبكة الهاتف المحمول لقراءة المانجا عبر الإنترنت</string>\n    <string name=\"contrast\">تباين</string>\n    <string name=\"text_unsaved_changes_prompt\">هل تريد حفظ أو تجاهل التغييرات الغير المحفوظة؟</string>\n    <string name=\"error_no_space_left\">لا توجد مساحة متبقية على الجهاز</string>\n    <string name=\"reader_slider\">إظهار شريط التمرير لتبديل الصفحات</string>\n    <string name=\"server_error\">خطأ من جانب الخادم (%1$d). الرجاء المحاولة مرة أخرى لاحقًا</string>\n    <string name=\"chapters_grid_view\">عرض الشبكة</string>\n    <string name=\"manga_error_description_pattern\">تفاصيل الخطأ:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. حاول &lt;a href=\\\"%2$s\\\"&gt;فتح المانجا في متصفح ويب&lt;/a&gt; للتأكد من أنها متوفرة على مصدرها&lt;br&gt;2. تأكد من أنك تستخدم &lt;a href=kotatsu://about&gt;أحدث إصدار من Kotatsu&lt;/a&gt;&lt;br&gt;3. إذا كانت متوفرة، أرسل تقرير خطأ إلى المطورين.</string>\n    <string name=\"history_shortcuts_summary\">إتاحة المانجا الحديثة بالضغط المطول على أيقونة التطبيق</string>\n    <string name=\"reader_control_ltr_summary\">النقر على الحافة اليمنى أو الضغط على المفتاح الأيمن يؤدي دائمًا إلى الانتقال للصفحة التالية</string>\n    <string name=\"reader_control_ltr\">تحكم مريح في القراءة</string>\n    <string name=\"brightness\">سطوع</string>\n    <string name=\"clear_new_chapters_counters\">قم أيضًا بمسح المعلومات حول الفصول الجديدة</string>\n    <string name=\"color_correction\">تصحيح الألوان</string>\n    <string name=\"reset\">إعادة تعيين</string>\n    <string name=\"discard\">تجاهل</string>\n    <string name=\"webtoon_zoom\">تكبير الويبتون</string>\n    <string name=\"network_unavailable\">الشبكة غير متاحة</string>\n    <string name=\"more\">المزيد</string>\n    <string name=\"prefetch_content\">اعادة تحميل المحتوى</string>\n    <string name=\"enable_logging\">تفعيل التسجيل</string>\n    <string name=\"theme_name_asuka\">أسوكا</string>\n    <string name=\"remove_completed_downloads_confirm\">سيتم حذف سجل التنزيلات الخاص بك بشكل نهائي. لن تتأثر أي ملفات تم تنزيلها</string>\n    <string name=\"theme_name_dynamic\">الديناميكية</string>\n    <string name=\"mark_as_current\">تسجيل على كونها الحالي</string>\n    <string name=\"theme_name_mamimi\">ماميمي</string>\n    <string name=\"theme_name_kanade\">كانادي</string>\n    <string name=\"got_it\">وجدتها</string>\n    <string name=\"downloads_wifi_only_summary\">إيقاف التحميل عند الانتقال إلى شبكة الهاتف المحمول</string>\n    <string name=\"resume\">استئناف</string>\n    <string name=\"cancel_all\">إلغاء الكل</string>\n    <string name=\"source_disabled\">تم تعطيل المصدر</string>\n    <string name=\"theme_name_rikka\">ريكا</string>\n    <string name=\"theme_name_sakura\">ساكورا</string>\n    <string name=\"pause\">إيقاف مؤقت</string>\n    <string name=\"show_on_shelf\">العرض في الرف</string>\n    <string name=\"remove_completed\">تمت الإزالة</string>\n    <string name=\"enable_logging_summary\">سجل بعض الأفعال لغايات التصحيح. لا تقم بتشغيله إذا لم تكن متأكدًا مما تفعله</string>\n    <string name=\"language\">اللغة</string>\n    <string name=\"show_suspicious_content\">إظهار المحتوى مشكوك فيه</string>\n    <string name=\"color_theme\">مخطط الألوان</string>\n    <string name=\"show_in_grid_view\">العرض في الشبكة</string>\n    <string name=\"theme_name_miku\">ميكو</string>\n    <string name=\"theme_name_mion\">ميون</string>\n    <string name=\"nothing_here\">لا يوجد شيئ هنا</string>\n    <string name=\"sync_auth_hint\">يمكنك تسجيل الدخول إلى حساب موجود أصلا أو إنشاء حساب جديد</string>\n    <string name=\"paused\">متوقف مؤقتاً</string>\n    <string name=\"downloads_wifi_only\">التحميل عبر شبكة الوايفاي فقط</string>\n    <string name=\"suggestions_notifications_summary\">إظهار الإشعارات أحيانًا بالمانجا المقترحة</string>\n    <string name=\"mirror_switching_summary\">اللغة العربية</string>\n    <string name=\"suggestions_enable_prompt\">‌‌‍‎‎‍هل ترغب في تلقي اقتراحات المانجا الشخصية؟</string>\n    <string name=\"images_proxy_title\">وكيل تحسين الصور</string>\n    <string name=\"webtoon_zoom_summary\">السماح بإيماءة التكبير في وضع الويب تون</string>\n    <string name=\"share_logs\">مشاركة السجلات</string>\n    <string name=\"downloaded\">تم التنزيل</string>\n    <string name=\"username\">اسم المستخدم</string>\n    <string name=\"password\">كلمة المرور</string>\n    <string name=\"authorization_optional\">التفويض (اختياري)</string>\n    <string name=\"reader_info_bar_summary\">إظهار الوقت الحالي و تقدم القراءة في الجزء العلوي من الشاشة</string>\n    <string name=\"show_pages_numbers_summary\">إظهار أرقام الصفحات في الزاوية السفلية</string>\n    <string name=\"in_progress\">قيد التنفيذ</string>\n    <string name=\"too_many_requests_message\">طلبات كثيرة جدا. حاول مرة أخرى في وقت لاحق</string>\n    <string name=\"email_password_enter_hint\">أدخل بريدك الإلكتروني وكلمة المرور للمتابعة</string>\n    <string name=\"network\">اتصال</string>\n    <string name=\"data_and_privacy\">البيانات والخصوصية</string>\n    <string name=\"download_option_first_n_chapters\">أولاً%s</string>\n    <string name=\"invalid_port_number\">رقم المنفذ غير صالح</string>\n    <string name=\"restore_summary\">استعادة النسخة الاحتياطية التي تم إنشاؤها مسبقًا</string>\n    <string name=\"download_option_all_unread_b\">جميع الفصول غير المقروءة (%s)</string>\n    <string name=\"download_option_next_unread_n_chapters\">التالي غير المقروء %s</string>\n    <string name=\"no_access_to_file\">ليس لديك حق الوصول إلى هذا الملف أو المجلد</string>\n    <string name=\"pick_custom_directory\">اختر مجلد مخصص</string>\n    <string name=\"local_manga_directories\">مجلدات المانجا المحلية</string>\n    <string name=\"description\">وصف</string>\n    <string name=\"this_month\">هذا الشهر</string>\n    <string name=\"voice_search\">البحث الصوتي</string>\n    <string name=\"related_manga\">المانجا ذات الصلة</string>\n    <string name=\"color_light\">فاتح</string>\n    <string name=\"color_dark\">غامق</string>\n    <string name=\"color_white\">ابيض</string>\n    <string name=\"color_black\">أسود</string>\n    <string name=\"background\">خلفية</string>\n    <string name=\"data_not_restored\">لم تتم استعادة البيانات</string>\n    <string name=\"tracker_wifi_only_summary\">لا تتحقق من وجود فصول جديدة باستخدام اتصالات الشبكة المقاسة</string>\n    <string name=\"search_hint\">أدخل عنوان المانجا أو التصنيف أو اسم المصدر</string>\n    <string name=\"data_not_restored_text\">تأكد من تحديد ملف النسخ الاحتياطي الصحيح</string>\n    <string name=\"suggestions_wifi_only_summary\">لا تقم بتحديث الاقتراحات باستخدام اتصالات الشبكة المقيدة</string>\n    <string name=\"captcha_required_summary\">%s يتطلب حل CAPTCHA للعمل بشكل صحيح</string>\n    <string name=\"progress\">تقدم</string>\n    <string name=\"order_added\">تمت الإضافة</string>\n    <string name=\"show\">أعرض</string>\n    <string name=\"unknown\">مجهول</string>\n    <string name=\"related_manga_summary\">عرض قائمة المانغا ذات الصلة. وفي بعض الحالات قد تكون غير دقيقة أو مفقودة</string>\n    <string name=\"invert_colors\">عكس الألوان</string>\n    <string name=\"images_procy_description\">استخدم خدمة wsrv.nl لتقليل استهلاك الانترنت وتسريع تحميل الصور إن أمكن</string>\n    <string name=\"type\">أكتب</string>\n    <string name=\"languages\">اللغات</string>\n    <string name=\"compact\">المدمج</string>\n    <string name=\"address\">عنوان</string>\n    <string name=\"download_option_manual_selection\">حدد الفصول يدويا</string>\n    <string name=\"mirror_switching\">اختر المرآة تلقائيًا</string>\n    <string name=\"suggestion_manga\">اقتراح : %s</string>\n    <string name=\"downloads_resumed\">تم استئناف التنزيلات</string>\n    <string name=\"downloads_paused\">لقد تم إيقاف التنزيلات مؤقتًا</string>\n    <string name=\"downloads_removed\">تمت إزالة التنزيلات</string>\n    <string name=\"downloads_cancelled\">تم إلغاء التنزيلات</string>\n    <string name=\"web_view_unavailable\">WebView غير متوفر: تحقق من تثبيت موفر الWebView</string>\n    <string name=\"clear_network_cache\">مسح ذاكرة التخزين المؤقت للشبكة</string>\n    <string name=\"proxy\">الوكيل</string>\n    <string name=\"invalid_value_message\">قيمة غير صالحة</string>\n    <string name=\"port\">منفذ</string>\n    <string name=\"clear_source_cookies_summary\">مسح ملفات تعريف الارتباط للمجال المحدد فقط. في معظم الحالات سوف يبطل الترخيص</string>\n    <string name=\"download_option_all_chapters\">جميع الفصول مع الترجمة : %s</string>\n    <string name=\"download_option_whole_manga\">المانجا كلها</string>\n    <string name=\"download_option_all_unread\">جميع الفصول غير المقروءة</string>\n    <string name=\"manage_categories\">إدارة القوائم</string>\n    <string name=\"advanced\">مُتقدم</string>\n    <string name=\"catalog\">القائمة</string>\n    <string name=\"source_enabled\">تم تفعيل المصدر</string>\n    <string name=\"sources_catalog\">قائمة المصادر</string>\n    <string name=\"keep_screen_on\">إبقاء الشاشة قيد التشغيل</string>\n    <string name=\"keep_screen_on_summary\">إبقاء الشاشة قيد التشغيل اثناء القرائه</string>\n    <string name=\"content_type_manga\">مانجا</string>\n    <string name=\"categories\">القوائم</string>\n    <string name=\"manga_list\">قائمة المانجا</string>\n    <string name=\"moved_to_top\">تم تحريكه الى الاعلى</string>\n    <string name=\"zoom_in\">تكبير</string>\n    <string name=\"error_corrupted_file\">تم استلام بيانات خاطئه او الملف تالف</string>\n    <string name=\"enhanced_colors_summary\">يقلل من النطاقات، ولكنه قد يؤثر على الأداء</string>\n    <string name=\"state_abandoned\">متروك</string>\n    <string name=\"reader_zoom_buttons\">اظهر ازرار التكبير/التصغير</string>\n    <string name=\"reader_zoom_buttons_summary\">ما إذا كان سيتم إظهار أزرار التحكم في التكبير/التصغير في الزاوية اليمنى السفلية</string>\n    <string name=\"enhanced_colors\">وضع الألوان 32-bit</string>\n    <string name=\"backup_frequency\">تكرار إنشاء النسخ الاحتياطي</string>\n    <string name=\"periodic_backups\">النسخ الاحتياطي الدوري</string>\n    <string name=\"suggest_new_sources\">أقترح مصادر جديدة عند تحديث البرنامج</string>\n    <string name=\"suggest_new_sources_summary\">المطالبة بتمكين المصادر المضافة حديثًا بعد تحديث التطبيق</string>\n    <string name=\"frequency_every_day\">كل يوم</string>\n    <string name=\"online_variant\">البديل على الانترنت</string>\n    <string name=\"frequency_every_2_days\">كل يومين</string>\n    <string name=\"frequency_once_per_week\">مرة في الأسبوع</string>\n    <string name=\"frequency_twice_per_month\">مرتين في الشهر</string>\n    <string name=\"frequency_once_per_month\">مرة في الشهر</string>\n    <string name=\"periodic_backups_enable\">تفعيل النسخ الاحتياطي الدوري</string>\n    <string name=\"backups_output_directory\">مجلد إخراج النسخ الاحتياطية</string>\n    <string name=\"last_successful_backup\">آخر نسخ احتياطي ناجح: %s</string>\n    <string name=\"lock_screen_rotation\">قفل دوران الشاشة</string>\n    <string name=\"available_d\">متاح: %1$d</string>\n    <string name=\"error_multiple_genres_not_supported\">لا يدعم مصدر المانجا هذا التصفية حسب التصنيفات المتعددة</string>\n    <string name=\"disable_nsfw_summary\">قم بتعطيل مصادر NSFW وإخفاء المانجا للبالغين من القائمة إن أمكن</string>\n    <string name=\"reader_optimize_summary\">قم بتقليل جودة الصفحات الموجودة خارج الشاشة لاستخدام ذاكرة أقل</string>\n    <string name=\"no_manga_sources_catalog_text\">لا توجد مصادر متاحة في هذا القسم، أو ربما تمت إضافتها كلها بالفعل.\n\\nابقوا متابعين</string>\n    <string name=\"error_multiple_states_not_supported\">لا يدعم مصدر المانجا هذا التصفية حسب الحالات المتعددة</string>\n    <string name=\"error_search_not_supported\">البحث غير مدعوم من مصدر المانجا هذا</string>\n    <string name=\"manual\">يدوي</string>\n    <string name=\"on_device\">في الجهاز</string>\n    <string name=\"to_top\">الى الاعلى</string>\n    <string name=\"zoom_out\">تصغير</string>\n    <string name=\"reader_optimize\">تقليل استهلاك الذاكرة (تجريبي)</string>\n    <string name=\"main_screen_sections\">أقسام الشاشه الرئيسيه</string>\n    <string name=\"content_type_other\">آخر</string>\n    <string name=\"content_type_comics\">كوميكس</string>\n    <string name=\"content_type_hentai\">هينتاي</string>\n    <string name=\"items_limit_exceeded\">لا يمكن اضافة المزيد من العناصر</string>\n    <string name=\"directories\">المجلدات</string>\n    <string name=\"manage_sources\">مصادر المانجات</string>\n    <string name=\"state_paused\">متوقف</string>\n    <string name=\"state\">الحالة</string>\n    <string name=\"list_options\">خيارات القائمة</string>\n    <string name=\"by_relevance\">ملاءمة</string>\n    <string name=\"no_manga_sources_found\">لم يتم العثور على مصادر مانجا متاحة من خلال طلبك</string>\n    <string name=\"downloads_settings_info\">يمكنك تمكين تباطؤ التنزيل لكل مصدر مانغا بشكل فردي في إعدادات المصدر إذا كنت تواجه مشكلات في الحظر من جانب الخادم</string>\n    <string name=\"error_filter_locale_genre_not_supported\">لا يدعم هذا المصدر التصفية حسب التصنيفات والإعدادات المحلية</string>\n    <string name=\"error_filter_states_genre_not_supported\">لا يدعم هذا المصدر التصفية حسب التصنيفات والحالات</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">قد يساعد في بدء التنزيل إذا كان لديك أي مشاكل معه</string>\n    <string name=\"genres_search_hint\">ابدأ بكتابة اسم التصنيف</string>\n    <string name=\"speed_value\">س%.1f</string>\n    <string name=\"skip\">تخطى</string>\n    <string name=\"grayscale\">تدرج الرمادي</string>\n    <string name=\"globally\">عالمياً</string>\n    <string name=\"this_manga\">هذه المانجا</string>\n    <string name=\"color_correction_apply_text\">يمكن تطبيق هذه الإعدادات عالمياً أو على المانجا الحالية فقط. إذا تم تطبيقه عالمياً، فلن يتم تجاوز الإعدادات الفردية.</string>\n    <string name=\"apply\">طَبِق</string>\n    <string name=\"welcome_text\">الرجاء تحديد مصادر المحتوى التي ترغب في تمكينها. يمكن أيضًا تكوين هذا لاحقًا في الإعدادات</string>\n    <string name=\"volume_unknown\">مجلد مجهول</string>\n    <string name=\"content_rating\">تقييم المحتوى</string>\n    <string name=\"last_read\">آخر ما تم قراءته</string>\n    <string name=\"show_labels_in_navbar\">إظهار التسميات في شريط التنقل</string>\n    <string name=\"suggestions_unavailable_text\">ميزة الاقتراحات معطلة</string>\n    <string name=\"check_for_new_chapters_disabled\">تم تعطيل التحقق من الفصول الجديدة</string>\n    <string name=\"three_months\">ثلاث أشهر</string>\n    <string name=\"migrate\">نقل</string>\n    <string name=\"state_upcoming\">قادم</string>\n    <string name=\"by_name_reverse\">الاسم معكوس</string>\n    <string name=\"mark_as_completed_prompt\">عَلِّم المانجا المحددة كمقروءة بالكامل\n\\n\n\\nتحذير: سيتم فقدان تقدم القراءة الحالي.</string>\n    <string name=\"category_hidden_done\">تم إخفاء هذه القائمة من الشاشة الرئيسية ويمكن الوصول إليها من خلال القائمة ← إدارة القوائم</string>\n    <string name=\"switch_pages_volume_buttons_summary\">استخدم أزرار الصوت للتبديل بين الصفحات</string>\n    <string name=\"vertical\">عمودي</string>\n    <string name=\"show_menu\">أظهر القائمة</string>\n    <string name=\"switch_pages_volume_buttons\">تمكين أزرار الصوت</string>\n    <string name=\"none\">لا شئ</string>\n    <string name=\"long_tap_action\">اضغط مطولاً على الإجراء</string>\n    <string name=\"month\">شهر</string>\n    <string name=\"all_time\">كل الوقت</string>\n    <string name=\"fullscreen_mode\">وضع ملء الشاشة</string>\n    <string name=\"reader_fullscreen_summary\">إخفاء حالة النظام وأشرطة التنقل</string>\n    <string name=\"reading_time_estimation_summary\">قد تكون قيمة تقدير الوقت غير دقيقة</string>\n    <string name=\"reading_time_estimation\">عرض توقيت القراءة المقدرة</string>\n    <string name=\"sync_auth\">سجل المزامنة</string>\n    <string name=\"restore\">أسترجع</string>\n    <string name=\"backup_date_\">تاريخ النسخ الاحتياطي: %s</string>\n    <string name=\"genres_exclude\">اقصاء التصنيفات</string>\n    <string name=\"rating_safe\">آمن</string>\n    <string name=\"rating_adult\">للكبار</string>\n    <string name=\"volume_\">المجلد %d</string>\n    <string name=\"incognito_mode_hint\">لن يتم حفظ تقدم القراءة الخاص بك</string>\n    <string name=\"toggle_ui\">إظهر/إخفِ واجهة المستخدم</string>\n    <string name=\"prev_page\">الصفحة السابقة</string>\n    <string name=\"reader_actions\">أداء القارئ</string>\n    <string name=\"tap_action\">اضغط على الإجراء</string>\n    <string name=\"config_reset_confirm\">إعادة ضبط الإعدادات على القيم الافتراضية؟ لا يمكن التراجع عن هذا الإجراء.</string>\n    <string name=\"use_two_pages_landscape\">استخدم تصميم الصفحتين في الاتجاه الأفقي (تجريبي)</string>\n    <string name=\"default_webtoon_zoom_out\">تصغير الويبتون الافتراضي</string>\n    <string name=\"pages_saving\">حفظ الصفحات</string>\n    <string name=\"day\">يوم</string>\n    <string name=\"manga_migration\">نقل المانجا</string>\n    <string name=\"no_chapters_deleted\">لم يتم حذف أي فصول</string>\n    <string name=\"delete_read_chapters_summary\">احذف الفصول التي قرأتها بالفعل من وحدة التخزين المحلية لتحرير المساحة</string>\n    <string name=\"delete_read_chapters_prompt\">سيؤدي هذا إلى حذف جميع الفصول المُعَلمة كـ\\\"مقروءة\\\" من وحدة التخزين المحلية لديك بشكل دائم. يمكنك إعادة تنزيلها لاحقًا، ولكن قد يتم فقدان الفصول المستوردة إلى الأبد</string>\n    <string name=\"mark_as_completed\">عَلِّم كمقروء</string>\n    <string name=\"prev_chapter\">الفصل السابق</string>\n    <string name=\"next_chapter\">الفصل التالي</string>\n    <string name=\"next_page\">الصفحة التالية</string>\n    <string name=\"migrate_confirmation\">سيتم استبدال المانجا \\\"%1$s\\\" من \\\"%2$s\\\" بـ \\\"%3$s\\\" من \\\"%4$s\\\" في سجلك ومفضلاتك (إن وجدت)</string>\n    <string name=\"migration_completed\">تم النقل بنجاح</string>\n    <string name=\"delete_read_chapters\">حذف الفصول المقروءة</string>\n    <string name=\"chapters_deleted_pattern\">تمت إزالة %1$s، وتم مسح %2$s</string>\n    <string name=\"delete_read_chapters_auto\">حذف فصول المقروءة تلقائياً</string>\n    <string name=\"last_used\">آخر أستخدام</string>\n    <string name=\"_new\">جديد</string>\n    <string name=\"remove_from_history\">حذف من السجل</string>\n    <string name=\"all_languages\">كل اللغات</string>\n    <string name=\"screenshots_block_incognito\">الحظر عند وضع التصفح المتخفي</string>\n    <string name=\"alternatives\">بدائل</string>\n    <string name=\"empty_stats_text\">لا توجد إحصائيات للفترة المحددة</string>\n    <string name=\"image_server\">خادم الصور المُفضل</string>\n    <string name=\"crop_pages\">قص الصفحات</string>\n    <string name=\"pin\">تثبيت</string>\n    <string name=\"unpin\">إلغاء التثبيت</string>\n    <string name=\"source_unpinned\">تم إلغاء تثبيت المصدر</string>\n    <string name=\"source_pinned\">تم تثبيت المصدر</string>\n    <string name=\"sources_unpinned\">تم إلغاء تثبيت المصادر</string>\n    <string name=\"sources_pinned\">تم تثبيت المصادر</string>\n    <string name=\"default_tab\">الصفحة الافتراضية</string>\n    <string name=\"recent_sources\">المصادر الحديثة</string>\n    <string name=\"less_than_minute\">أقل من دقيقة</string>\n    <string name=\"clear_stats_confirm\">هل تريد حقًا مسح جميع إحصائيات القراءة؟ لا يمكن التراجع عن هذا الإجراء.</string>\n    <string name=\"week\">أسبوع</string>\n    <string name=\"pages_read_s\">الصفحات التي قرأت: %s</string>\n    <string name=\"show_pages_thumbs_summary\">قم بتمكين علامة التبويب \\\"الصفحات\\\" في شاشة التفاصيل</string>\n    <string name=\"disable_connectivity_check\">تعطيل التحقق من الاتصال</string>\n    <string name=\"show_updated\">عرض التحديثات</string>\n    <string name=\"rating_suggestive\">موحية</string>\n    <string name=\"reader_actions_summary\">تهيئة الإجراءات لمناطق الشاشة القابلة للنقر عليها</string>\n    <string name=\"ask_for_dest_dir_every_time\">اطلب وجهة المجلد في كل مرة</string>\n    <string name=\"default_page_save_dir\">مجلد حفظ الصفحة الافتراضية</string>\n    <string name=\"preferred_download_format\">تنسيق التحميل المُفضل</string>\n    <string name=\"automatic\">تلقائي</string>\n    <string name=\"multiple_cbz_files\">ملفات CBZ متعددة</string>\n    <string name=\"reading_stats\">إحصائيات القراءة</string>\n    <string name=\"other_manga\">مانجا أخرى</string>\n    <string name=\"statistics\">الإحصائيات</string>\n    <string name=\"clear_stats\">حذف الإحصائيات</string>\n    <string name=\"stats_cleared\">تم حذف الإحصائيات</string>\n    <string name=\"split_by_translations\">تقسيم عن طريق الترجمة</string>\n    <string name=\"split_by_translations_summary\">اعرض الفصول ذات الترجمات المختلفة بشكل منفصل، وليس في قائمة واحدة</string>\n    <string name=\"order_oldest\">الأقدم</string>\n    <string name=\"long_ago_read\">قرأت منذ وقت طويل</string>\n    <string name=\"unread\">غير مقروءة</string>\n    <string name=\"show_pages_thumbs\">إظهار الصور المصغرة للصفحات</string>\n    <string name=\"unsupported_backup_message\">الرجاء تحديد ملف النسخ الاحتياطي السليم لKotatsu</string>\n    <string name=\"minutes_short\">%d ش</string>\n    <string name=\"hours_minutes_short\">%1$d س %2$d ش</string>\n    <string name=\"webtoon_gaps\">الفجوات في وضع الويبتون</string>\n    <string name=\"location\">المكان</string>\n    <string name=\"disable_nsfw_notifications\">تعطيل اشعارات NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">لا تعرض إشعارات حول تحديثات مانجات NSFW</string>\n    <string name=\"tracker_debug_info\">التحقق من سجل الفصول الجديدة</string>\n    <string name=\"tracker_debug_info_summary\">تصحيح المعلومات حول عمليات التحقق من الخلفية للفصول الجديدة</string>\n    <string name=\"search_suggestions\">اقتراحات البحث</string>\n    <string name=\"recent_queries\">الأستعلامات الأخيره</string>\n    <string name=\"suggested_queries\">الاستعلامات المقترحة</string>\n    <string name=\"single_cbz_file\">ملف CBZ واحد</string>\n    <string name=\"hours_short\">%d س</string>\n    <string name=\"webtoon_gaps_summary\">إظهار الفجوات الرأسية بين الصفحات في وضع الويبتون</string>\n    <string name=\"authors\">المؤلفون</string>\n    <string name=\"disable_connectivity_check_summary\">تخطي التحقق من الاتصال في حالة وجود مشكلات في الاتصال (على سبيل المثال، الانتقال إلى وضع عدم الاتصال بالإنترنت على الرغم من اتصال الشبكة)</string>\n    <string name=\"ignore_ssl_errors_summary\">يمكنك تعطيل التحقق من شهادات SSL في حالة مواجهة مشكلات متعلقة بـ SSL عند الوصول إلى موارد الشبكة. قد يؤثر هذا على أمانك. مطلوب إعادة تشغيل التطبيق بعد تغيير هذا الإعداد.</string>\n    <string name=\"runs_on_app_start\">يعمل عند بدء تشغيل التطبيق</string>\n    <string name=\"error_no_data_received\">لم يتم تلقي أي بيانات من الخادم</string>\n    <string name=\"disable\">تعطيل</string>\n    <string name=\"sources_disabled\">تم تعطيل المصادر</string>\n    <string name=\"enable_source\">تمكين المصدر</string>\n    <string name=\"unsupported_source\">مصدر المانجا هذا غير مدعوم</string>\n    <string name=\"blocked_by_server_message\">لقد تم حظرك من قبل الخادم. حاول استخدام اتصال شبكة مختلف (VPN, Proxy, الخ.)</string>\n    <string name=\"less_frequently\">أقل ترددا</string>\n    <string name=\"more_frequently\">أكثر ترددا</string>\n    <string name=\"frequency_of_check\">تردد الفحص</string>\n    <string name=\"pin_navigation_ui\">تثبيت واجهة المستخدم</string>\n    <string name=\"pin_navigation_ui_summary\">لا تخفي شريط التنقل وعرض البحث عند التمرير</string>\n    <string name=\"fix\">اصلاح</string>\n    <string name=\"missing_storage_permission\">لا يوجد إذن للوصول إلى المانجا على وحدة التخزين الخارجية</string>\n    <string name=\"percent_read\">نسبة القراءة</string>\n    <string name=\"percent_left\">النسبة المتبقية</string>\n    <string name=\"chapters_read\">الفصول المقروءة</string>\n    <string name=\"chapters_left\">الفصول المتبقية</string>\n    <string name=\"text_empty_holder_secondary_filtered\">لا توجد مانجا تطابق التصفيات التي حددتها</string>\n    <string name=\"connection_ok\">الاتصال جيد</string>\n    <string name=\"external_source\">خارجي/إضافي</string>\n    <string name=\"plugin_incompatible\">مكون إضافي غير متوافق أو خطأ داخلي. تأكد من استخدام أحدث إصدار من المكون الإضافي وKotatsu</string>\n    <string name=\"invalid_server_address_message\">عنوان الخادم غير صالح</string>\n    <string name=\"retry\">‮حاول مجددا</string>\n    <string name=\"pages_saved\">الصفحات المحفوظة</string>\n    <string name=\"too_many_requests_message_retry\">هنالك الكثير من الطلبات. حاول مرة أخرى بعد%s</string>\n    <string name=\"scrobbler_auth_required\">تسجيل الدخول إلى %s للمتابعة</string>\n    <string name=\"scrobbler_auth_intro\">سجّل الدخول لإعداد التكامل مع %s. سيسمح لك هذا بتتبع تقدمك في قراءة المانجا وحالتك</string>\n    <string name=\"unstable_feature\">ميزة غير مستقرة</string>\n    <string name=\"unstable_feature_summary\">هذه الخاصية تجريبية. يرجى التأكد من وجود نسخة احتياطية لتجنب فقدان البيانات</string>\n    <string name=\"popular_today\">شائعة اليوم</string>\n    <string name=\"year\">السنة</string>\n    <string name=\"error_connection_reset\">تم إعادة ضبط الاتصال</string>\n    <string name=\"show_slider\">إظهار شريط التمرير</string>\n    <string name=\"incognito\">متخفي</string>\n    <string name=\"backup_tg_check\">التحقق من حساب Telegram</string>\n    <string name=\"backup_tg_echo\">اختبار اتصال Telegram</string>\n    <string name=\"backup_tg_id_not_set\">لم يتم تعيين معرف دردشة Telegram</string>\n    <string name=\"telegram_chat_id\">معرف دردشة Telegram</string>\n    <string name=\"open_telegram_bot\">فتح روبوت Telegram</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"plugin_incompatible_with_cause\">خطأ في الإضافة: %s\\n تأكد من أنك تستخدم أحدث إصدار من الإضافة و كوتاتسو</string>\n    <string name=\"invalid_proxy_configuration\">تكوين الوكيل غير صالح</string>\n    <string name=\"skip_all\">تخطي الكل</string>\n    <string name=\"stuck\">عالقة</string>\n    <string name=\"updated_long_ago\">تم التحديث منذ وقت طويل</string>\n    <string name=\"low_rating\">تقييم منخفض</string>\n    <string name=\"sort_order_asc\">ترتيب تصاعدي</string>\n    <string name=\"sort_order_desc\">ترتيب تنازلي</string>\n    <string name=\"by_date\">حسب التاريخ</string>\n    <string name=\"popularity\">الشعبية</string>\n    <string name=\"popular_in_month\">شائعة في الشهر</string>\n    <string name=\"popular_in_year\">شائعة في العام</string>\n    <string name=\"original_language\">اللغة الأصلية</string>\n    <string name=\"demographics\">التركيبة السكانية</string>\n    <string name=\"demographic_shounen\">شونين</string>\n    <string name=\"demographic_shoujo\">شوجو</string>\n    <string name=\"demographic_seinen\">سينين</string>\n    <string name=\"demographic_josei\">جوسي</string>\n    <string name=\"years\">سنوات</string>\n    <string name=\"any\">أي</string>\n    <string name=\"demographic_kodomo\">كودومو</string>\n    <string name=\"content_type_one_shot\">فصل واحد</string>\n    <string name=\"content_type_doujinshi\">دوجينشي</string>\n    <string name=\"content_type_image_set\">مجموعة صور</string>\n    <string name=\"content_type_artist_cg\">CG الفنان</string>\n    <string name=\"filter_search_warning\">هذا المصدر لا يدعم البحث باستخدام عوامل التصفية. تم مسح عوامل التصفية الخاصة بك</string>\n    <string name=\"content_type_game_cg\">CG اللعبة</string>\n    <string name=\"debug\">تصحيح الأخطاء</string>\n    <string name=\"source_code\">التعليمات البرمجية المصدرية</string>\n    <string name=\"user_manual\">دليل المستخدم</string>\n    <string name=\"telegram_group\">مجموعة Telegram</string>\n    <string name=\"landscape\">أفقي</string>\n    <string name=\"portrait\">معرض</string>\n    <string name=\"captcha_required_message\">مطلوب اختبار CAPTCHA</string>\n    <string name=\"handle_links\">معالجة الروابط</string>\n    <string name=\"handle_links_summary\">معالجة الروابط باستخدام هذا التطبيق</string>\n    <string name=\"email\">البريد الإلكتروني</string>\n    <string name=\"unpopular\">غير شعبي</string>\n    <string name=\"chapter_selection_hint\">يمكنك تحديد الفصول لتنزيلها عن طريق النقر المطول على العنصر في قائمة الفصول.</string>\n    <string name=\"download_added\">تمت إضافة التنزيل</string>\n    <string name=\"more_options\">المزيد من الخيارات</string>\n    <string name=\"destination_directory\">دليل الوجهة</string>\n    <string name=\"chapters_all\">جميع الفصول</string>\n    <string name=\"download_cellular_confirm\">هل تريد السماح بالتنزيل عبر شبكة الجوال؟</string>\n    <string name=\"allow_always\">السماح دائمًا</string>\n    <string name=\"error_not_image\">تنسيق غير صالح: الصورة المتوقعة ولكن حصلت على %s</string>\n    <string name=\"genre\">النوع</string>\n    <string name=\"download_over_cellular\">التنزيل عبر شبكة الجوال</string>\n    <string name=\"dont_allow\">عدم السماح</string>\n    <string name=\"access_denied_403\">تم رفض الوصول (403)</string>\n    <string name=\"max_backups_count\">الحد الأقصى لعدد النسخ الاحتياطية</string>\n    <string name=\"clear_database_summary\">إزالة الإدخالات غير المستخدمة من قاعدة البيانات</string>\n    <string name=\"clear_database\">مسح قاعدة البيانات</string>\n    <string name=\"send_backups_telegram\">إرسال النسخ الاحتياطية إلى Telegram</string>\n    <string name=\"test_connection\">اختبار الاتصال</string>\n    <string name=\"open_telegram_bot_summary\">تشغيل روبوت Telegram</string>\n    <string name=\"translation\">الترجمة</string>\n    <string name=\"show_quick_filters\">إظهار عوامل التصفية السريعة</string>\n    <string name=\"show_quick_filters_summary\">إظهار عوامل التصفية السريعة فوق لوحة المفاتيح</string>\n    <string name=\"sfw\">آمن للاستخدام في العمل</string>\n    <string name=\"downloads_background\">التنزيلات في الخلفية</string>\n    <string name=\"download_new_chapters\">تنزيل الفصول الجديدة</string>\n    <string name=\"manga_with_downloaded_chapters\">المانجا التي تم تنزيل فصول منها</string>\n    <string name=\"manga_replaced\">تم استبدال المانجا \\\"%1$s\\\" (%2$s) بـ \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"no_fix_required\">لا يتطلب الإصلاح لـ ”%s“</string>\n    <string name=\"fixing_manga\">جاري إصلاح المانجا</string>\n    <string name=\"fixed\">تم الإصلاح</string>\n    <string name=\"no_alternatives_found\">لم يتم العثور على بدائل ل ”%s“</string>\n    <string name=\"manga_fix_prompt\">سيقوم هذا الخيار بالبحث عن مصادر بديلة للمانجا المحددة. ستستغرق العملية بعض الوقت وستتم في الخلفية</string>\n    <string name=\"start_download\">بدء التنزيل</string>\n    <string name=\"save_manga\">حفظ المانجا</string>\n    <string name=\"save_manga_confirm\">حفظ المانجا المحددة؟ قد يستهلك ذلك حركة البيانات ومساحة التخزين</string>\n    <string name=\"not_in_favorites\">ليس في المفضلة</string>\n    <string name=\"error_image_format\">تنسيق صورة غير مدعوم: %s</string>\n    <string name=\"content_type_novel\">رواية</string>\n    <string name=\"content_type_manhua\">مانهوا</string>\n    <string name=\"recently_added\">أضيفت مؤخرًا</string>\n    <string name=\"added_long_ago\">أضيفت منذ وقت طويل</string>\n    <string name=\"popular_in_hour\">شائعة في الساعة</string>\n    <string name=\"popular_in_week\">شائعة في الأسبوع</string>\n    <string name=\"allow_once\">السماح مرة واحدة</string>\n    <string name=\"ask_every_time\">السؤال في كل مرة</string>\n    <string name=\"content_type_manhwa\">منهوا</string>\n    <string name=\"screen_orientation\">اتجاه الشاشة</string>\n    <string name=\"delete_old_backups\">حذف النسخ الاحتياطية القديمة</string>\n    <string name=\"delete_old_backups_summary\">حذف النسخ الاحتياطية القديمة تلقائيًا</string>\n    <string name=\"telegram_chat_id_summary\">معرف دردشة Telegram المستلمة</string>\n    <string name=\"enable_all_sources_summary\">تمكين جميع المصادر الموجودة</string>\n    <string name=\"all_sources_enabled\">تم تمكين جميع المصادر</string>\n    <string name=\"author\">المؤلف</string>\n    <string name=\"rating\">التقييم</string>\n    <string name=\"source\">المصدر</string>\n    <string name=\"enable_all_sources\">تمكين جميع المصادر</string>\n    <string name=\"error_details\">تفاصيل الخطأ</string>\n    <string name=\"error_disclaimer_manga\">حاول فتح المانجا في متصفح الويب للتأكد من توفرها في مصدرها.</string>\n    <string name=\"error_disclaimer_report\">يمكنك إرسال تقرير عن الأخطاء إلى المطورين. سيساعدنا هذا في التحقق من المشكلة وإصلاحها.</string>\n    <string name=\"restoring_backup\">استعادة النسخة الاحتياطية</string>\n    <string name=\"search_disabled_sources\">البحث في المصادر المعطلة</string>\n    <string name=\"reader_info_bar_transparent\">شريط معلومات القارئ الشفاف</string>\n    <string name=\"chapter_volume_number\">المجلد %1$s الفصل %2$s</string>\n    <string name=\"chapter_number\">الفصل %s</string>\n    <string name=\"unnamed_chapter\">فصل غير مسمى</string>\n    <string name=\"simple\">بسيط</string>\n    <string name=\"reader_controls_in_bottom_bar\">ادوات التحكم بالقارئ في الشريط السفلي</string>\n    <string name=\"chapters_and_pages\">الفصول و الصفحات</string>\n    <string name=\"pages_slider\">شريط تمرير الصفحة</string>\n    <string name=\"screen_rotation_locked\">تم ايقاف دوران الشاشة</string>\n    <string name=\"screen_rotation_unlocked\">تم تشغيل دوران الشاشة</string>\n    <string name=\"link_to_manga_on_s\">رابط المانغا على %s</string>\n    <string name=\"link_to_manga_in_app\">رابط المانغا في Kotatsu</string>\n    <string name=\"backup_restored_background\">سوف يتم استعادة النسخة الاحتياطية في الخلفية</string>\n    <string name=\"error_disclaimer_app_outdated\">يبدو أن إصدار Kotatsu الخاص بك قديم. يرجى تثبيت أحدث إصدار للحصول على جميع الإصلاحات المتاحة.</string>\n    <string name=\"disable_captcha_notifications\">تعطيل اشعارات captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">لن تتلقى إشعارات حول حل CAPTCHA لهذا المصدر ولكن هذا قد يؤدي إلى تعطيل العمليات الخلفية (التحقق من وجود فصول جديدة، الحصول على توصيات، وما إلى ذلك...)</string>\n    <string name=\"global_search\">البحث العالمي</string>\n    <string name=\"search_everywhere\">البحث في كل مكان</string>\n    <string name=\"badges_in_lists\">الشارات في القوائم</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"reader_navigation_inverted\">عكس التنقل اثناء التصفح</string>\n    <string name=\"reader_navigation_inverted_summary\">عكس اتجاه زر التحكم في الصوت ومفتاح التنقل الاتجاهي في الأجهزة (يسار/أعلى/أسفل/يمين)</string>\n    <string name=\"clear_browser_data\">مسح بيانات المتصفح</string>\n    <string name=\"clear_browser_data_summary\">مسح بيانات المتصفح، مثل ذاكرة التخزين المؤقت وملفات تعريف الارتباط. تحذير: قد يصبح التفويض في مصادر المانجا غير صالح.</string>\n    <string name=\"no_write_permission_to_file\">ليس لديك صلاحية لكتابة ملف.</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">لن تظهر المانغا المخصصة للبالغين في الاقتراحات. قد لا يعمل هذا الخيار بدقة مع بعض المصادر.</string>\n    <string name=\"include_disabled_sources\">\"شمل المصادر  المعطلة.\"</string>\n    <string name=\"suggestions_disabled_sources_summary\">\"اضهار الاقتراحات من كل مصادر المانقا  شامل المعطلين.\"</string>\n    <string name=\"tags_warnings\">­ابراز التصنيفات الخطيرة</string>\n    <string name=\"tags_warnings_summary\">ابراز التصنيفات التي قد تكون غير مناسبة لغالبية المستخدين.</string>\n    <string name=\"error_non_file_uri\">لا يمكن استخدام المسار المحدد لأنه لا يشير إلى ملف أو دليل.</string>\n    <string name=\"manga_override_hint\">ستؤثر هذه التغييرات على كيفية عرض المانجا في التطبيق.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-arq/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-arz/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-arz/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-as/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d অধ্যায়</item>\n        <item quantity=\"other\">%1$d অধ্যাযয়সমূহ</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d ঘণ্টা আগতে</item>\n        <item quantity=\"other\">%1$d ঘণ্টা পূৰ্বে</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d মাহ আগতে</item>\n        <item quantity=\"other\">%1$d মাহ পূৰ্বে</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d ঘণ্টা</item>\n        <item quantity=\"other\">%1$d ঘণ্টা</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d মিনিট</item>\n        <item quantity=\"other\">%1$d মিনিট</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d বস্তু</item>\n        <item quantity=\"other\">%1$d বস্তুসমূহ</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d দিন আগতে</item>\n        <item quantity=\"other\">%1$d দিন পূৰ্বে</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d মিনিট আগতে</item>\n        <item quantity=\"other\">%1$d মিনিট পূৰ্বে</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d নতুন অধ্যায়</item>\n        <item quantity=\"other\">%1$d নতুন অধ্যাযয়সমূহ</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-b+yue+Hant/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-b+yue+Hant/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-bci/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-be/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d элемент</item>\n        <item quantity=\"few\">%1$d элементы</item>\n        <item quantity=\"many\">%1$d элементаў</item>\n        <item quantity=\"other\">%1$d элементаў</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d новая частка</item>\n        <item quantity=\"few\">%1$d новыя часткi</item>\n        <item quantity=\"many\">%1$d новых частак</item>\n        <item quantity=\"other\">%1$d новых частак</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d частка</item>\n        <item quantity=\"few\">%1$d часткi</item>\n        <item quantity=\"many\">%1$d частак</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d хвіліну таму</item>\n        <item quantity=\"few\">%1$d хвіліны таму</item>\n        <item quantity=\"many\">%1$d хвілін таму</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d гадзіну таму</item>\n        <item quantity=\"few\">%1$d гадзіны таму</item>\n        <item quantity=\"many\">%1$d гадзін таму</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d дзень таму</item>\n        <item quantity=\"few\">%1$d дні таму</item>\n        <item quantity=\"many\">%1$d дзён таму</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d месяц таму</item>\n        <item quantity=\"few\">%1$d месяцы таму</item>\n        <item quantity=\"many\">%1$d месяцаў таму</item>\n        <item quantity=\"other\">%1$d месяцаў таму</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$dгадзіна</item>\n        <item quantity=\"few\">%1$dгадзіны</item>\n        <item quantity=\"many\">%1$dгадзін</item>\n        <item quantity=\"other\">%1$dгадзін</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$dхвіліна</item>\n        <item quantity=\"few\">%1$dхвіліны</item>\n        <item quantity=\"many\">%1$dхвілін</item>\n        <item quantity=\"other\">%1$dхвілін</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-be/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"local_storage\">Лакальнае сховішча</string>\n    <string name=\"favourites\">Выбранае</string>\n    <string name=\"history\">Гісторыя</string>\n    <string name=\"error_occurred\">Адбылася памылка</string>\n    <string name=\"network_error\">Памылка сеткі</string>\n    <string name=\"details\">Падрабязнасцi</string>\n    <string name=\"chapters\">Раздзелы</string>\n    <string name=\"list\">Спіс</string>\n    <string name=\"detailed_list\">Падрабязны спіс</string>\n    <string name=\"grid\">Табліца</string>\n    <string name=\"list_mode\">Выгляд спісу</string>\n    <string name=\"settings\">Налады</string>\n    <string name=\"remote_sources\">Крыніцы мангі</string>\n    <string name=\"loading_\">Загрузка…</string>\n    <string name=\"chapter_d_of_d\">Глава %1$d з %2$d</string>\n    <string name=\"close\">Закрыць</string>\n    <string name=\"try_again\">Паўтарыць</string>\n    <string name=\"clear_history\">Ачысціць гісторыю</string>\n    <string name=\"nothing_found\">Нічога не знойдзена</string>\n    <string name=\"history_is_empty\">Гісторыя пустая</string>\n    <string name=\"read\">Чытаць</string>\n    <string name=\"you_have_not_favourites_yet\">Выбранага пакуль няма</string>\n    <string name=\"add_to_favourites\">У выбранае</string>\n    <string name=\"add_new_category\">Стварыць катэгорыю</string>\n    <string name=\"add\">Дадаць</string>\n    <string name=\"save\">Захаваць</string>\n    <string name=\"share\">Падзяліцца</string>\n    <string name=\"create_shortcut\">Стварыць ярлык</string>\n    <string name=\"share_s\">Падзялiцца %s</string>\n    <string name=\"search\">Пошук</string>\n    <string name=\"search_manga\">Пошук мангі</string>\n    <string name=\"manga_downloading_\">Спампоўванне мангі…</string>\n    <string name=\"processing_\">Апрацоўка…</string>\n    <string name=\"download_complete\">Спампоўванне завершана</string>\n    <string name=\"downloads\">Спампоўкі</string>\n    <string name=\"by_name\">Па імю</string>\n    <string name=\"popular\">Папулярная</string>\n    <string name=\"updated\">Абноўленая</string>\n    <string name=\"newest\">Новая</string>\n    <string name=\"by_rating\">Па рэйтынгу</string>\n    <string name=\"sort_order\">Сартаванне</string>\n    <string name=\"filter\">Фільтр</string>\n    <string name=\"theme\">Тэма</string>\n    <string name=\"light\">Светлая</string>\n    <string name=\"dark\">Цёмная</string>\n    <string name=\"follow_system\">Як у сістэме</string>\n    <string name=\"pages\">Старонкi</string>\n    <string name=\"clear\">Ачысціць</string>\n    <string name=\"remove\">Выдаліць</string>\n    <string name=\"_s_deleted_from_local_storage\">«%s» выдалены з лакальнага сховішча</string>\n    <string name=\"save_page\">Захаваць старонку</string>\n    <string name=\"page_saved\">Старонка захавана</string>\n    <string name=\"share_image\">Абагуліць відарыс</string>\n    <string name=\"_import\">Імпарт</string>\n    <string name=\"delete\">Выдаліць</string>\n    <string name=\"operation_not_supported\">Аперацыя не падтрымліваецца</string>\n    <string name=\"text_file_not_supported\">Файл не падтрымліваецца. Падтрымліваюцца толькі ZIP і CBZ.</string>\n    <string name=\"no_description\">Няма апісання</string>\n    <string name=\"clear_pages_cache\">Ачысціць кэш старонак</string>\n    <string name=\"text_file_sizes\">Б|кБ|МБ|ГБ|ТБ</string>\n    <string name=\"standard\">Стандартны</string>\n    <string name=\"webtoon\">Манхва</string>\n    <string name=\"read_mode\">Рэжым чытання</string>\n    <string name=\"grid_size\">Памер табліцы</string>\n    <string name=\"search_on_s\">Пошук па %s</string>\n    <string name=\"delete_manga\">Выдаліць мангу</string>\n    <string name=\"reader_settings\">Налады чытання</string>\n    <string name=\"switch_pages\">Гартанне старонак</string>\n    <string name=\"text_delete_local_manga\">Назаўсёды выдаліць «%s» з прылады?</string>\n    <string name=\"_continue\">Працягнцуць</string>\n    <string name=\"error\">Памылка</string>\n    <string name=\"clear_thumbs_cache\">Ачысціць кэш мініяцюр</string>\n    <string name=\"search_history_cleared\">Гісторыя пошуку ачышчана</string>\n    <string name=\"clear_search_history\">Ачысціць гісторыю пошуку</string>\n    <string name=\"internal_storage\">Унутранае сховішча</string>\n    <string name=\"external_storage\">Знешняе сховішча</string>\n    <string name=\"domain\">Дамен</string>\n    <string name=\"app_update_available\">Даступна абнаўленне праграмы</string>\n    <string name=\"open_in_browser\">Адкрыць у браўзеры</string>\n    <string name=\"notifications\">Паведамленні</string>\n    <string name=\"enabled_d_of_d\">Уключана %1$d з %2$d</string>\n    <string name=\"new_chapters\">Новыя раздзелы</string>\n    <string name=\"download\">Спампаваць</string>\n    <string name=\"notifications_settings\">Налады апавяшчэнняў</string>\n    <string name=\"notification_sound\">Гук апавяшчэння</string>\n    <string name=\"light_indicator\">Светлавая iндыкацыя</string>\n    <string name=\"vibration\">Вібрацыя</string>\n    <string name=\"favourites_categories\">Катэгорыі абранага</string>\n    <string name=\"remove_category\">Выдаліць катэгорыю</string>\n    <string name=\"manga_shelf\">Паліца</string>\n    <string name=\"recent_manga\">Нядаўняя манга</string>\n    <string name=\"pages_animation\">Анімацыя гартання</string>\n    <string name=\"manga_save_location\">Папка для загрузак</string>\n    <string name=\"not_available\">Недаступна</string>\n    <string name=\"cannot_find_available_storage\">Не атрымалася знайсці ніводнага даступнага сховішча</string>\n    <string name=\"other_storage\">Іншае сховішча</string>\n    <string name=\"done\">Гатова</string>\n    <string name=\"all_favourites\">Усё абранае</string>\n    <string name=\"favourites_category_empty\">У гэтай катэгорыі нічога няма</string>\n    <string name=\"read_later\">Прачытаць пазней</string>\n    <string name=\"updates\">Абнаўленні</string>\n    <string name=\"text_feed_holder\">Тут паказваюцца новыя раздзелы таго, што вы чытаеце</string>\n    <string name=\"search_results\">Вынікі пошуку</string>\n    <string name=\"new_version_s\">Новая версія: %s</string>\n    <string name=\"size_s\">Памер: %s</string>\n    <string name=\"clear_updates_feed\">Ачысціць стужку абнаўленняў</string>\n    <string name=\"updates_feed_cleared\">Ачышчана</string>\n    <string name=\"rotate_screen\">Павярнуць экран</string>\n    <string name=\"update\">Абнавіць</string>\n    <string name=\"feed_will_update_soon\">Абнаўленне хутка пачнецца</string>\n    <string name=\"track_sources\">Правяраць абнаўленні мангі</string>\n    <string name=\"dont_check\">Не правяраць</string>\n    <string name=\"enter_password\">Увядзіце пароль</string>\n    <string name=\"wrong_password\">Няверны пароль</string>\n    <string name=\"protect_application\">Абараніць праграму</string>\n    <string name=\"protect_application_summary\">Запытваць пароль пры запуску праграмы</string>\n    <string name=\"repeat_password\">Паўтарыце пароль</string>\n    <string name=\"passwords_mismatch\">Паролі не супадаюць</string>\n    <string name=\"about\">Аб праграме</string>\n    <string name=\"app_version\">Версія %s</string>\n    <string name=\"check_for_updates\">Праверыць абнаўленні</string>\n    <string name=\"no_update_available\">Няма даступных абнаўленняў</string>\n    <string name=\"right_to_left\">Справа налева</string>\n    <string name=\"create_category\">Стварыць катэгорыю</string>\n    <string name=\"scale_mode\">Маштабаванне</string>\n    <string name=\"zoom_mode_fit_center\">Умясціць у экран</string>\n    <string name=\"zoom_mode_fit_height\">Падагнаць па вышыні</string>\n    <string name=\"zoom_mode_fit_width\">Падагнаць па шырыні</string>\n    <string name=\"zoom_mode_keep_start\">Зыходны памер</string>\n    <string name=\"black_dark_theme\">Чорная</string>\n    <string name=\"black_dark_theme_summary\">Спажывае менш энергіі на экранах AMOLED</string>\n    <string name=\"backup_restore\">Рэзервовае капіяванне і аднаўленне</string>\n    <string name=\"create_backup\">Стварыць рэзервовую копію</string>\n    <string name=\"restore_backup\">Аднавіць данныя</string>\n    <string name=\"data_restored\">Данныя адноўлены</string>\n    <string name=\"preparing_\">Падрыхтоўка…</string>\n    <string name=\"file_not_found\">Файл не знойдзены</string>\n    <string name=\"data_restored_success\">Усе данныя паспяхова адноўлены</string>\n    <string name=\"data_restored_with_errors\">Данныя адноўлены, але ўзніклі некаторыя памылкі</string>\n    <string name=\"backup_information\">Вы можаце стварыць рэзервовую копію абранага і гісторыі і потым аднавіць іх</string>\n    <string name=\"just_now\">Толькі што</string>\n    <string name=\"yesterday\">Учора</string>\n    <string name=\"long_ago\">Даўно</string>\n    <string name=\"group\">Групаваць</string>\n    <string name=\"today\">Сёння</string>\n    <string name=\"tap_to_try_again\">Паспрабаваць яшчэ раз</string>\n    <string name=\"reader_mode_hint\">Абраны рэжым будзе захаваны для бягучай мангі</string>\n    <string name=\"silent\">Без гуку</string>\n    <string name=\"captcha_required\">Неабходна прайсці CAPTCHA</string>\n    <string name=\"captcha_solve\">Прайсці</string>\n    <string name=\"clear_cookies\">Ачысціць кукi</string>\n    <string name=\"cookies_cleared\">Усе кукi выдалены</string>\n    <string name=\"clear_feed\">Ачысціць стужку</string>\n    <string name=\"text_clear_updates_feed_prompt\">Уся гісторыя абнаўленняў будзе ачышчана і яе нельга будзе вярнуць. Вы ўпэўненыя?</string>\n    <string name=\"check_for_new_chapters\">Праверка новых глаў</string>\n    <string name=\"reverse\">У адваротным парадку</string>\n    <string name=\"sign_in\">Увайсці</string>\n    <string name=\"auth_required\">Для прагляду гэтага кантэнту патрабуецца аўтарызацыя</string>\n    <string name=\"default_s\">Прадвызначаны: %s</string>\n    <string name=\"next\">Далей</string>\n    <string name=\"protect_application_subtitle\">Калі ласка, увядзіце пароль, які спатрэбіцца пры запуску праграмы</string>\n    <string name=\"confirm\">Пацвердзіць</string>\n    <string name=\"password_length_hint\">Пароль павінен змяшчаць не менш за 4 сімвалы</string>\n    <string name=\"text_clear_search_history_prompt\">Вы сапраўды хочаце выдаліць усе апошнія пошукавыя запыты?</string>\n    <string name=\"read_more\">Падрабязна</string>\n    <string name=\"tracker_warning\">Некаторыя вытворцы могуць змяняць паводзіны сістэмы, што можа парушаць выкананне фонавых задач.</string>\n    <string name=\"backup_saved\">Рэзервовая копія паспяхова захавана</string>\n    <string name=\"welcome\">Вітаю</string>\n    <string name=\"text_local_holder_secondary\">Захавайце што-небудзь з інтэрнэт-каталога або імпартуйце гэта з файла.</string>\n    <string name=\"text_local_holder_primary\">Спачатку захавайце што-небудзь</string>\n    <string name=\"text_history_holder_secondary\">Знайдзіце, што пачытаць, у раздзеле «Агляд»</string>\n    <string name=\"text_history_holder_primary\">Тут будзе паказана манга, якую вы чытаеце</string>\n    <string name=\"text_search_holder_secondary\">Паспрабуйце перафармуляваць запыт.</string>\n    <string name=\"text_empty_holder_primary\">Неяк тут пуста…</string>\n    <string name=\"chapter_is_missing\">Глава адсутнічае</string>\n    <string name=\"queued\">У чарзе</string>\n    <string name=\"about_app_translation_summary\">Дапамагчы з перакладам праграмы</string>\n    <string name=\"about_app_translation\">Пераклад</string>\n    <string name=\"text_clear_cookies_prompt\">Вы выйдзеце з усіх крыніц, у якіх вы аўтарызаваны</string>\n    <string name=\"auth_not_supported_by\">Аўтарызацыя на %s не падтрымліваецца</string>\n    <string name=\"auth_complete\">Аўтарызацыя выканана</string>\n    <string name=\"genres\">Жанры</string>\n    <string name=\"state_finished\">Завершана</string>\n    <string name=\"state_ongoing\">Ангоінг</string>\n    <string name=\"system_default\">Па змаўчанні</string>\n    <string name=\"exclude_nsfw_from_history\">Выключыць NSFW мангу з гісторыі</string>\n    <string name=\"show_pages_numbers\">Паказваць нумары старонак</string>\n    <string name=\"computing_\">Вылічэнні…</string>\n    <string name=\"screenshots_allow\">Дазваляць</string>\n    <string name=\"screenshots_policy\">Палітыка скрыншотаў</string>\n    <string name=\"screenshots_block_all\">Заўсёды блакуйце</string>\n    <string name=\"screenshots_block_nsfw\">Забараніць для NSFW</string>\n    <string name=\"disabled\">Адкл.</string>\n    <string name=\"enabled\">Уключаны</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Ня прапаноўваць NSFW мангу</string>\n    <string name=\"text_suggestion_holder\">Пачніце чытаць мангу, і вы атрымаеце персаналізаваныя прапановы</string>\n    <string name=\"suggestions_info\">Усе даныя аналізуюцца толькі лакальна на гэтай прыладзе і нікуды не адпраўляюцца.</string>\n    <string name=\"suggestions_summary\">Прапануеце мангу, заснаваную на вашых перавагах</string>\n    <string name=\"suggestions_enable\">Уключыць прапановы</string>\n    <string name=\"suggestions\">Прапанова</string>\n    <string name=\"onboard_text\">Выберыце мову, на якой вы хочаце чытаць мангу. Вы зможаце змяніць гэта пазней.</string>\n    <string name=\"reset_filter\">Скінуць фільтр</string>\n    <string name=\"always\">Заўсёды</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"never\">Ніколі</string>\n    <string name=\"only_using_wifi\">Толькі праз Wi-Fi</string>\n    <string name=\"logged_in_as\">Вы аўтарызаваны як %s</string>\n    <string name=\"preload_pages\">Папярэдняя загрузка старонак</string>\n    <string name=\"various_languages\">Розныя мовы</string>\n    <string name=\"search_chapters\">Знайсці главу</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"chapters_empty\">У гэтай манге няма раздзелаў</string>\n    <string name=\"hide\">Схаваць</string>\n    <string name=\"appearance\">Знешні выгляд</string>\n    <string name=\"disable_all\">Адключыць усе</string>\n    <string name=\"use_fingerprint\">Выкарыстоўваць біяметрычныя дадзеныя, калі яны даступныя</string>\n    <string name=\"appwidget_shelf_description\">Манга з Вашага абранага</string>\n    <string name=\"appwidget_recent_description\">Манга, якую вы нядаўна чыталі</string>\n    <string name=\"suggestions_updating\">Абнаўленне рэкамендацый</string>\n    <string name=\"local_manga_processing\">Апрацоўка захаванай мангі</string>\n    <string name=\"chapters_will_removed_background\">Раздзелы будуць выдалены ў фонавым рэжыме</string>\n    <string name=\"check_new_chapters_title\">Правяраць новыя главы і паведамляць пра іх</string>\n    <string name=\"show_notification_new_chapters_on\">Вы будзеце атрымліваць апавяшчэнні пра абнаўленні мангі, якую вы чытаеце</string>\n    <string name=\"show_notification_new_chapters_off\">Вы не будзеце атрымліваць паведамленні, але новыя главы будуць паказаны ў спісе</string>\n    <string name=\"notifications_enable\">Уключыць апавяшчэнні</string>\n    <string name=\"bookmarks\">Закладкі</string>\n    <string name=\"bookmark_removed\">Закладка выдалена</string>\n    <string name=\"bookmark_added\">Закладка дадазена</string>\n    <string name=\"removed_from_history\">Выдалена з гісторыі</string>\n    <string name=\"dns_over_https\">DNS праз HTTPS</string>\n    <string name=\"detect_reader_mode\">Аўтавызначэнне рэжыму чытання</string>\n    <string name=\"detect_reader_mode_summary\">Аўтаматычна вызначаць, ці з\\'яўляецца манга манхвой</string>\n    <string name=\"new_sources_text\">Даступныя новыя крыніцы мангі</string>\n    <string name=\"download_slowdown\">Запавольванне спампоўкі</string>\n    <string name=\"suggestions_excluded_genres\">Выключыць жанры</string>\n    <string name=\"suggestions_excluded_genres_summary\">Укажыце жанры, якія вы не хочаце бачыць у рэкамендацыях</string>\n    <string name=\"text_delete_local_manga_batch\">Выдаліць выбраныя элементы з прылады назаўжды\\?</string>\n    <string name=\"removal_completed\">Выдаленне завершана</string>\n    <string name=\"download_slowdown_summary\">Дапамагае пазбегнуць блакіроўкі па IP-адрасе</string>\n    <string name=\"bookmark_remove\">Выдаліць закладку</string>\n    <string name=\"default_mode\">Рэжым па змаўчанні</string>\n    <string name=\"empty_favourite_categories\">Няма катэгорый абранага</string>\n    <string name=\"name\">Назва</string>\n    <string name=\"edit\">Змяніць</string>\n    <string name=\"edit_category\">Змяніць катэгорыю</string>\n    <string name=\"bookmark_add\">Дадаць закладку</string>\n    <string name=\"undo\">Адмена</string>\n    <string name=\"disable_battery_optimization\">Адключыць аптымізацыю акумулятара</string>\n    <string name=\"disable_battery_optimization_summary\">Дапамагае з фонавай праверкай абнаўленняў</string>\n    <string name=\"crash_text\">Штосьці пайшло не так. Калі ласка, адпраўце справаздачу пра памылку распрацоўшчыкам, каб дапамагчы нам яе выправіць.</string>\n    <string name=\"send\">Адправіць</string>\n    <string name=\"invalid_domain_message\">Памылковы дамен</string>\n    <string name=\"report\">Справаздача</string>\n    <string name=\"tracking\">Адсочванне</string>\n    <string name=\"status_completed\">Завершана</string>\n    <string name=\"status_on_hold\">Адкладзена</string>\n    <string name=\"status_dropped\">Кінута</string>\n    <string name=\"show_reading_indicators\">Паказваць індыкатары прагрэсу чытання</string>\n    <string name=\"status_planned\">Запланавана</string>\n    <string name=\"status_reading\">Чытаю</string>\n    <string name=\"logout\">Выйсці</string>\n    <string name=\"show_reading_indicators_summary\">Паказаць працэнт прачытаных у гісторыі і абраным</string>\n    <string name=\"data_deletion\">Выдаленне даных</string>\n    <string name=\"show_all\">Паказаць усе</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Манга, пазначаная як NSFW, не будзе дададзеная ў гісторыю і ваш прагрэс не будзе захаваны</string>\n    <string name=\"clear_cookies_summary\">Можа дапамагчы з некаторымі праблемам. Усе аўтарызацыі будуць ануляваныя</string>\n    <string name=\"not_found_404\">Змесціва не знойдзена ці выдалена</string>\n    <string name=\"status_re_reading\">Перачытваю</string>\n    <string name=\"select_range\">Выберыце дыяпазон</string>\n    <string name=\"nothing_here\">Тут нічога няма</string>\n    <string name=\"services\">Службы</string>\n    <string name=\"theme_name_kanade\">Канадзе</string>\n    <string name=\"clear_all_history\">Ачысціць усю гісторыю</string>\n    <string name=\"history_cleared\">Гісторыя ачышчана</string>\n    <string name=\"incognito_mode\">Рэжым інкогніта</string>\n    <string name=\"categories_delete_confirm\">Вы ўпэўнены, што хочаце выдаліць выбраныя абраныя катэгорыі? \\nУся манга ў ім будзе страчана, і гэта нельга будзе адрабіць.</string>\n    <string name=\"no_bookmarks_summary\">Вы можаце стварыць закладку падчас чытання мангі</string>\n    <string name=\"saved_manga\">Захаваная манга</string>\n    <string name=\"theme_name_mamimi\">Мамімі</string>\n    <string name=\"scrobbling_empty_hint\">Каб адсочваць ход чытання, выберыце Меню → Адсочваць на экране падрабязнасцей мангі.</string>\n    <string name=\"server_error\">Памылка сервера (%1$d). Калі ласка паспрабуйце зноў пазней</string>\n    <string name=\"clear_new_chapters_counters\">Таксама ачысціць інфармацыю аб новых раздзелах</string>\n    <string name=\"compact\">Кампактны</string>\n    <string name=\"prefetch_content\">Папярэдняя загрузка кантэнту</string>\n    <string name=\"mark_as_current\">Пазначыць як бягучы</string>\n    <string name=\"error_no_space_left\">На прыладзе не засталося месца</string>\n    <string name=\"network_unavailable\">Сетка недаступная</string>\n    <string name=\"network_unavailable_hint\">Каб чытаць мангу онлайн, уключыце Wi-Fi або мабільную сетку</string>\n    <string name=\"webtoon_zoom\">Маштабаванне ў рэжыме манхвы</string>\n    <string name=\"theme_name_dynamic\">Дынамічны</string>\n    <string name=\"color_theme\">Каляровая гама</string>\n    <string name=\"language\">Мова</string>\n    <string name=\"account_already_exists\">Уліковы запіс ужо існуе</string>\n    <string name=\"back\">Назад</string>\n    <string name=\"sync\">Сінхранізацыя</string>\n    <string name=\"sync_title\">Сінхранізацыя вашых дадзеных</string>\n    <string name=\"email_enter_hint\">Каб працягнуць, увядзіце свой адрас электроннай пошты</string>\n    <string name=\"import_completed_hint\">Вы можаце выдаліць зыходны файл са сховішча, каб зэканоміць месца</string>\n    <string name=\"import_will_start_soon\">Хутка пачнецца імпарт</string>\n    <string name=\"manga_error_description_pattern\">Дэталі памылкі:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Паспрабуйце &lt;a href=%2$s&gt;адкрыць мангу ў вэб-браўзеры&lt;/a&gt;, каб пераканацца, што яна даступная ў крыніцы&lt;br&gt;2. Упэўніцеся, што вы выкарыстоўваеце &lt;a href=kotatsu://about&gt;апошнюю версію Kotatsu&lt;/a&gt;&lt;br&gt;3. Калі ён даступны, адпраўце распрацоўнікам справаздачу аб памылцы.</string>\n    <string name=\"history_shortcuts\">Паказаць апошнія ярлыкі мангі</string>\n    <string name=\"history_shortcuts_summary\">Зрабіце нядаўнюю мангу даступнай, доўга націскаючы на значок праграмы</string>\n    <string name=\"reader_control_ltr_summary\">Не настройвайце кірунак пераключэння старонак у рэжым чытання, напрыклад, націск правай клавішы заўсёды перамыкае на наступную старонку. Гэтая опцыя ўплывае толькі на апаратныя прылады ўводу</string>\n    <string name=\"reader_control_ltr\">Эрганамічнае кіраванне рэжымам чытання</string>\n    <string name=\"color_correction\">Карэкцыя колеру</string>\n    <string name=\"brightness\">Яркасць</string>\n    <string name=\"contrast\">Кантраст</string>\n    <string name=\"reset\">Скінуць</string>\n    <string name=\"text_unsaved_changes_prompt\">Захаваць ці адхіліць незахаваныя змены\\?</string>\n    <string name=\"discard\">Адмяніць</string>\n    <string name=\"enable_logging\">Уключыць запіс</string>\n    <string name=\"share_logs\">Падзяліцца логамі</string>\n    <string name=\"enable_logging_summary\">Запішыце некаторыя дзеянні для адладкі. Уключайце толькі калі ведаеце, што робіце</string>\n    <string name=\"show_suspicious_content\">Паказаць падазроны кантэнт</string>\n    <string name=\"canceled\">Адменена</string>\n    <string name=\"manage\">Кіраваць</string>\n    <string name=\"available\">Даступны</string>\n    <string name=\"feed\">Стужка</string>\n    <string name=\"reader_slider\">Паказаць паўзунок пераключэння старонак</string>\n    <string name=\"source_disabled\">Крыніца адключана</string>\n    <string name=\"show_in_grid_view\">Паказаць у выглядзе сеткі</string>\n    <string name=\"theme_name_miku\">Міку</string>\n    <string name=\"theme_name_asuka\">Аска</string>\n    <string name=\"theme_name_mion\">Міён</string>\n    <string name=\"theme_name_rikka\">Рыка</string>\n    <string name=\"theme_name_sakura\">Сакура</string>\n    <string name=\"no_chapters\">Няма раздзелаў</string>\n    <string name=\"automatic_scroll\">Аўтаматычная пракрутка</string>\n    <string name=\"reader_info_pattern\">Разд. %1$d/%2$d Стар. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Паказаць інфармацыйную панэль у праграме чытання</string>\n    <string name=\"comics_archive\">Архіў коміксаў</string>\n    <string name=\"folder_with_images\">Папка з відарысамі</string>\n    <string name=\"importing_manga\">Імпарт мангі</string>\n    <string name=\"import_completed\">Імпарт завершаны</string>\n    <string name=\"last_2_hours\">Апошнія 2 гадзіны</string>\n    <string name=\"no_bookmarks_yet\">Закладак пакуль няма</string>\n    <string name=\"bookmarks_removed\">Закладкі выдалены</string>\n    <string name=\"no_manga_sources\">Няма крыніц мангі</string>\n    <string name=\"no_manga_sources_text\">Каб чытаць мангу онлайн, уключыце крыніцы мангі</string>\n    <string name=\"random\">Выпадковы</string>\n    <string name=\"reorder\">Змяніць парадак</string>\n    <string name=\"empty\">Пуста</string>\n    <string name=\"explore\">Агляд</string>\n    <string name=\"confirm_exit\">Націсніце «Назад» яшчэ раз, каб выйсці</string>\n    <string name=\"exit_confirmation_summary\">Двойчы націсніце «Назад», каб выйсці з праграмы</string>\n    <string name=\"exit_confirmation\">Пацверджанне выхаду</string>\n    <string name=\"pages_cache\">Кэш старонак</string>\n    <string name=\"other_cache\">Іншы кэш</string>\n    <string name=\"storage_usage\">Выкарыстанне памяці</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"removed_from_favourites\">Выдалена з абранага</string>\n    <string name=\"options\">Параметры</string>\n    <string name=\"user_agent\">Загаловак UserAgent</string>\n    <string name=\"allow_unstable_updates\">Дазволіць нестабільныя абнаўленні</string>\n    <string name=\"allow_unstable_updates_summary\">Атрымліваць апавяшчэнні аб нестабільных зборках</string>\n    <string name=\"download_started\">Спампоўка пачалася</string>\n    <string name=\"settings_apply_restart_required\">Калі ласка, перазапусціце праграму каб прымяніць змены</string>\n    <string name=\"got_it\">Зразумеў</string>\n    <string name=\"sources_reorder_tip\">Націсніце і ўтрымлівайце элемент, каб змяніць іх парадак</string>\n    <string name=\"comics_archive_import_description\">Вы можаце выбраць адзін або некалькі файлаў .cbz або .zip, кожны файл будзе распазнаны як асобная манга.</string>\n    <string name=\"folder_with_images_import_description\">Вы можаце выбраць каталог з архівамі або відарысамі. Кожны архіў (або падкаталог) будзе прызнаны раздзелам.</string>\n    <string name=\"speed\">Хуткасць</string>\n    <string name=\"show_on_shelf\">Паказаць на паліцы</string>\n    <string name=\"find_similar\">Знайсці падобныя</string>\n    <string name=\"sync_auth_hint\">Вы можаце ўвайсці ў існуючы ўліковы запіс або стварыць новы</string>\n    <string name=\"sync_settings\">Налады сінхранізацыі</string>\n    <string name=\"server_address\">Адрас сервера</string>\n    <string name=\"mirror_switching\">Выберыце люстэрка аўтаматычна</string>\n    <string name=\"mirror_switching_summary\">Аўтаматычна пераключаць дамены для крыніц мангі ў выпадку памылак, калі даступныя люстэркі</string>\n    <string name=\"ignore_ssl_errors\">Ігнараваць памылкі SSL</string>\n    <string name=\"resume\">Аднавіць</string>\n    <string name=\"paused\">Прыпынена</string>\n    <string name=\"cancel_all\">Адмяніць усе</string>\n    <string name=\"downloads_wifi_only\">Спампаваць толькі праз Wi-Fi</string>\n    <string name=\"sync_host_description\">Вы можаце выкарыстоўваць уласны сервер сінхранізацыі або сервер па змаўчанні. Не змяняйце гэта, калі вы не ўпэўненыя, што робіце.</string>\n    <string name=\"pause\">Паўза</string>\n    <string name=\"remove_completed\">Выдаліць завершаныя</string>\n    <string name=\"downloads_wifi_only_summary\">Спыніць загрузку пры пераключэнні на мабільную сетку</string>\n    <string name=\"suggestions_notifications_summary\">Часам паказваць апавяшчэнні з прапанаванай мангай</string>\n    <string name=\"more\">Больш</string>\n    <string name=\"enable\">Уключыць</string>\n    <string name=\"cancel_all_downloads_confirm\">Усе актыўныя спампоўкі будуць адменены, часткова спампаваныя даныя будуць страчаны</string>\n    <string name=\"suggestions_enable_prompt\">Хочаце атрымліваць персаналізаваныя прапановы мангі\\?</string>\n    <string name=\"suggestion_manga\">Прапанова: %s</string>\n    <string name=\"no_thanks\">Не, дзякуй</string>\n    <string name=\"remove_completed_downloads_confirm\">Ваша гісторыя загрузак будзе выдалена назаўжды. Загружаныя файлы не будуць закрануты</string>\n    <string name=\"text_downloads_list_holder\">У вас няма загрузак</string>\n    <string name=\"downloads_resumed\">Спампоўкі аднавіліся</string>\n    <string name=\"downloads_paused\">Спампоўкі прыпыненыя</string>\n    <string name=\"downloads_removed\">Спампоўкі выдалены</string>\n    <string name=\"downloads_cancelled\">Спампоўкі былі адменены</string>\n    <string name=\"web_view_unavailable\">WebView недаступны: праверце, ці ўсталяваны пастаўшчык WebView</string>\n    <string name=\"type\">Тып</string>\n    <string name=\"address\">Адрас</string>\n    <string name=\"port\">Порт</string>\n    <string name=\"proxy\">Проксі</string>\n    <string name=\"clear_network_cache\">Ачысціць сеткавы кэш</string>\n    <string name=\"invalid_value_message\">Няправільнае значэнне</string>\n    <string name=\"downloaded\">Спампавана</string>\n    <string name=\"images_proxy_title\">Проксі для аптымізацыі відарысаў</string>\n    <string name=\"username\">Імя карыстальніка</string>\n    <string name=\"invalid_port_number\">Няправільны нумар порта</string>\n    <string name=\"authorization_optional\">Аўтарызацыя (неабавязкова)</string>\n    <string name=\"images_procy_description\">Калі гэта магчыма, выкарыстоўваць службу wsrv.nl, каб паменшыць выкарыстанне трафіку і паскорыць загрузку відарысаў</string>\n    <string name=\"password\">Пароль</string>\n    <string name=\"invert_colors\">Інвертаваць колеры</string>\n    <string name=\"show_pages_numbers_summary\">Паказаць нумары старонак у ніжнім куце</string>\n    <string name=\"network\">Сетка</string>\n    <string name=\"data_and_privacy\">Дадзеныя і канфідэнцыяльнасць</string>\n    <string name=\"webtoon_zoom_summary\">Дазволіць жэсты маштабавання ў рэжыме манхвы</string>\n    <string name=\"restore_summary\">Аднавіць раней створаную рэзервовую копію</string>\n    <string name=\"reader_info_bar_summary\">Паказаць бягучы час і ход чытання ў верхняй частцы экрана</string>\n    <string name=\"clear_source_cookies_summary\">Выдаліць файлы cookie толькі для вызначанага дамена. У большасці выпадкаў гэта робіць аўтарызацыю несапраўднай</string>\n    <string name=\"download_option_whole_manga\">Манга цалкам</string>\n    <string name=\"local_manga_directories\">Лакальныя каталогі мангі</string>\n    <string name=\"download_option_all_chapters\">Усе раздзелы з перакладам %s</string>\n    <string name=\"download_option_first_n_chapters\">Першыя %s</string>\n    <string name=\"download_option_all_unread_b\">Усе непрачытаныя раздзелы (%s)</string>\n    <string name=\"download_option_all_unread\">Усе непрачытаныя раздзелы</string>\n    <string name=\"download_option_manual_selection\">Выбірайце раздзелы ўручную</string>\n    <string name=\"pick_custom_directory\">Выберыце карыстальніцкі каталог</string>\n    <string name=\"download_option_next_unread_n_chapters\">Наступная непрачытаная %s</string>\n    <string name=\"no_access_to_file\">У вас няма доступу да гэтага файла або каталога</string>\n    <string name=\"description\">Апісанне</string>\n    <string name=\"this_month\">Гэты месяц</string>\n    <string name=\"voice_search\">Галасавы пошук</string>\n    <string name=\"related_manga\">Падобная манга</string>\n    <string name=\"color_light\">Светлы</string>\n    <string name=\"color_dark\">Цёмны</string>\n    <string name=\"color_white\">Белы</string>\n    <string name=\"data_not_restored\">Дадзеныя не былі адноўлены</string>\n    <string name=\"suggestions_wifi_only_summary\">Не абнаўляць рэкамендацыі пры выкарыстанні лімітнага інтэрнэт-злучэння</string>\n    <string name=\"tracker_wifi_only_summary\">Не правяраць наяўнасць новых раздзелаў пры выкарыстанні лімітнага інтэрнэт-злучэння</string>\n    <string name=\"color_black\">Чорны</string>\n    <string name=\"background\">Фон</string>\n    <string name=\"data_not_restored_text\">Пераканайцеся, што вы выбралі правільны файл рэзервовай копіі</string>\n    <string name=\"search_hint\">Увядзіце назву мангі, жанр або назву крыніцы</string>\n    <string name=\"progress\">Прагрэс</string>\n    <string name=\"manage_categories\">Кіраванне катэгорыямі</string>\n    <string name=\"order_added\">Дададзена</string>\n    <string name=\"show\">Паказаць</string>\n    <string name=\"captcha_required_summary\">Для правільнай працы %s патрабуецца праверка captcha</string>\n    <string name=\"languages\">Мовы</string>\n    <string name=\"unknown\">Невядомы</string>\n    <string name=\"in_progress\">У працэсе</string>\n    <string name=\"disable_nsfw\">Адключыць NSFW</string>\n    <string name=\"too_many_requests_message\">Занадта шмат запытаў. Паўтарыце спробу пазней</string>\n    <string name=\"related_manga_summary\">Паказаць спіс звязанай мангі. У некаторых выпадках ён можа быць недакладным або адсутнічаць</string>\n    <string name=\"advanced\">Прасунутая</string>\n    <string name=\"manga_list\">Спіс мангі</string>\n    <string name=\"error_corrupted_file\">Вяртаюцца няправільныя дадзеныя ці файл пашкоджаны</string>\n    <string name=\"on_device\">На прыладзе</string>\n    <string name=\"moved_to_top\">Перанесены ўверх</string>\n    <string name=\"items_limit_exceeded\">Больш нельга дадаваць элементы</string>\n    <string name=\"directories\">Каталогі</string>\n    <string name=\"main_screen_sections\">Раздзелы галоўнага экрана</string>\n    <string name=\"to_top\">Уверх</string>\n    <string name=\"zoom_in\">Павялічыць</string>\n    <string name=\"reader_zoom_buttons_summary\">Ці паказваць кнопкі кіравання маштабаваннем у правым ніжнім куце</string>\n    <string name=\"reader_zoom_buttons\">Паказаць кнопкі маштабавання</string>\n    <string name=\"zoom_out\">Зменшыць</string>\n    <string name=\"keep_screen_on\">Трымаць экран уключаным</string>\n    <string name=\"keep_screen_on_summary\">Ня выключаць экран падчас чытання мангі</string>\n    <string name=\"state_abandoned\">Кінута</string>\n    <string name=\"categories\">Катэгорыі</string>\n    <string name=\"list_options\">Параметры спісу</string>\n    <string name=\"suggest_new_sources\">Прапаноўваць новыя крыніцы пасля абнаўлення праграмы</string>\n    <string name=\"enhanced_colors_summary\">Памяншае паласы, але можа паўплываць на прадукцыйнасць</string>\n    <string name=\"by_relevance\">Актуальнасць</string>\n    <string name=\"enhanced_colors\">32-бітны каляровы рэжым</string>\n    <string name=\"suggest_new_sources_summary\">Прапаноўваць крыніцы мангі, дададзеныя ў апошнім абнаўленні праграмы</string>\n    <string name=\"online_variant\">Анлайн варыянт</string>\n    <string name=\"frequency_every_day\">Кожны дзень</string>\n    <string name=\"backup_frequency\">Частата стварэння рэзервовых копій</string>\n    <string name=\"periodic_backups_enable\">Уключыць рэзервовае капіраванне па раскладзе</string>\n    <string name=\"frequency_every_2_days\">Кожныя 2 дні</string>\n    <string name=\"frequency_once_per_week\">Раз на тыдзень</string>\n    <string name=\"periodic_backups\">Перыядычнае рэзервовае капіраванне</string>\n    <string name=\"frequency_twice_per_month\">Два разы на месяц</string>\n    <string name=\"frequency_once_per_month\">Адзін раз у месяц</string>\n    <string name=\"last_successful_backup\">Апошняе паспяховае рэзервовае капіраванне: %s</string>\n    <string name=\"backups_output_directory\">Вывадны каталог рэзервовых копій</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">Блакаванне павароту экрана</string>\n    <string name=\"sources_catalog\">Каталог крыніц</string>\n    <string name=\"content_type_manga\">Манга</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_hentai\">Хентай</string>\n    <string name=\"content_type_comics\">Коміксы</string>\n    <string name=\"catalog\">Каталог</string>\n    <string name=\"manage_sources\">Кіраванне крыніцамі</string>\n    <string name=\"no_manga_sources_found\">Па вашаму запыту не знойдзена даступных крыніц мангі</string>\n    <string name=\"manual\">Уручную</string>\n    <string name=\"source_enabled\">Крыніца ўключана</string>\n    <string name=\"disable_nsfw_summary\">Адключыць крыніцы NSFW і схавайць мангу для дарослых са спісу, калі гэта магчыма</string>\n    <string name=\"no_manga_sources_catalog_text\">У гэтым раздзеле няма даступных крыніц, ці ўсе яны маглі быць ужо дададзены.\n\\nСачыце за абнаўленнямі</string>\n    <string name=\"available_d\">Даступна: %1$d</string>\n    <string name=\"content_type_other\">Іншае</string>\n    <string name=\"state_paused\">Прыпынена</string>\n    <string name=\"error_multiple_states_not_supported\">Фільтраванне па некалькіх станам не падтрымліваецца гэтай крыніцай мангі</string>\n    <string name=\"reader_optimize\">Памяншэнне спажывання памяці (бэта)</string>\n    <string name=\"error_multiple_genres_not_supported\">Фільтраванне па некалькіх жанрах не падтрымліваецца гэтай крыніцай мангі</string>\n    <string name=\"error_search_not_supported\">Пошук не падтрымліваецца гэтай крыніцай мангі</string>\n    <string name=\"reader_optimize_summary\">Паменшыць якасць закадравых старонак, каб выкарыстоўваць менш памяці</string>\n    <string name=\"state\">Стан</string>\n    <string name=\"error_filter_states_genre_not_supported\">Гэта крыніца не падтрымлівае фільтрацыю па жанрах і станах</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Гэта крыніца не падтрымлівае фільтрацыю па жанрах і лакалі</string>\n    <string name=\"apply\">Ужыць</string>\n    <string name=\"genres_search_hint\">Пачніце ўводзіць назву жанру</string>\n    <string name=\"globally\">Глабальна</string>\n    <string name=\"downloads_settings_info\">Вы можаце ўключыць запаволенне загрузкі для кожнай крыніцы мангі асобна ў наладах крыніцы, калі ў вас узніклі праблемы з блакіроўкай на баку сервера</string>\n    <string name=\"this_manga\">Гэтая манга</string>\n    <string name=\"skip\">Прапусціць</string>\n    <string name=\"color_correction_apply_text\">Гэтыя налады могуць прымяняцца глабальна або толькі да бягучай мангі. Пры глабальным прымяненні індывідуальныя налады не будуць перавызначаны.</string>\n    <string name=\"grayscale\">Адценні шэрага</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Можа дапамагчы з пачаткам загрузкі, калі у вас узнікаюць з ёй праблемы</string>\n    <string name=\"welcome_text\">Выберыце, якія крыніцы змесціва вы хочаце ўключыць. Гэта таксама можна наладзіць пазней ў наладах</string>\n    <string name=\"restore\">Аднавіць</string>\n    <string name=\"backup_date_\">Дата стварэння рэзервовай копіі: %s</string>\n    <string name=\"sync_auth\">Увайсці ў акаўнт сінхранізацыі</string>\n    <string name=\"by_name_reverse\">Імя (зваротнае)</string>\n    <string name=\"content_rating\">Рэйтынг кантэнту</string>\n    <string name=\"genres_exclude\">Выключыць жанры</string>\n    <string name=\"rating_safe\">Бяспечны</string>\n    <string name=\"rating_suggestive\">З падказкамі</string>\n    <string name=\"rating_adult\">Дарослы</string>\n    <string name=\"default_tab\">Укладка па змаўчанні</string>\n    <string name=\"state_upcoming\">Чакаецца</string>\n    <string name=\"volume_\">Том %d</string>\n    <string name=\"volume_unknown\">Невядомы том</string>\n    <string name=\"incognito_mode_hint\">Ваш прагрэс чытання не будзе захаваны</string>\n    <string name=\"vertical\">Вертыкальны</string>\n    <string name=\"category_hidden_done\">Гэтая катэгорыя была схаваная з галоўнага экрана і даступная праз Меню → Кіраванне катэгорыямі</string>\n    <string name=\"last_read\">Апошняе чытанне</string>\n    <string name=\"show_menu\">Паказаць меню</string>\n    <string name=\"toggle_ui\">Паказаць/схаваць інтэрфейс</string>\n    <string name=\"prev_chapter\">Папярэдняя глава</string>\n    <string name=\"next_chapter\">Наступная глава</string>\n    <string name=\"prev_page\">Папярэдняя старонка</string>\n    <string name=\"next_page\">Наступная старонка</string>\n    <string name=\"reader_actions\">Дзеянні ў рэжыме чытання</string>\n    <string name=\"switch_pages_volume_buttons\">Уключыць кнопкі гучнасці</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Выкарыстоўвайце кнопкі гучнасці для гартання старонак</string>\n    <string name=\"tap_action\">Дзеянне пры націску</string>\n    <string name=\"long_tap_action\">Дзеянне пры доўгім націску</string>\n    <string name=\"none\">Нічога</string>\n    <string name=\"mark_as_completed\">Адзначыць як выкананае</string>\n    <string name=\"mark_as_completed_prompt\">Пазначыць выбраную мангу як цалкам прачытаную?\n\\n\n\\nУвага: бягучы ход чытання будзе страчаны.</string>\n    <string name=\"reader_actions_summary\">Налада дзеянняў для сэнсарных абласцей экрана</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"email_password_enter_hint\">Каб працягнуць, увядзіце адрас электроннай пошты і пароль</string>\n    <string name=\"config_reset_confirm\">Скінуць налады да значэнняў па змаўчанні? Гэта дзеянне нельга адмяніць.</string>\n    <string name=\"use_two_pages_landscape\">Выкарыстоўвайце двухстаронкавы макет у альбомнай арыентацыі (бэта)</string>\n    <string name=\"default_webtoon_zoom_out\">Аддаленне ў рэжыме манхвы</string>\n    <string name=\"fullscreen_mode\">Поўнаэкранны рэжым</string>\n    <string name=\"reader_fullscreen_summary\">Схаваць інтэрфейс сістэмы</string>\n    <string name=\"suggestions_unavailable_text\">Функцыя прапаноў адключана</string>\n    <string name=\"check_for_new_chapters_disabled\">Праверка новых раздзелаў адключана</string>\n    <string name=\"reading_time_estimation\">Паказаць прыблізны час чытання</string>\n    <string name=\"reading_time_estimation_summary\">Значэнне ацэнкі часу можа быць недакладным</string>\n    <string name=\"show_labels_in_navbar\">Паказаць меткі на панэлі навігацыі</string>\n    <string name=\"ask_for_dest_dir_every_time\">Кожны раз запытваць каталог прызначэння</string>\n    <string name=\"default_page_save_dir\">Каталог захавання старонкі па змаўчанні</string>\n    <string name=\"remove_from_history\">Выдаліць з гісторыі</string>\n    <string name=\"pages_saving\">Захаванне старонак</string>\n    <string name=\"location\">Размяшчэнне</string>\n    <string name=\"reading_stats\">Статыстыка чытання</string>\n    <string name=\"other_manga\">Іншая манга</string>\n    <string name=\"less_than_minute\">Менш хвіліны</string>\n    <string name=\"statistics\">Статыстыка</string>\n    <string name=\"clear_stats\">Ачысціць статыстыку</string>\n    <string name=\"week\">Тыдзень</string>\n    <string name=\"stats_cleared\">Статыстыка ачышчана</string>\n    <string name=\"month\">Месяц</string>\n    <string name=\"all_time\">Увесь час</string>\n    <string name=\"day\">Дзень</string>\n    <string name=\"empty_stats_text\">Статыстыка за абраны перыяд адсутнічае</string>\n    <string name=\"pages_read_s\">Прачытана старонак: %s</string>\n    <string name=\"automatic\">Аўтаматычны</string>\n    <string name=\"three_months\">Тры месяцы</string>\n    <string name=\"clear_stats_confirm\">Вы сапраўды хочаце ачысціць усю статыстыку чытання? Гэта дзеянне нельга адмяніць.</string>\n    <string name=\"preferred_download_format\">Пераважны фармат для загрузак</string>\n    <string name=\"single_cbz_file\">Адзін файл CBZ</string>\n    <string name=\"multiple_cbz_files\">Некалькі файлаў CBZ</string>\n    <string name=\"alternatives\">Альтэрнатывы</string>\n    <string name=\"migrate_confirmation\">Манга «%1$s» з «%2$s» будзе заменена на «%3$s» з «%4$s» у вашай гісторыі і ў абраных (калі ёсць)</string>\n    <string name=\"migration_completed\">Перанос завершаны</string>\n    <string name=\"manga_migration\">Перанос мангі</string>\n    <string name=\"migrate\">Перанесці</string>\n    <string name=\"delete_read_chapters_summary\">Выдаліце раздзелы, якія вы ўжо прачыталі, з лакальнага сховішча, каб вызваліць месца</string>\n    <string name=\"delete_read_chapters_prompt\">Гэта прывядзе да незваротнага выдалення ўсіх раздзелаў, пазначаных як прачытаныя, з лакальнага сховішча. Вы можаце загрузіць іх пазней, але імпартаваныя раздзелы могуць быць страчаны назаўжды</string>\n    <string name=\"chapters_grid_view\">Выгляд сеткі</string>\n    <string name=\"delete_read_chapters\">Выдаліць прачытаныя раздзелы</string>\n    <string name=\"no_chapters_deleted\">Ні адзін раздзел не быў выдалены</string>\n    <string name=\"chapters_deleted_pattern\">Выдалены %1$s, ачышчаны %2$s</string>\n    <string name=\"runs_on_app_start\">Запускаецца пры запуску праграмы</string>\n    <string name=\"long_ago_read\">Даўно прачытаная</string>\n    <string name=\"split_by_translations\">Падзяліць па перакладах</string>\n    <string name=\"split_by_translations_summary\">Паказваць раздзелы з рознымі перакладамі асобна, а не ў адным спісе</string>\n    <string name=\"order_oldest\">Самы стары</string>\n    <string name=\"unread\">Непрачытаная</string>\n    <string name=\"delete_read_chapters_auto\">Аўтаматычна выдаляць прачытаныя раздзелы</string>\n    <string name=\"enable_source\">Уключыць крыніцу</string>\n    <string name=\"unsupported_source\">Гэтая крыніца мангі не падтрымліваецца</string>\n    <string name=\"show_pages_thumbs\">Паказаць мініяцюры старонак</string>\n    <string name=\"show_pages_thumbs_summary\">Уключыце ўкладку «Старонкі» на экране звестак</string>\n    <string name=\"error_no_data_received\">Ніякія дадзеныя не былі атрыманы з сервера</string>\n    <string name=\"unsupported_backup_message\">Абярыце правільны файл рэзервовай копіі Kotatsu</string>\n    <string name=\"last_used\">Апошні раз выкарыстоўваўся</string>\n    <string name=\"hours_minutes_short\">%1$d гз. %2$d хв.</string>\n    <string name=\"minutes_short\">%d хв.</string>\n    <string name=\"hours_short\">%d гз.</string>\n    <string name=\"fix\">Выправіць</string>\n    <string name=\"missing_storage_permission\">Няма дазволу на доступ да мангі на знешнім сховішчы</string>\n    <string name=\"show_updated\">Паказаць абноўлена</string>\n    <string name=\"webtoon_gaps\">Прабелы ў рэжыме манхвы</string>\n    <string name=\"webtoon_gaps_summary\">Паказваць вертыкальныя прамежкі паміж старонкамі ў рэжыме манхвы</string>\n    <string name=\"search_suggestions\">Пошукавыя прапановы</string>\n    <string name=\"recent_queries\">Нядаўнія запыты</string>\n    <string name=\"suggested_queries\">Прапанаваныя запыты</string>\n    <string name=\"pin_navigation_ui_summary\">Не хаваць навігацыйную панэль і радок пошуку пры пракрутцы</string>\n    <string name=\"authors\">Аўтары</string>\n    <string name=\"less_frequently\">Радзей</string>\n    <string name=\"more_frequently\">Часцей</string>\n    <string name=\"frequency_of_check\">Частата праверкі</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Замацаваць інтэрфейс навігацыі</string>\n    <string name=\"blocked_by_server_message\">Вы заблакаваныя серверам. Паспрабуйце выкарыстоўваць іншае сеткавае падлучэнне (VPN, проксі і т. д.)</string>\n    <string name=\"ignore_ssl_errors_summary\">Вы можаце адключыць праверку SSL-сертыфіката, калі пры доступе да сеткавых рэсурсаў узнікаюць праблемы, звязаныя з SSL. Гэта можа паўплываць на вашую бяспеку. Пасля змены гэтага параметра запатрабуецца перазагрузка праграмы.</string>\n    <string name=\"disable_nsfw_notifications_summary\">Не паказваць апавяшчэння аб абнаўленнях мангі NSFW</string>\n    <string name=\"tracker_debug_info\">Часопіс праверкі новых раздзелаў</string>\n    <string name=\"tracker_debug_info_summary\">Адладкавая інфармацыя аб фонавай праверцы наяўнасці новых раздзелаў</string>\n    <string name=\"disable_connectivity_check\">Адключыць праверку падключэння</string>\n    <string name=\"disable_connectivity_check_summary\">Прапусціць праверкі падключэння ў выпадку праблем з падключэннем (напрыклад, пераход у аўтаномны рэжым, нават калі сетка падключана)</string>\n    <string name=\"disable_nsfw_notifications\">Адключыць апавяшчэння NSFW</string>\n    <string name=\"disable\">Адкл.</string>\n    <string name=\"sources_disabled\">Крыніцы адключаны</string>\n    <string name=\"_new\">Новае</string>\n    <string name=\"all_languages\">Усе мовы</string>\n    <string name=\"screenshots_block_incognito\">Блакіраваць у рэжыме інкогніта</string>\n    <string name=\"image_server\">Сервер відарысаў</string>\n    <string name=\"pin\">Замацаваць</string>\n    <string name=\"unpin\">Адмацаваць</string>\n    <string name=\"source_unpinned\">Крыніца адмацавана</string>\n    <string name=\"source_pinned\">Крыніца замацавана</string>\n    <string name=\"sources_unpinned\">Крыніцы адмацаваны</string>\n    <string name=\"recent_sources\">Нядаўнія крыніцы</string>\n    <string name=\"sources_pinned\">Крыніцы замацаваны</string>\n    <string name=\"crop_pages\">Абрэзаць старонкі</string>\n    <string name=\"percent_read\">Працэнт прачытанага</string>\n    <string name=\"percent_left\">Астатні працэнт</string>\n    <string name=\"chapters_read\">Прачытаныя раздзелы</string>\n    <string name=\"chapters_left\">Астатнія раздзелы</string>\n    <string name=\"external_source\">Знешні/плагін</string>\n    <string name=\"plugin_incompatible\">Несумяшчальны плягін або ўнутраная памылка. Пераканайцеся, што вы карыстаецеся апошнюю версію плагіна і Kotatsu</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Няма мангі, якая адпавядае абраным вамі фільтрам</string>\n    <string name=\"connection_ok\">Злучэнне ў парадку</string>\n    <string name=\"invalid_proxy_configuration\">Няправільная канфігурацыя проксі</string>\n    <string name=\"invalid_server_address_message\">Няверны адрас сервера</string>\n    <string name=\"show_quick_filters_summary\">Дае магчымасць фільтраваць спісы мангі па пэўных параметрах</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"seconds_short\">%d с</string>\n    <string name=\"minutes_seconds_short\">%1$d хв %2$d с</string>\n    <string name=\"show_quick_filters\">Паказаць хуткія фільтры</string>\n    <string name=\"not_in_favorites\">Няма ў абраных</string>\n    <string name=\"updated_long_ago\">Абноўлена даўно</string>\n    <string name=\"unpopular\">Непапулярны</string>\n    <string name=\"low_rating\">Нізкі рэйтынг</string>\n    <string name=\"sort_order_asc\">Па ўзрастанні</string>\n    <string name=\"sort_order_desc\">Па змяншэнні</string>\n    <string name=\"by_date\">Дата</string>\n    <string name=\"popularity\">Папулярнасць</string>\n    <string name=\"retry\">Паўтарыць спробу</string>\n    <string name=\"too_many_requests_message_retry\">Занадта шмат запытаў. Паспрабуйце яшчэ раз пасля %s</string>\n    <string name=\"skip_all\">Прапусціць усе</string>\n    <string name=\"scrobbler_auth_required\">Увайдзіце ў %s, каб працягваць</string>\n    <string name=\"unstable_feature\">Нестабільная функцыя</string>\n    <string name=\"unstable_feature_summary\">Гэта функцыя эксперыментальная. Пераканайцеся, што ў вас ёсць рэзервовая копія, каб пазбегнуць страты даных</string>\n    <string name=\"scrobbler_auth_intro\">Увайдзіце, каб настроіць інтэграцыю з %s. Гэта дазволіць вам адсочваць прагрэс і статус чытання мангі</string>\n    <string name=\"stuck\">Завісла</string>\n    <string name=\"content_type_novel\">Навэла</string>\n    <string name=\"content_type_manhua\">Маньхуа</string>\n    <string name=\"content_type_manhwa\">Манхва</string>\n    <string name=\"recently_added\">Нядаўна дададзена</string>\n    <string name=\"added_long_ago\">Дадаў даўно</string>\n    <string name=\"popular_today\">Папулярна сёння</string>\n    <string name=\"popular_in_week\">Папулярна на гэтым тыдні</string>\n    <string name=\"popular_in_month\">Папулярна ў гэтым месяцы</string>\n    <string name=\"popular_in_year\">Папулярна ў гэтым годзе</string>\n    <string name=\"original_language\">Зыходная мова</string>\n    <string name=\"year\">Год</string>\n    <string name=\"popular_in_hour\">Папулярна ў гэтую гадзіну</string>\n    <string name=\"demographic_shounen\">Сёнэн</string>\n    <string name=\"demographics\">Дэмаграфія</string>\n    <string name=\"demographic_shoujo\">Сёдзё</string>\n    <string name=\"demographic_seinen\">Сэйнэн</string>\n    <string name=\"demographic_josei\">Дзёсэй</string>\n    <string name=\"years\">Гады</string>\n    <string name=\"any\">Любой</string>\n    <string name=\"content_type_one_shot\">Ваншот</string>\n    <string name=\"filter_search_warning\">Гэтая крыніца не падтрымлівае пошук з фільтрамі. Вашы фільтры былі ачышчаны</string>\n    <string name=\"demographic_kodomo\">Кодомо</string>\n    <string name=\"downloads_background\">Фонавыя загрузкі</string>\n    <string name=\"manga_with_downloaded_chapters\">Манга з загружанымі раздзеламі</string>\n    <string name=\"download_new_chapters\">Спампаваць новыя раздзелы</string>\n    <string name=\"manga_replaced\">Манга «%1$s» (%2$s) заменена на «%3$s» (%4$s)</string>\n    <string name=\"fixing_manga\">Выпраўленне мангі</string>\n    <string name=\"fixed\">Выпраўлена паспяхова</string>\n    <string name=\"no_fix_required\">Выпраўленне для «%s» не патрабуецца</string>\n    <string name=\"no_alternatives_found\">Альтэрнатывы для «%s» не знойдзены</string>\n    <string name=\"manga_fix_prompt\">Гэтая функцыя знойдзе альтэрнатыўныя крыніцы для абранай мангі. Задача зойме некаторы час і будзе выконвацца ў фонавым рэжыме</string>\n    <string name=\"content_type_doujinshi\">Дадзінсі</string>\n    <string name=\"content_type_image_set\">Набор відарысаў</string>\n    <string name=\"content_type_artist_cg\">Мастак CG</string>\n    <string name=\"content_type_game_cg\">Гульнявая CG</string>\n    <string name=\"debug\">Адладка</string>\n    <string name=\"source_code\">Зыходны код</string>\n    <string name=\"user_manual\">Кіраўніцтва карыстальніка</string>\n    <string name=\"telegram_group\">Група ў Telegram</string>\n    <string name=\"error_image_format\">Фармат відарыса не падтрымліваецца: %s</string>\n    <string name=\"start_download\">Пачаць загрузку</string>\n    <string name=\"save_manga_confirm\">Захаваць выбраную мангу? Гэта можа заняць трафік і месца на дыску</string>\n    <string name=\"save_manga\">Захаваць мангу</string>\n    <string name=\"genre\">Жанр</string>\n    <string name=\"landscape\">Ландшафтная</string>\n    <string name=\"plugin_incompatible_with_cause\">Памылка плагіна: %s\\nПераканайцеся, што вы карыстаецеся апошнюю версію плагіна і Kotatsu</string>\n    <string name=\"download_added\">Загрузка дададзена</string>\n    <string name=\"more_options\">Больш опцый</string>\n    <string name=\"destination_directory\">Каталог прызначэння</string>\n    <string name=\"chapter_selection_hint\">Вы можаце абраць раздзелы для загрузкі, утрымліваючы па элеменце ў спісе раздзелаў.</string>\n    <string name=\"chapters_all\">Усё</string>\n    <string name=\"download_over_cellular\">Загрузка праз мабільную сетку</string>\n    <string name=\"download_cellular_confirm\">Дазволіць загрузку праз мабільную сетку?</string>\n    <string name=\"dont_allow\">Не дазваляць</string>\n    <string name=\"allow_always\">Дазволіць заўсёды</string>\n    <string name=\"allow_once\">Дазволіць адзін раз</string>\n    <string name=\"ask_every_time\">Пытацца кожны раз</string>\n    <string name=\"screen_orientation\">Арыентацыя экрана</string>\n    <string name=\"portrait\">Партрэтная</string>\n    <string name=\"pages_saved\">Старонкі захаваны</string>\n    <string name=\"error_not_image\">Няправільны фармат: чакаўся відарыс, але атрыманы %s</string>\n    <string name=\"enable_all_sources_summary\">Усе даступныя крыніцы мангі будуць уключаны назаўжды</string>\n    <string name=\"error_details\">Падрабязнасці памылкі</string>\n    <string name=\"handle_links_summary\">Апрацоўка спасылак на мангу з вонкавых праграм (напрыклад, вэб-браўзэра). Вам таксама можа спатрэбіцца ўключыць яго ўручную ў сістэмных наладах праграмы</string>\n    <string name=\"backup_restored_background\">Рэзервовая копія будзе адноўлена ў фонавым рэжыме</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"access_denied_403\">Доступ адхілены (403)</string>\n    <string name=\"max_backups_count\">Максімальная колькасць рэзервовых копій</string>\n    <string name=\"show_slider\">Паказаць слайдэр</string>\n    <string name=\"incognito\">Інкогніта</string>\n    <string name=\"error_connection_reset\">Скід падключэння аддаленым хостам</string>\n    <string name=\"clear_database\">Ачысціць базу дадзеных</string>\n    <string name=\"clear_database_summary\">Выдаліць інфармацыю аб манге, якая не выкарыстоўваецца</string>\n    <string name=\"enable_all_sources\">Уключыць усе крыніцы мангі</string>\n    <string name=\"all_sources_enabled\">Усе крыніцы ўключаны</string>\n    <string name=\"reader_info_bar_transparent\">Празрыстая інфармацыйная панэль чытання</string>\n    <string name=\"restoring_backup\">Аднаўленне рэзервовай копіі</string>\n    <string name=\"backup_tg_check\">Праверце, ці працуе API</string>\n    <string name=\"telegram_chat_id\">ID чата Telegram</string>\n    <string name=\"backup_tg_echo\">Тэставое паведамленне</string>\n    <string name=\"open_telegram_bot\">Адкрыйце Telegram-бота</string>\n    <string name=\"backup_tg_id_not_set\">ID чата не ўсталяваны</string>\n    <string name=\"search_disabled_sources\">Пошук па адключаных крыніцах</string>\n    <string name=\"captcha_required_message\">Гэтая крыніца патрабуе рашэння капчы, каб працягнуць</string>\n    <string name=\"handle_links\">Апрацоўка спасылак</string>\n    <string name=\"email\">Электронная пошта</string>\n    <string name=\"chapter_volume_number\">Том %1$s Раздзел %2$s</string>\n    <string name=\"unnamed_chapter\">Безназоўны раздзел</string>\n    <string name=\"chapter_number\">Раздзел %s</string>\n    <string name=\"simple\">Просты</string>\n    <string name=\"send_backups_telegram\">Адпраўляць рэзервовыя копіі ў Telegram</string>\n    <string name=\"telegram_chat_id_summary\">Увядзіце ID чата, куды трэба адпраўляць рэзервовыя копіі</string>\n    <string name=\"open_telegram_bot_summary\">Націсніце, каб адкрыць чат з Kotatsu Backup Bot</string>\n    <string name=\"test_connection\">Тэставое злучэнне</string>\n    <string name=\"delete_old_backups_summary\">Аўтаматычна выдаляць старыя рэзервовыя копіі, каб вызваліць месца для дадзеных</string>\n    <string name=\"delete_old_backups\">Выдаліць старыя рэзервовыя копіі</string>\n    <string name=\"translation\">Пераклад</string>\n    <string name=\"reader_controls_in_bottom_bar\">Элементы кіравання чытаннем на ніжняй панэлі</string>\n    <string name=\"chapters_and_pages\">Раздзелы і старонкі</string>\n    <string name=\"screen_rotation_locked\">Паварот экрана быў заблакаваны</string>\n    <string name=\"screen_rotation_unlocked\">Паварот экрана быў разблакаваны</string>\n    <string name=\"pages_slider\">Паўзунок пераключэння старонак</string>\n    <string name=\"disable_captcha_notifications\">Адключыць апавяшчэння аб CAPTCHA</string>\n    <string name=\"global_search\">Глабальны пошук</string>\n    <string name=\"author\">Аўтар</string>\n    <string name=\"rating\">Рэйтынг</string>\n    <string name=\"source\">Крыніца</string>\n    <string name=\"search_everywhere\">Пошук паўсюль</string>\n    <string name=\"badges_in_lists\">Значкі ў спісах</string>\n    <string name=\"link_to_manga_on_s\">Спасылка на мангу на %s</string>\n    <string name=\"link_to_manga_in_app\">Спасылка на мангу ў Kotatsu</string>\n    <string name=\"clear_browser_data\">Ачысціць дадзеныя браўзэра</string>\n    <string name=\"include_disabled_sources\">Уключыць адключаныя крыніцы</string>\n    <string name=\"suggestions_disabled_sources_summary\">Адлюстроўваць рэкамендацыі з усіх крыніц мангі, у тым ліку адключаныя</string>\n    <string name=\"tags_warnings\">Вылучаць небяспечныя жанры</string>\n    <string name=\"tags_warnings_summary\">Вылучаць жанры, якія могуць быць непрымальныя для большасці карыстальнікаў</string>\n    <string name=\"pick_manga_page\">Выбраць старонку мангі</string>\n    <string name=\"pick_custom_file\">Выбраць карыстацкі файл</string>\n    <string name=\"change_cover\">Змяніць вокладку</string>\n    <string name=\"dont_ask_again\">Больш не пытацца</string>\n    <string name=\"incognito_mode_hint_nsfw\">Гэтая манга можа змяшчаць кантэнт для дарослых. Хочаце выкарыстоўваць рэжым інкогніта?</string>\n    <string name=\"incognito_for_nsfw\">Рэжым інкогніта для NSFW-мангі</string>\n    <string name=\"no_write_permission_to_file\">Няма правоў на запіс у файл</string>\n    <string name=\"use_default_cover\">Выкарыстоўваць вокладку па змаўчанні</string>\n    <string name=\"error_disclaimer_report\">Вы можаце адправіць справаздачу аб памылцы распрацоўшчыкам. Гэта дапаможа нам выправіць праблему.</string>\n    <string name=\"error_disclaimer_app_outdated\">Падобна, што Ваша версія Kotatsu састарэлая. Калі ласка, усталюйце апошнюю версію, каб атрымаць усе даступныя выпраўленні.</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Дарослая манга не будзе адлюстроўвацца ў рэкамендацыях. Гэтая опцыя можа не працаваць з некаторымі крыніцамі</string>\n    <string name=\"disable_captcha_notifications_summary\">Вы не будзеце атрымліваць апавяшчэнняў аб рашэнні CAPTCHA для гэтай крыніцы, але гэта можа прывесці да парушэння фонавых аперацый (праверкі новых раздзелаў, атрымання рэкамендацый і г.д.)</string>\n    <string name=\"error_disclaimer_manga\">Паспрабуйце адкрыць мангу ў браўзэры, каб пераканацца, што яна даступная ў крыніцы.</string>\n    <string name=\"manga_override_hint\">Гэтыя змены будуць уплываць на тое, як манга адлюстроўваецца ў праграме</string>\n    <string name=\"page_switch_timer\">Старонка будзе мяняцца кожныя ~%d секунд</string>\n    <string name=\"error_non_file_uri\">Абраны шлях не можа быць выкарыстаны, паколькі ён не абазначае файл або каталог</string>\n    <string name=\"collapse\">Згортванне</string>\n    <string name=\"expand\">Пашыраць</string>\n    <string name=\"clear_browser_data_summary\">Выдаліць дадзеныя убудаванага браўзэра, такія як кэш і печыва. Увага: аўтарызацыя ў крыніцах мангі можа быць страчана</string>\n    <string name=\"theme_name_expressive\">Выразны (Тэст)</string>\n    <string name=\"additional_action_required\">Патрабуюцца дадатковыя дзеянні</string>\n    <string name=\"hide_from_main_screen\">Схаваць з галоўнага экрана</string>\n    <string name=\"adblock\">Блакаваць рэкламу ў браўзэры</string>\n    <string name=\"collapse_long_description\">Згарнуць доўгае апісанне</string>\n    <string name=\"adblock_summary\">Блакаванне рэкламы ва ўбудаваным браўзэры (бэта)</string>\n    <string name=\"changelog\">Часопіс змен</string>\n    <string name=\"changelog_summary\">Гісторыя змен для нядаўна выпушчаных версій</string>\n    <string name=\"reader_navigation_inverted\">Інвертаваць элементы кіравання навігацыяй</string>\n    <string name=\"reader_navigation_inverted_summary\">Памяняць месцамі кірунак кнопкі рэгулявання гучнасці і апаратнай клавішы навігацыі (налева/уверх/уніз/направа)</string>\n    <string name=\"creating_backup\">Стварэнне рэзервовай копіі</string>\n    <string name=\"share_backup\">Падзяліцца рэзервовай копіяй</string>\n    <string name=\"reader_multitask\">Адчыняць чыталку як асобную задачу</string>\n    <string name=\"reader_multitask_summary\">Дазваляе трымаць адкрытымі некалькі чыталак з рознай мангой адначасова</string>\n    <string name=\"theme_name_itsuka\">Іцука</string>\n    <string name=\"theme_name_totoro\">Тоторо</string>\n    <string name=\"book_effect\">Жаўтлявы фон (фільтр сіняга)</string>\n    <string name=\"local_storage_cleanup\">Ачыстка лакальнага сховішча</string>\n    <string name=\"packup_creation_failed\">Рэзервовае капіраванне не атрымалася</string>\n    <string name=\"main_screen\">Галоўны экран</string>\n    <string name=\"main_screen_fab\">Паказаць плаваючую кнопку «Працягнуць»</string>\n    <string name=\"main_screen_fab_summary\">Дазваляе працягнуць чытанне адным пстрычкай мышы. Гэтая кнопка не адлюстроўваецца ў рэжыме інкогніта або калі гісторыя пустая</string>\n    <string name=\"error_corrupted_zip\">Пашкоджаны ZIP-архіў (%s)</string>\n    <string name=\"discord_rpc\">Discord Rich Presence</string>\n    <string name=\"discord_token\">Токен Discord</string>\n    <string name=\"discord_token_summary\">Увядзіце свой токен Discord, каб уключыць Rich Presence</string>\n    <string name=\"discord_token_description\">Увядзіце свой токен Discord або націсніце %s, каб атрымаць яго з дапамогай браўзера</string>\n    <string name=\"discord_token_hint\">Устаўце сюды свой токен Discord</string>\n    <string name=\"discord_rpc_summary\">Пакажыце свой статус чытання ў Discord</string>\n    <string name=\"obtain\">Атрымаць</string>\n    <string name=\"discord_rpc_description\">Чытанне мангі на Kotatsu - праграма для чытання мангі</string>\n    <string name=\"reading_s\">Чытанне %s</string>\n    <string name=\"read_on_s\">Чытаць далей %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Не выкарыстоўваць RPC для дарослага кантэнту</string>\n    <string name=\"invalid_token\">Няправільны токен: %s</string>\n    <string name=\"show_floating_control_button\">Паказаць плаваючую кнопку кіравання</string>\n    <string name=\"unavailable\">Недаступна</string>\n    <string name=\"manga_restricted_description\">Гэтая манга недаступная для чытання на гэтай крыніцы. Паспрабуйце знайсці яе на іншых крыніцах або адкрыйце ў браўзеры для атрымання дадатковай інфармацыі</string>\n    <string name=\"no_chapters_in_manga\">Гэтая манга не ўтрымлівае раздзелаў</string>\n    <string name=\"chapters_load_failed\">Не атрымалася загрузіць спіс раздзелаў</string>\n    <string name=\"telegram_integration\">Інтэграцыя з Telegram</string>\n    <string name=\"pull_top_no_prev\">Няма папярэдняй главы</string>\n    <string name=\"pull_bottom_no_next\">Няма наступнай главы</string>\n    <string name=\"enable_pull_gesture_title\">Уключыць жэст перацягвання</string>\n    <string name=\"enable_pull_gesture_summary\">Выкарыстоўвайце жэст пацягвання, каб пераключацца паміж главамі ў манхве</string>\n    <string name=\"test_parser\">Праверыць крыніцу мангі</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-bn/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d আইটেম</item>\n        <item quantity=\"other\">%1$d টি আইটেম</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d টি নতুন পর্ব</item>\n        <item quantity=\"other\">%1$d টি নতুন পর্ব</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">মাত্র %1$d দিন আগে</item>\n        <item quantity=\"other\">%1$d দিন আগে</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">প্রায় %1$d মাস আগে</item>\n        <item quantity=\"other\">%1$d মাস আগে</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d পর্ব</item>\n        <item quantity=\"other\">%1$d টি পর্ব</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">মাত্র %1$d ঘন্টা আগে</item>\n        <item quantity=\"other\">%1$d ঘন্টা আগে</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">মাত্র %1$d মিনিট আগে</item>\n        <item quantity=\"other\">%1$d মিনিট আগে</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d মিনিট</item>\n        <item quantity=\"other\">%1$d টি মিনিট</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d ঘন্টা</item>\n        <item quantity=\"other\">%1$d টি ঘন্টা</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-bn/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"favourites\">পছন্দের</string>\n    <string name=\"history\">সম্প্রতি দেখা</string>\n    <string name=\"local_storage\">লোকাল স্টোরেজ</string>\n    <string name=\"_continue\">চালিয়ে যান</string>\n    <string name=\"clear_thumbs_cache\">থাম্বনেইল ক্যাচ সাফ করুন</string>\n    <string name=\"clear_search_history\">সার্চ ইতিহাস সাফ করুন</string>\n    <string name=\"internal_storage\">ইন্টার্নাল স্টোরেজ</string>\n    <string name=\"external_storage\">এক্সটার্নাল স্টোরেজ</string>\n    <string name=\"domain\">ডোমেইন</string>\n    <string name=\"app_update_available\">অ্যাপের নতুন ভার্সন পাওয়া গেছে</string>\n    <string name=\"open_in_browser\">ব্রাউজারে খুলুন</string>\n    <string name=\"error_occurred\">কিছু একটা সমস্যা হয়েছে</string>\n    <string name=\"details\">বিস্তারিত</string>\n    <string name=\"chapters\">চ্যাপ্টার</string>\n    <string name=\"list\">তালিকা</string>\n    <string name=\"detailed_list\">বিস্তারিত তালিকা</string>\n    <string name=\"grid\">গ্রিড</string>\n    <string name=\"settings\">সেটিংস</string>\n    <string name=\"loading_\">লোড হচ্ছে…</string>\n    <string name=\"close\">বন্ধ</string>\n    <string name=\"try_again\">আবার চেষ্টা করুন</string>\n    <string name=\"clear_history\">ইতিহাস মুছুন</string>\n    <string name=\"computing_\">প্রস্তুত হচ্ছে…</string>\n    <string name=\"chapter_d_of_d\">%2$d এর্ %1$d তম অধ্যায়</string>\n    <string name=\"nothing_found\">কিছু পাওয়া যায়নি</string>\n    <string name=\"history_is_empty\">কোনো ইতিহাস নেই</string>\n    <string name=\"read\">পড়ুন</string>\n    <string name=\"add_to_favourites\">পছন্দ করুন</string>\n    <string name=\"text_file_not_supported\">একটি ZIP অথবা CBZ ফাইল নিন</string>\n    <string name=\"no_description\">কোনো বর্ণনা নেই</string>\n    <string name=\"clear_pages_cache\">পেজের ক্যাচ পরিষ্কার করুন</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"share_image\">ছবি শেয়ার করুন</string>\n    <string name=\"_import\">আমদানি</string>\n    <string name=\"delete\">মুছে ফেলুন</string>\n    <string name=\"operation_not_supported\">এই কাজটি করা সম্ভব নয়</string>\n    <string name=\"switch_pages\">পেজ পাল্টান</string>\n    <string name=\"search_history_cleared\">সাফ করা হয়েছে</string>\n    <string name=\"network_error\">নেটওয়ার্কে ত্রুটি</string>\n    <string name=\"remote_sources\">মানগা সোর্স সমূহ</string>\n    <string name=\"you_have_not_favourites_yet\">এখনো কিছু পছন্দ হয়নি</string>\n    <string name=\"add_new_category\">নতুন বিভাগ</string>\n    <string name=\"by_rating\">রেটিং</string>\n    <string name=\"add\">যোগ করুন</string>\n    <string name=\"save\">সেভ করুন</string>\n    <string name=\"sort_order\">বাছাইয়ের ধারা</string>\n    <string name=\"share\">শেয়ার করুন</string>\n    <string name=\"create_shortcut\">শর্টকাট তৈরি করুন…</string>\n    <string name=\"share_s\">%s শেয়ার করুন</string>\n    <string name=\"search\">খুঁজুন</string>\n    <string name=\"search_manga\">মানগা খুঁজুন</string>\n    <string name=\"manga_downloading_\">ডাউনলোড হচ্ছে…</string>\n    <string name=\"processing_\">প্রস্তুত হচ্ছে…</string>\n    <string name=\"downloads\">ডাউনলোড হ‌ওয়া গুলো</string>\n    <string name=\"by_name\">নাম</string>\n    <string name=\"popular\">জনপ্রিয়</string>\n    <string name=\"updated\">আপডেট হয়েছে</string>\n    <string name=\"newest\">সবচেয়ে নতুন</string>\n    <string name=\"filter\">বাছাই</string>\n    <string name=\"theme\">থিম</string>\n    <string name=\"light\">আলো</string>\n    <string name=\"dark\">আঁধার</string>\n    <string name=\"follow_system\">সিস্টেম অনুযায়ী</string>\n    <string name=\"pages\">পৃষ্ঠাগুলি</string>\n    <string name=\"webtoon\">ওয়েবটুন</string>\n    <string name=\"read_mode\">পড়ার মোড</string>\n    <string name=\"grid_size\">গ্রিডের আকার</string>\n    <string name=\"clear\">পরিষ্কার</string>\n    <string name=\"remove\">সরিয়ে ফেলুন</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" লোকাল স্টোরেজ থেকে সরানো হয়েছে</string>\n    <string name=\"save_page\">পেজ সেভ করুন</string>\n    <string name=\"page_saved\">Page saved</string>\n    <string name=\"standard\">স্ট্যান্ডার্ড</string>\n    <string name=\"search_on_s\">%s এ খুঁজুন</string>\n    <string name=\"delete_manga\">মানগা ডিলিট করুন</string>\n    <string name=\"text_delete_local_manga\">\\\"%s\\\" পারমানেন্ট ভাবে ডিভাইস থেকে ডিলিট করতে চান\\?</string>\n    <string name=\"reader_settings\">রিডারের সেটিংস</string>\n    <string name=\"error\">সমস্যা</string>\n    <string name=\"updates\">আপডেট</string>\n    <string name=\"manga_shelf\">তাক</string>\n    <string name=\"text_history_holder_secondary\">\\\"এক্সপ্লোর\\\" বিভাগে কী পড়তে হবে তা খুঁজুন</string>\n    <string name=\"cancel_all\">সব বাতিল করুন</string>\n    <string name=\"sync_host_description\">আপনি একটি স্ব-হোস্টেড সিঙ্ক্রোনাইজেশন সার্ভার বা একটি ডিফল্ট ব্যবহার করতে পারেন৷ আপনি কি করছেন তা নিশ্চিত না হলে এটি পরিবর্তন করবেন না।</string>\n    <string name=\"all_favourites\">সব প্রিয়</string>\n    <string name=\"light_indicator\">LED নির্দেশক</string>\n    <string name=\"favourites_categories\">প্রিয় বিভাগ</string>\n    <string name=\"rotate_screen\">পর্দা ঘোরান</string>\n    <string name=\"vibration\">কম্পন</string>\n    <string name=\"no_update_available\">কোন আপডেট উপলব্ধ নেই</string>\n    <string name=\"remove_category\">অপসারণ</string>\n    <string name=\"downloads_wifi_only_summary\">মোবাইল নেটওয়ার্কে স্যুইচ করার সময় ডাউনলোড করা বন্ধ করুন</string>\n    <string name=\"read_later\">পরে পড়ুন</string>\n    <string name=\"cannot_find_available_storage\">সঞ্চয়স্থান উপলব্ধ নয়</string>\n    <string name=\"ignore_ssl_errors\">SSL ত্রুটি উপেক্ষা করুন</string>\n    <string name=\"server_address\">সার্ভার ঠিকানা</string>\n    <string name=\"text_feed_holder\">New chapters of what you are reading are shown here</string>\n    <string name=\"favourites_category_empty\">খালি বিভাগ</string>\n    <string name=\"pause\">বিরতি</string>\n    <string name=\"remove_completed\">সম্পূর্ণভাবে সরান</string>\n    <string name=\"manga_save_location\">Downloads folder</string>\n    <string name=\"suggestions_notifications_summary\">কখনও কখনও প্রস্তাবিত মাঙ্গা সহ বিজ্ঞপ্তিগুলি দেখান৷</string>\n    <string name=\"updates_feed_cleared\">সাফ করা হয়েছে</string>\n    <string name=\"list_mode\">তালিকার ধরন</string>\n    <string name=\"download_complete\">ডাউনলোড করা হয়েছে</string>\n    <string name=\"update\">হালনাগাদ</string>\n    <string name=\"feed_will_update_soon\">ফিড আপডেট শীঘ্রই শুরু হবে</string>\n    <string name=\"resume\">আবার শুরু করুন</string>\n    <string name=\"new_version_s\">নতুন সংস্করণ: %s</string>\n    <string name=\"scale_mode\">স্কেল মোড</string>\n    <string name=\"sync_settings\">সিঙ্ক্রোনাইজেশন সেটিংস</string>\n    <string name=\"text_history_holder_primary\">আপনি যা পড়েছেন তা এখানে প্রদর্শিত হবে</string>\n    <string name=\"create_category\">নতুন বিভাগ</string>\n    <string name=\"notification_sound\">বিজ্ঞপ্তি শব্দ</string>\n    <string name=\"downloads_wifi_only\">শুধুমাত্র Wi-Fi এর মাধ্যমে ডাউনলোড করুন</string>\n    <string name=\"cancel_all_downloads_confirm\">সমস্ত সক্রিয় ডাউনলোড বাতিল করা হবে, আংশিকভাবে ডাউনলোড করা ডেটা হারিয়ে যাবে</string>\n    <string name=\"notifications\">বিজ্ঞপ্তি</string>\n    <string name=\"not_available\">পাওয়া যায় না</string>\n    <string name=\"track_sources\">আপডেটের জন্য দেখুন</string>\n    <string name=\"paused\">থামুন</string>\n    <string name=\"wrong_password\">ভুল পাসওয়ার্ড</string>\n    <string name=\"download\">ডাউনলোড করুন</string>\n    <string name=\"size_s\">আকার: %s</string>\n    <string name=\"about\">সম্পর্কিত</string>\n    <string name=\"zoom_mode_fit_center\">ফিট সেন্টার</string>\n    <string name=\"new_chapters\">নতুন অধ্যায়</string>\n    <string name=\"more\">আরো</string>\n    <string name=\"protect_application\">অ্যাপটি রক্ষা করুন</string>\n    <string name=\"passwords_mismatch\">অমিল পাসওয়ার্ড</string>\n    <string name=\"check_for_updates\">হালনাগাদ এর জন্য অনুসন্ধান করুন</string>\n    <string name=\"protect_application_summary\">কোটাটসু শুরু করার সময় পাসওয়ার্ডের জন্য জিজ্ঞাসা করুন</string>\n    <string name=\"right_to_left\">ডান থেকে বাম</string>\n    <string name=\"mirror_switching\">স্বয়ংক্রিয়ভাবে আয়না চয়ন করুন</string>\n    <string name=\"clear_updates_feed\">সাফ আপডেট ফিড</string>\n    <string name=\"enable\">সক্ষম করুন</string>\n    <string name=\"notifications_settings\">বিজ্ঞপ্তি সেটিংস</string>\n    <string name=\"text_search_holder_secondary\">প্রশ্নটি সংস্কার করার চেষ্টা করুন।</string>\n    <string name=\"recent_manga\">সাম্প্রতিক</string>\n    <string name=\"dont_check\">চেক করবেন না</string>\n    <string name=\"search_results\">অনুসন্ধান ফলাফল</string>\n    <string name=\"app_version\">সংস্করণ %s</string>\n    <string name=\"pages_animation\">পৃষ্ঠা অ্যানিমেশন</string>\n    <string name=\"other_storage\">অন্যান্য স্টোরেজ</string>\n    <string name=\"enter_password\">পাসওয়ার্ড লিখুন</string>\n    <string name=\"repeat_password\">পাসওয়ার্ড পুনরাবৃত্তি করুন</string>\n    <string name=\"mirror_switching_summary\">মিরর উপলব্ধ থাকলে ত্রুটির মাঙ্গা উত্সগুলির জন্য স্বয়ংক্রিয়ভাবে ডোমেনগুলি পরিবর্তন করুন৷</string>\n    <string name=\"no_thanks\">না ধন্যবাদ</string>\n    <string name=\"text_local_holder_secondary\">Save something from an online catalog or import it from a file।</string>\n    <string name=\"text_local_holder_primary\">আগে কিছু সংরক্ষণ করুন</string>\n    <string name=\"suggestion_manga\">পরামর্শ: %s</string>\n    <string name=\"text_empty_holder_primary\">এখানে খালি…</string>\n    <string name=\"done\">সম্পন্ন</string>\n    <string name=\"text_empty_holder_secondary_filtered\">There are no manga matching the filters you selected</string>\n    <string name=\"zoom_mode_fit_height\">Fit to height</string>\n    <string name=\"black_dark_theme\">Black</string>\n    <string name=\"pages_saved\">Pages saved</string>\n    <string name=\"zoom_mode_fit_width\">Fit to width</string>\n    <string name=\"zoom_mode_keep_start\">Keep at start</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d of %2$d on</string>\n    <string name=\"retry\">পুনরায় চেষ্টা করুন</string>\n    <string name=\"color_theme\">কালার স্কিম</string>\n    <string name=\"content_type_novel\">উপন্যাস</string>\n    <string name=\"content_type_manhua\">মানহুয়া</string>\n    <string name=\"prefetch_content\">কন্টেন্ট প্রিলোড হচ্ছে</string>\n    <string name=\"mark_as_current\">বর্তমান হিসেবে চিহ্নিত করুন</string>\n    <string name=\"compact\">কম্প্যাক্ট</string>\n    <string name=\"show_in_grid_view\">গ্রিড ভিউতে দেখান</string>\n    <string name=\"language\">ভাষা</string>\n    <string name=\"share_logs\">লগ শেয়ার করুন</string>\n    <string name=\"enable_logging\">লগিং সক্ষম করুন</string>\n    <string name=\"show_suspicious_content\">সন্দেহজনক কন্টেন্ট দেখান</string>\n    <string name=\"source_disabled\">উৎস অক্ষম করা হয়েছে</string>\n    <string name=\"delete_old_backups\">পুরনো ব্যাকআপ মুছে ফেলুন</string>\n    <string name=\"enable_logging_summary\">ডিবাগের উদ্দেশ্যে কিছু অ্যাকশন রেকর্ড করুন। আপনি কী করছেন তা নিশ্চিত না হলে এটি চালু করবেন না</string>\n    <string name=\"theme_name_dynamic\">ডাইনামিক</string>\n    <string name=\"theme_name_miku\">মিকু</string>\n    <string name=\"data_not_restored\">ডেটা পুনরুদ্ধার করা হয়নি</string>\n    <string name=\"incognito_mode_hint\">আপনার পড়ার অগ্রগতি সেভ হবে না</string>\n    <string name=\"volume_\">আওয়াজ%d</string>\n    <string name=\"volume_unknown\">অজানা ভলিউম</string>\n    <string name=\"suggested_queries\">সাম্প্রতিক প্রশ্ন</string>\n    <string name=\"authors\">লেখক</string>\n    <string name=\"blocked_by_server_message\">আপনি সার্ভার দ্বারা অবরুদ্ধ করা হয়. একটি ভিন্ন নেটওয়ার্ক সংযোগ ব্যবহার করার চেষ্টা করুন (ভিপিএন, প্রক্সি, ইত্যাদি)</string>\n    <string name=\"disable\">নিষ্ক্রিয় করুন</string>\n    <string name=\"sources_disabled\">উৎস নিষ্ক্রিয়</string>\n    <string name=\"disable_connectivity_check\">সংযোগ পরীক্ষা করতে অক্ষম করুন</string>\n    <string name=\"ignore_ssl_errors_summary\">নেটওয়ার্ক সংস্থানগুলি অ্যাক্সেস করার সময় আপনি যদি কোনও SSL- সম্পর্কিত সমস্যার মুখোমুখি হন তবে আপনি SSL শংসাপত্র যাচাইকরণ অক্ষম করতে পারেন৷ এটি আপনার নিরাপত্তা প্রভাবিত করতে পারে। এই সেটিং পরিবর্তন করার পরে অ্যাপ্লিকেশন পুনরায় চালু করা প্রয়োজন।</string>\n    <string name=\"disable_connectivity_check_summary\">আপনার সমস্যা থাকলে কানেক্টিভিটি চেক এড়িয়ে যান (যেমন, নেটওয়ার্ক সংযুক্ত থাকা সত্ত্বেও অফলাইন মোডে যাওয়া)</string>\n    <string name=\"disable_nsfw_notifications\">NSFW বিজ্ঞপ্তি অক্ষম করুন</string>\n    <string name=\"disable_nsfw_notifications_summary\">NSFW মাঙ্গা আপডেট সম্পর্কে বিজ্ঞপ্তি দেখাবেন না</string>\n    <string name=\"tracker_debug_info\">নতুন অধ্যায় লগ জন্য পরীক্ষা করা হচ্ছে</string>\n    <string name=\"tracker_debug_info_summary\">নতুন অধ্যায়গুলির জন্য পটভূমি পরীক্ষা সম্পর্কে তথ্য ডিবাগ করুন</string>\n    <string name=\"_new\">নতুন</string>\n    <string name=\"all_languages\">সব ভাষা</string>\n    <string name=\"screenshots_block_incognito\">ছদ্মবেশী মোড হলে ব্লক করুন</string>\n    <string name=\"image_server\">পছন্দের ইমেজ সার্ভার</string>\n    <string name=\"crop_pages\">পৃষ্ঠাগুলি কাটা</string>\n    <string name=\"pin\">পিন</string>\n    <string name=\"unpin\">আনপিন করুন</string>\n    <string name=\"source_pinned\">উৎস পিন করা হয়েছে</string>\n    <string name=\"source_unpinned\">উৎস আনপিন করা হয়েছে</string>\n    <string name=\"sources_unpinned\">উৎস আনপিন করা হয়েছে</string>\n    <string name=\"sources_pinned\">উৎস পিন করা হয়েছে</string>\n    <string name=\"recent_sources\">সাম্প্রতিক সূত্র</string>\n    <string name=\"percent_read\">শতকরা কতটুকু করেছেন</string>\n    <string name=\"percent_left\">শতকরা কতটুকু পড়তে বাকি আছে</string>\n    <string name=\"chapters_read\">অধ্যায় পড়া</string>\n    <string name=\"chapters_left\">অধ্যায় বাকি আছে</string>\n    <string name=\"external_source\">বাহ্যিক/প্লাগইন</string>\n    <string name=\"plugin_incompatible\">বেমানান প্লাগইন বা অভ্যন্তরীণ ত্রুটি. আপনি প্লাগইন এবং Kotatsu এর সর্বশেষ সংস্করণ ব্যবহার করছেন তা নিশ্চিত করুন৷</string>\n    <string name=\"plugin_incompatible_with_cause\">প্লাগইন ত্রুটি: %s \\n· নিশ্চিত করুন যে আপনি প্লাগইন এবং Kotatsu এর সর্বশেষ সংস্করণ ব্যবহার করছেন</string>\n    <string name=\"connection_ok\">সংযোগ ঠিক আছে</string>\n    <string name=\"invalid_proxy_configuration\">অবৈধ প্রক্সি কনফিগারেশন</string>\n    <string name=\"show_quick_filters\">দ্রুত ফিল্টার দেখান</string>\n    <string name=\"show_quick_filters_summary\">নির্দিষ্ট পরামিতি দ্বারা মাঙ্গা তালিকা ফিল্টার করার ক্ষমতা প্রদান করে</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"skip_all\">সব এড়িয়ে যান</string>\n    <string name=\"stuck\">আটকে গেছে</string>\n    <string name=\"source_broken_warning\">এই মঙ্গা সূত্রটি\\nভাঙ্গা হিসাবে চিহ্নিত। কিছু বৈশিষ্ট্য \\nকাজ নাও হতে পারে</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ca/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d article</item>\n        <item quantity=\"many\">%1$d articles</item>\n        <item quantity=\"other\">%1$d articles</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nou capítol</item>\n        <item quantity=\"many\">%1$d nous capítols</item>\n        <item quantity=\"other\">%1$d nous capítols</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d capítol</item>\n        <item quantity=\"many\">%1$d capítols</item>\n        <item quantity=\"other\">%1$d capítols</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">Fa %1$d minut</item>\n        <item quantity=\"many\">Fa %1$d minuts</item>\n        <item quantity=\"other\">Fa %1$d minuts</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">Fa %1$d hora</item>\n        <item quantity=\"many\">Fa %1$d hores</item>\n        <item quantity=\"other\">Fa %1$d hores</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">Fa %1$d dia</item>\n        <item quantity=\"many\">Fa %1$d dies</item>\n        <item quantity=\"other\">Fa %1$d dies</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">Fa %1$d mes</item>\n        <item quantity=\"many\">Fa %1$d mesos</item>\n        <item quantity=\"other\">Fa %1$d mesos</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d hora</item>\n        <item quantity=\"many\">%1$d hores</item>\n        <item quantity=\"other\">%1$d hores</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minut</item>\n        <item quantity=\"many\">%1$d minuts</item>\n        <item quantity=\"other\">%1$d minuts</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ca/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"local_storage\">Emmagatzematge local</string>\n    <string name=\"favourites\">Favorits</string>\n    <string name=\"history\">Historial</string>\n    <string name=\"error_occurred\">S\\'ha produït un error</string>\n    <string name=\"network_error\">Error de xarxa</string>\n    <string name=\"details\">Detalls</string>\n    <string name=\"chapters\">Capítols</string>\n    <string name=\"list_mode\">Mode de llista</string>\n    <string name=\"no_description\">Sense descripció</string>\n    <string name=\"clear_pages_cache\">Netejar memòria cau de la pàgina</string>\n    <string name=\"list\">Llista</string>\n    <string name=\"detailed_list\">Llista detallada</string>\n    <string name=\"grid\">Quadrícula</string>\n    <string name=\"remote_sources\">Fonts de Manga</string>\n    <string name=\"loading_\">S\\'està carregant…</string>\n    <string name=\"chapter_d_of_d\">Capítol %1$d de %2$d</string>\n    <string name=\"nothing_found\">No s\\'ha trobat res</string>\n    <string name=\"read\">Llegir</string>\n    <string name=\"you_have_not_favourites_yet\">Encara no hi ha favorits</string>\n    <string name=\"add_to_favourites\">Afegir a favorits</string>\n    <string name=\"add_new_category\">Nova categoria</string>\n    <string name=\"share\">Comparteix</string>\n    <string name=\"save\">Desa</string>\n    <string name=\"add\">Afegeix</string>\n    <string name=\"create_shortcut\">Crea drecera…</string>\n    <string name=\"share_s\">Compartir %s</string>\n    <string name=\"search\">Cerca</string>\n    <string name=\"search_manga\">Cerca manga</string>\n    <string name=\"manga_downloading_\">Descarregant…</string>\n    <string name=\"processing_\">Processant…</string>\n    <string name=\"download_complete\">Descarregat</string>\n    <string name=\"downloads\">Descàrregues</string>\n    <string name=\"by_name\">Nom</string>\n    <string name=\"popular\">Popular</string>\n    <string name=\"updated\">Actualitzat</string>\n    <string name=\"newest\">Més recent</string>\n    <string name=\"by_rating\">Qualificació</string>\n    <string name=\"sort_order\">Ordre d\\'ordenació</string>\n    <string name=\"filter\">Filtre</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"light\">Clar</string>\n    <string name=\"dark\">Fosc</string>\n    <string name=\"follow_system\">Seguir el sistema</string>\n    <string name=\"pages\">Pàgines</string>\n    <string name=\"clear\">Neteja</string>\n    <string name=\"remove\">Suprimir</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" eliminat de l\\'emmagatzematge local</string>\n    <string name=\"save_page\">Desa la pàgina</string>\n    <string name=\"page_saved\">Desat</string>\n    <string name=\"share_image\">Comparteix la imatge</string>\n    <string name=\"_import\">Importar</string>\n    <string name=\"delete\">Suprimir</string>\n    <string name=\"operation_not_supported\">Aquesta operació no està suportada</string>\n    <string name=\"text_file_not_supported\">Trieu un fitxer ZIP o bé un CBZ.</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Mode de lectura</string>\n    <string name=\"grid_size\">Mida de la quadrícula</string>\n    <string name=\"search_on_s\">Cerca a %s</string>\n    <string name=\"text_delete_local_manga\">Esborrar permanentment \\\"%s\\\" del dispositiu?</string>\n    <string name=\"switch_pages\">Canviar pàgines</string>\n    <string name=\"_continue\">Continua</string>\n    <string name=\"error\">Error</string>\n    <string name=\"clear_thumbs_cache\">Esborrar memòria cau de miniatures</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Estàndard</string>\n    <string name=\"settings\">Configuració</string>\n    <string name=\"computing_\">Computant…</string>\n    <string name=\"close\">Tancar</string>\n    <string name=\"try_again\">Torna a provar</string>\n    <string name=\"clear_history\">Esborrar historial</string>\n    <string name=\"history_is_empty\">Encara no hi ha història</string>\n    <string name=\"delete_manga\">Elimina manga</string>\n    <string name=\"reader_settings\">Configuració del lector</string>\n    <string name=\"clear_search_history\">Esborrar l\\'historial de cerca</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ckb/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d شت</item>\n        <item quantity=\"other\">%1$d شت</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d بەشی نوێ</item>\n        <item quantity=\"other\">%1$d بەشی نوێ</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d بەش</item>\n        <item quantity=\"other\">%1$d بەش</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d خولەک لەمەوبەر</item>\n        <item quantity=\"other\">%1$d خولەک لەمەوبەر</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d کاتژمێر لەمەوبەر</item>\n        <item quantity=\"other\">%1$d کاتژمێر لەمەوبەر</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d ڕۆژ لەمەوبەر</item>\n        <item quantity=\"other\">%1$d ڕۆژ لەمەوبەر</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ckb/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"demographic_kodomo\">کۆدۆمۆ</string>\n    <string name=\"local_storage\">بیرگەی ناوەکی</string>\n    <string name=\"favourites\">دڵخوازەکان</string>\n    <string name=\"history\">مێژوو</string>\n    <string name=\"error_occurred\">ھەڵەیەک ڕوویدا</string>\n    <string name=\"network_error\">ھەڵەی تۆڕ</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-cs/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d předmět</item>\n        <item quantity=\"few\">%1$d předměty</item>\n        <item quantity=\"other\">%1$d předmětů</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nová kapitola</item>\n        <item quantity=\"few\">%1$d nové kapitoly</item>\n        <item quantity=\"other\">%1$d nových kapitol</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d kapitola</item>\n        <item quantity=\"few\">%1$d kapitoly</item>\n        <item quantity=\"other\">%1$d kapitol</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">před %1$d minutou</item>\n        <item quantity=\"few\">před %1$d minutami</item>\n        <item quantity=\"other\">před %1$d minutami</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">před %1$d hodinou</item>\n        <item quantity=\"few\">před %1$d hodinami</item>\n        <item quantity=\"other\">před %1$d hodinami</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">před %1$d dnem</item>\n        <item quantity=\"few\">před %1$d dny</item>\n        <item quantity=\"other\">před %1$d dny</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">před %1$d měsícem</item>\n        <item quantity=\"few\">před %1$d měsíci</item>\n        <item quantity=\"other\">před %1$d měsíci</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d hodina</item>\n        <item quantity=\"few\">%1$d hodiny</item>\n        <item quantity=\"other\">%1$d hodin</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuta</item>\n        <item quantity=\"few\">%1$d minuty</item>\n        <item quantity=\"other\">%1$d minut</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-cs/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"network_error\">Problém s připojením</string>\n    <string name=\"details\">Detaily</string>\n    <string name=\"chapters\">Kapitoly</string>\n    <string name=\"list\">Seznam</string>\n    <string name=\"detailed_list\">Podrobný seznam</string>\n    <string name=\"grid\">Mřížka</string>\n    <string name=\"list_mode\">Režim seznamu</string>\n    <string name=\"settings\">Nastavení</string>\n    <string name=\"remote_sources\">Zdroje mang</string>\n    <string name=\"loading_\">Načítání…</string>\n    <string name=\"computing_\">Vypočítávání…</string>\n    <string name=\"chapter_d_of_d\">Kapitola %1$d z %2$d</string>\n    <string name=\"close\">Zavřít</string>\n    <string name=\"try_again\">Zkusit znovu</string>\n    <string name=\"clear_history\">Vyčistit historii</string>\n    <string name=\"nothing_found\">Nic nenalezeno</string>\n    <string name=\"history_is_empty\">Zatím žádná historie</string>\n    <string name=\"read\">Číst</string>\n    <string name=\"you_have_not_favourites_yet\">Zatím žádné oblíbené</string>\n    <string name=\"add_to_favourites\">Přidat do oblíbených</string>\n    <string name=\"add\">Přidat</string>\n    <string name=\"share\">Sdílet</string>\n    <string name=\"create_shortcut\">Vytvořit zkratku</string>\n    <string name=\"share_s\">Sdílet %s</string>\n    <string name=\"processing_\">Zpracovávání…</string>\n    <string name=\"download_complete\">Staženo</string>\n    <string name=\"downloads\">Stažené</string>\n    <string name=\"by_name\">Název</string>\n    <string name=\"sort_order\">Pořadí řazení</string>\n    <string name=\"filter\">Filtr</string>\n    <string name=\"theme\">Téma</string>\n    <string name=\"light\">Světlé</string>\n    <string name=\"dark\">Tmavé</string>\n    <string name=\"follow_system\">Následovat systém</string>\n    <string name=\"remove\">Odstranit</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" smazáno z místního uložiště</string>\n    <string name=\"share_image\">Sdílet obrázek</string>\n    <string name=\"_import\">Importovat</string>\n    <string name=\"delete\">Smazat</string>\n    <string name=\"text_file_not_supported\">Vyberte soubor ZIP či CBZ.</string>\n    <string name=\"clear_pages_cache\">Vyčistit mezipaměť stran</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Standardní</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Režim čtení</string>\n    <string name=\"grid_size\">Velikost mřížky</string>\n    <string name=\"delete_manga\">Smazat mangu</string>\n    <string name=\"text_delete_local_manga\">Odstranit trvale \\\"%s\\\" z tohoto zařízení?</string>\n    <string name=\"reader_settings\">Nastavení čtečky</string>\n    <string name=\"switch_pages\">Prohodit strany</string>\n    <string name=\"_continue\">Pokračovat</string>\n    <string name=\"error\">Chyba</string>\n    <string name=\"clear_thumbs_cache\">Vyčistit mezipamět náhledů</string>\n    <string name=\"clear_search_history\">Vyčisit historii vyhledávání</string>\n    <string name=\"domain\">Doména</string>\n    <string name=\"app_update_available\">Je dostupná nová verze této aplikace</string>\n    <string name=\"open_in_browser\">Otevřít v prohlížeči</string>\n    <string name=\"notifications\">Oznámení</string>\n    <string name=\"text_empty_holder_primary\">Je tu nějak prázdno…</string>\n    <string name=\"text_search_holder_secondary\">Zkuste přeformulovat dotaz.</string>\n    <string name=\"text_history_holder_primary\">To co čtete se zobrazí zde</string>\n    <string name=\"text_history_holder_secondary\">Najděte co číst v sekci « Prozkoumat»</string>\n    <string name=\"text_local_holder_primary\">Nejdříve něco uložte</string>\n    <string name=\"text_local_holder_secondary\">Uložte něco z online zdrojů nebo importujte soubor.</string>\n    <string name=\"recent_manga\">Nedávné</string>\n    <string name=\"manga_shelf\">Polička</string>\n    <string name=\"pages_animation\">Animace stránek</string>\n    <string name=\"manga_save_location\">Složka pro stahování</string>\n    <string name=\"not_available\">Není dostupné</string>\n    <string name=\"cannot_find_available_storage\">Není dostupné uložiště</string>\n    <string name=\"other_storage\">Další uložiště</string>\n    <string name=\"done\">Hotovo</string>\n    <string name=\"all_favourites\">Všechny oblíbené</string>\n    <string name=\"favourites_category_empty\">Prázdná kategorie</string>\n    <string name=\"text_feed_holder\">Nové kapitoly toho co čtete jsou zobrazeny zde</string>\n    <string name=\"search_results\">Výsledky vyhledávání</string>\n    <string name=\"new_version_s\">Nová verze: %s</string>\n    <string name=\"updates_feed_cleared\">Vyčištěno</string>\n    <string name=\"rotate_screen\">Otočit displej</string>\n    <string name=\"update\">Aktualizovat</string>\n    <string name=\"dont_check\">Nekontrolovat</string>\n    <string name=\"enter_password\">Zadejte heslo</string>\n    <string name=\"wrong_password\">Špatné heslo</string>\n    <string name=\"protect_application\">Chránit aplikaci</string>\n    <string name=\"repeat_password\">Zopakujte heslo</string>\n    <string name=\"about\">O</string>\n    <string name=\"app_version\">Verze %s</string>\n    <string name=\"check_for_updates\">Zkontrolovat aktualizace</string>\n    <string name=\"no_update_available\">Nejsou dostupné žádné aktualizace</string>\n    <string name=\"scale_mode\">Režim měřítka</string>\n    <string name=\"zoom_mode_fit_center\">Pasovat do středu</string>\n    <string name=\"zoom_mode_fit_height\">Pasovat na výšku</string>\n    <string name=\"zoom_mode_fit_width\">Pasovat na šířku</string>\n    <string name=\"zoom_mode_keep_start\">Uchovat při zapnutí</string>\n    <string name=\"black_dark_theme\">Černá</string>\n    <string name=\"black_dark_theme_summary\">Využívá méně energie na AMOLED displejích</string>\n    <string name=\"backup_restore\">Zálohovat a obnovit</string>\n    <string name=\"create_backup\">Vytvořit zálohu dat</string>\n    <string name=\"restore_backup\">Obnovit ze zálohy</string>\n    <string name=\"data_restored\">Obnoveno</string>\n    <string name=\"preparing_\">Připravuji…</string>\n    <string name=\"file_not_found\">Soubor nebyl nalezen</string>\n    <string name=\"data_restored_success\">Všechna data byla obnovena</string>\n    <string name=\"clear_updates_feed\">Vyčistit frontu aktualizací</string>\n    <string name=\"feed_will_update_soon\">Aktualizace fronty brzy začne</string>\n    <string name=\"just_now\">Právě teď</string>\n    <string name=\"long_ago\">Dávno</string>\n    <string name=\"group\">Skupina</string>\n    <string name=\"today\">Dnes</string>\n    <string name=\"tap_to_try_again\">Klikněte pro zopakování</string>\n    <string name=\"reader_mode_hint\">Zvolená konfigurace bude pro tuto mangu zapamatována</string>\n    <string name=\"silent\">Tiché</string>\n    <string name=\"captcha_required\">Vyžadována CAPTCHA</string>\n    <string name=\"captcha_solve\">Vyřešit</string>\n    <string name=\"text_clear_updates_feed_prompt\">Vyčistit permanentně všechnu historii aktualizací\\?</string>\n    <string name=\"check_for_new_chapters\">Zkontrolovat nové kapitoly</string>\n    <string name=\"reverse\">Obráceně</string>\n    <string name=\"sign_in\">Přihlásit se</string>\n    <string name=\"auth_required\">Abyste mohli vidět tento kontent, je nutné se přihlásit</string>\n    <string name=\"default_s\">Základní: %s</string>\n    <string name=\"next\">Další</string>\n    <string name=\"protect_application_subtitle\">Zadejte heslo pro spuštění aplikace s</string>\n    <string name=\"password_length_hint\">Heslo musí obsahovat nejméně 4 nebo více znaků</string>\n    <string name=\"text_clear_search_history_prompt\">Odstranit permanentně všechny nedávné vyhledávání\\?</string>\n    <string name=\"welcome\">Vítejte</string>\n    <string name=\"backup_saved\">Záloha uložena</string>\n    <string name=\"queued\">Ve frontě</string>\n    <string name=\"chapter_is_missing\">Chybí kapitola</string>\n    <string name=\"about_app_translation\">Překlad</string>\n    <string name=\"auth_complete\">Autorizováno</string>\n    <string name=\"auth_not_supported_by\">Přihlášení na %s není podporováno</string>\n    <string name=\"text_clear_cookies_prompt\">Budete odhlášeni ze všech zdrojů</string>\n    <string name=\"genres\">Žánry</string>\n    <string name=\"state_ongoing\">Pokračující</string>\n    <string name=\"system_default\">Základní</string>\n    <string name=\"exclude_nsfw_from_history\">Vynechat všechny NSFW mangy z historie</string>\n    <string name=\"show_pages_numbers\">Očíslované stránky</string>\n    <string name=\"screenshots_policy\">Zásady snímku obrazovky</string>\n    <string name=\"screenshots_allow\">Povolit</string>\n    <string name=\"screenshots_block_nsfw\">Zakázat na NSFW</string>\n    <string name=\"screenshots_block_all\">Vždy blokovat</string>\n    <string name=\"suggestions\">Návrhy</string>\n    <string name=\"suggestions_enable\">Zapnout návrhy</string>\n    <string name=\"suggestions_summary\">Navrhovat mangy dle vašich preferencí</string>\n    <string name=\"suggestions_info\">Všechna data jsou analyzována pouze lokálně na tomto zařízení a nikdy nejsou nikam odesílána.</string>\n    <string name=\"text_suggestion_holder\">Začněte číst mangu a získejte personalizované návrhy</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Nenavrhovat NSFW mangu</string>\n    <string name=\"enabled\">Zapnuto</string>\n    <string name=\"disabled\">Vypnuto</string>\n    <string name=\"reset_filter\">Resetovat filtr</string>\n    <string name=\"onboard_text\">Vyberte jazyk ve kterém si přejete číst mangu. Můžete jej později změnit v nastavení.</string>\n    <string name=\"never\">Nikdy</string>\n    <string name=\"only_using_wifi\">Pouze na Wi-Fi</string>\n    <string name=\"always\">Vždy</string>\n    <string name=\"preload_pages\">Přednačíst stránky</string>\n    <string name=\"logged_in_as\">Přihlášen jako %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Různé jazyky</string>\n    <string name=\"search_chapters\">Najít kapitolu</string>\n    <string name=\"chapters_empty\">Žádné kapitoly v této manze</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Vzhled</string>\n    <string name=\"suggestions_excluded_genres_summary\">Specifikujte žánry které nechcete vidět v návrzích</string>\n    <string name=\"text_delete_local_manga_batch\">Permanentně odstranit vybrané předměty ze zařízení\\?</string>\n    <string name=\"removal_completed\">Odstraňování dokončeno</string>\n    <string name=\"download_slowdown\">Zpomalení stahování</string>\n    <string name=\"local_manga_processing\">Zpracovávání uložených mang</string>\n    <string name=\"chapters_will_removed_background\">Kapitoly budou odstraněny na pozadí</string>\n    <string name=\"account_already_exists\">Účet již existuje</string>\n    <string name=\"back\">Zpět</string>\n    <string name=\"sync_title\">Synchronizujte svá data</string>\n    <string name=\"email_enter_hint\">Pro pokračování zadejte svůj email</string>\n    <string name=\"hide\">Schovat</string>\n    <string name=\"new_sources_text\">Jsou dostupné nové zdroje mang</string>\n    <string name=\"check_new_chapters_title\">Kontrolovat nové kapiroly a upozornit na ně</string>\n    <string name=\"show_notification_new_chapters_on\">Dostanete oznámení o aktualizaci mang které čtete</string>\n    <string name=\"notifications_enable\">Zapnout oznámení</string>\n    <string name=\"name\">Jméno</string>\n    <string name=\"edit\">Upravit</string>\n    <string name=\"edit_category\">Upravit kategorii</string>\n    <string name=\"tracking\">Sledování</string>\n    <string name=\"empty_favourite_categories\">Žádné oblíbené kategorie</string>\n    <string name=\"logout\">Odhlásit se</string>\n    <string name=\"bookmark_add\">Přidat záložku</string>\n    <string name=\"bookmark_remove\">Odstranit záložku</string>\n    <string name=\"bookmarks\">Záložky</string>\n    <string name=\"bookmark_added\">Přidána záložka</string>\n    <string name=\"undo\">Vrátit zpět</string>\n    <string name=\"removed_from_history\">Odstraněno z historie</string>\n    <string name=\"dns_over_https\">DNS přes HTTPS</string>\n    <string name=\"default_mode\">Základní režim</string>\n    <string name=\"detect_reader_mode\">Automaticky detekovat režim čtení</string>\n    <string name=\"detect_reader_mode_summary\">Automaticky detekovat zda je manga webtoon</string>\n    <string name=\"disable_battery_optimization\">Vypnout optimalizaci baterie</string>\n    <string name=\"disable_battery_optimization_summary\">Pomáhá s kontrolou nových kapitol</string>\n    <string name=\"send\">Odeslat</string>\n    <string name=\"status_planned\">Plánované</string>\n    <string name=\"status_reading\">Čtení</string>\n    <string name=\"status_re_reading\">Znovu-čtení</string>\n    <string name=\"status_completed\">Dokončeno</string>\n    <string name=\"status_on_hold\">Pozastaveno</string>\n    <string name=\"status_dropped\">Zahozeno</string>\n    <string name=\"disable_all\">Vypnout vše</string>\n    <string name=\"use_fingerprint\">Pokud lze, použijte biometrii</string>\n    <string name=\"appwidget_shelf_description\">Manga z vašich oblíbených</string>\n    <string name=\"appwidget_recent_description\">Vaše nedávno čtená manga</string>\n    <string name=\"report\">Hlášení</string>\n    <string name=\"show_reading_indicators\">Zobrazovat indikátory pokroku ve čtení</string>\n    <string name=\"data_deletion\">Smazání dat</string>\n    <string name=\"show_reading_indicators_summary\">Zobrazovat procento čtení v historii a oblíbených</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga označená jako NSFW nebude nikdy přidána do historie a nebude ukládán Váš pokrok</string>\n    <string name=\"clear_cookies_summary\">Může pomoci v případě nekterých problémů. Všechny autorizace budou neplatné</string>\n    <string name=\"show_all\">Zobrazit vše</string>\n    <string name=\"invalid_domain_message\">Neplatná doména</string>\n    <string name=\"clear_all_history\">Vyčistit celou historii</string>\n    <string name=\"history_cleared\">Historie vyčištěna</string>\n    <string name=\"manage\">Spravovat</string>\n    <string name=\"no_bookmarks_yet\">Zatím žádné záložky</string>\n    <string name=\"bookmarks_removed\">Záložky odstraněny</string>\n    <string name=\"no_manga_sources\">Žádné zdroje mang</string>\n    <string name=\"no_manga_sources_text\">Zapnout zdroje mang pro čtení online</string>\n    <string name=\"categories_delete_confirm\">Jste si jisti že chcete smazat zvolené oblíbené kategorie? \\nVšechny mangy v ní budou ztraceny a nelze jej vrátit zpět.</string>\n    <string name=\"reorder\">Přeskupit</string>\n    <string name=\"empty\">Prázdné</string>\n    <string name=\"explore\">Prozkoumat</string>\n    <string name=\"confirm_exit\">Stiskněte Zpět znovu pro ukončení</string>\n    <string name=\"exit_confirmation_summary\">Stiskněte Zpět dvakrát pro ukončení aplikace</string>\n    <string name=\"exit_confirmation\">Potvrzení ukončení</string>\n    <string name=\"saved_manga\">Uložená manga</string>\n    <string name=\"pages_cache\">Mezipaměť stránek</string>\n    <string name=\"other_cache\">Další mezipamět</string>\n    <string name=\"storage_usage\">Využívání uložiště</string>\n    <string name=\"available\">Dostupné</string>\n    <string name=\"memory_usage_pattern\">%s -%s</string>\n    <string name=\"removed_from_favourites\">Odstraněno z oblíbených</string>\n    <string name=\"options\">Možnosti</string>\n    <string name=\"not_found_404\">Kontent nebyl nalezen nebo byl odstraněn</string>\n    <string name=\"incognito_mode\">Režim inkognito</string>\n    <string name=\"no_chapters\">Žádné kapitoly</string>\n    <string name=\"automatic_scroll\">Automatické rolování</string>\n    <string name=\"reader_info_pattern\">Kap. %1$d/%2$d Str. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Zobrazovat informační lištu v čtečce</string>\n    <string name=\"comics_archive\">Archiv komiksů</string>\n    <string name=\"folder_with_images\">Složka s obrázky</string>\n    <string name=\"importing_manga\">Importuji mangu</string>\n    <string name=\"import_will_start_soon\">Importování brzy začne</string>\n    <string name=\"feed\">Fronta</string>\n    <string name=\"manga_error_description_pattern\">Podrobnosti chyby:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Zkuste &lt;a href=%2$s&gt;otveřít mangu v prohlížeči&lt;/a&gt; abyste se ujistili že je dostupná na zdroji&lt;br&gt;2. Ujistěte se že používáte &lt;a href=kotatsu://about&gt;nejnovější verzi Kotatsu&lt;/a&gt;&lt;br&gt;3. Pokud je dostupná, pošlete hlášení o chybě vývojářům.</string>\n    <string name=\"history_shortcuts\">Zobrazovat zkratky nedávných mang</string>\n    <string name=\"history_shortcuts_summary\">Udělejte nedávné mangy dostupné dlouhým kliknutím na ikonu aplikace</string>\n    <string name=\"reader_control_ltr_summary\">Neměňte směr přepínání stránek v režimu čtení; například stisknutí šipky vpravo vždy přepne na další stránku. Tato volba ovlivňuje pouze hardwarová vstupní zařízení</string>\n    <string name=\"reader_control_ltr\">Ovládání ergonomické čtečky</string>\n    <string name=\"color_correction\">Korekce barev</string>\n    <string name=\"brightness\">Jas</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"reset\">Resetovat</string>\n    <string name=\"text_unsaved_changes_prompt\">Uložit nebo zahodit neuložené změny\\?</string>\n    <string name=\"discard\">Zahodit</string>\n    <string name=\"error_no_space_left\">Na zařízení není volné žádné místo</string>\n    <string name=\"reader_slider\">Zobrazovat posuvník změny stran</string>\n    <string name=\"webtoon_zoom\">Webtoon přiblížení</string>\n    <string name=\"network_unavailable\">Připojení není dostupné</string>\n    <string name=\"network_unavailable_hint\">Zapněte Wi-Fi nebo mobilní data abyste mohli číst mangu online</string>\n    <string name=\"server_error\">Chyba na straně serveru (%1$d). Prosíme zkuste to znovu později</string>\n    <string name=\"clear_new_chapters_counters\">Také vyčistit informace o nových kapitolách</string>\n    <string name=\"compact\">Kompaktní</string>\n    <string name=\"source_disabled\">Zdroj vypnut</string>\n    <string name=\"prefetch_content\">Přednačítání kontentu</string>\n    <string name=\"mark_as_current\">Označit jako aktuální</string>\n    <string name=\"language\">Jazyk</string>\n    <string name=\"share_logs\">Sdílet záznamy</string>\n    <string name=\"enable_logging\">Zapnout zaznamenávání</string>\n    <string name=\"enable_logging_summary\">Zaznamenejte některé akce pro účely ladění. Nezapínejte jej, pokud si nejste jisti, co děláte</string>\n    <string name=\"show_suspicious_content\">Zobrazovat podezřelý kontent</string>\n    <string name=\"theme_name_dynamic\">Dynamické</string>\n    <string name=\"color_theme\">Schéma barev</string>\n    <string name=\"show_in_grid_view\">Zobrazovat v zobrazení mřížek</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"nothing_here\">Nic tu není</string>\n    <string name=\"scrobbling_empty_hint\">Pro sledování pokroku čtení, vyberte Menu → Sledovat na displeji detailů mangy.</string>\n    <string name=\"services\">Služby</string>\n    <string name=\"allow_unstable_updates\">Povolit nestabilní aktualizace</string>\n    <string name=\"allow_unstable_updates_summary\">Přijímat oznámení o nestabilních sestaveních</string>\n    <string name=\"download_started\">Stahování začalo</string>\n    <string name=\"got_it\">Mám to</string>\n    <string name=\"sources_reorder_tip\">Klikněte a přidržte na předmětu pro přeskupení</string>\n    <string name=\"user_agent\">Záhlaví UserAgent</string>\n    <string name=\"settings_apply_restart_required\">Prosíme restartujte aplikaci pro aplikování těchto změn</string>\n    <string name=\"comics_archive_import_description\">Můžete vybrat jeden či více .cbz nebo .zip souborů, každý soubor bude brán jako samostatná manga.</string>\n    <string name=\"show_on_shelf\">Zobrazovat na poličce</string>\n    <string name=\"sync_auth_hint\">Můžete se přihlásit do již existujícího účtu nebo vytvořit nový</string>\n    <string name=\"find_similar\">Najít podobné</string>\n    <string name=\"sync_settings\">Nastavení synchronizace</string>\n    <string name=\"server_address\">Adresa serveru</string>\n    <string name=\"ignore_ssl_errors\">Ignorovat SSL chyby</string>\n    <string name=\"mirror_switching\">Vybrat zrcadlo automaticky</string>\n    <string name=\"mirror_switching_summary\">Automaticky přepínat domény pro zdroje manga při chybách, pokud jsou k dispozici zrcadla</string>\n    <string name=\"pause\">Pozastavit</string>\n    <string name=\"resume\">Vrátit</string>\n    <string name=\"paused\">Pozastaveno</string>\n    <string name=\"remove_completed\">Odstranit dokončené</string>\n    <string name=\"downloads_wifi_only_summary\">Zastavit stahování při měnění na mobilní data</string>\n    <string name=\"suggestion_manga\">Doporučení: %s</string>\n    <string name=\"suggestions_notifications_summary\">Občas zobrazit oznámení s navrženou mangou</string>\n    <string name=\"more\">Více</string>\n    <string name=\"enable\">Zapnout</string>\n    <string name=\"no_thanks\">Ne, děkuji</string>\n    <string name=\"cancel_all_downloads_confirm\">Všechna aktivní stahování budou zrušena, částečně stažená data budou ztracena</string>\n    <string name=\"remove_completed_downloads_confirm\">Historie stahování bude permanentně odstraněna. Žádné stažené soubory nebudou ovlivněny</string>\n    <string name=\"text_downloads_list_holder\">Nemáte žádná stažení</string>\n    <string name=\"downloads_resumed\">Stahování bylo vráceno</string>\n    <string name=\"downloads_paused\">Stahování bylo pozastaveno</string>\n    <string name=\"downloads_removed\">Stažené soubory byly odstraněny</string>\n    <string name=\"downloads_cancelled\">Stahování bylo zrušeno</string>\n    <string name=\"suggestions_enable_prompt\">Chcete získávat personalizované návrhy mang\\?</string>\n    <string name=\"web_view_unavailable\">WebView není dostupné: zkontrolovay jestli je provozovatel WebView nainstalovaný</string>\n    <string name=\"clear_network_cache\">Vyčistit mezipaměť sítě</string>\n    <string name=\"type\">Typ</string>\n    <string name=\"address\">Adresa</string>\n    <string name=\"invalid_value_message\">Neplatná hodnota</string>\n    <string name=\"downloaded\">Staženo</string>\n    <string name=\"images_proxy_title\">Proxy pro optimalizaci obrázků</string>\n    <string name=\"images_procy_description\">Použít službu wsrv.nl ke snížení využití provozu a urychlení načítání obrázků, pokud je to možné</string>\n    <string name=\"invert_colors\">Invertovat barvy</string>\n    <string name=\"username\">Uživatelské jméno</string>\n    <string name=\"password\">Heslo</string>\n    <string name=\"authorization_optional\">Autorizace (dobrovolné)</string>\n    <string name=\"invalid_port_number\">Neplatné číslo portu</string>\n    <string name=\"network\">Síť</string>\n    <string name=\"data_and_privacy\">Data a soukromí</string>\n    <string name=\"restore_summary\">Obnovit nedávno vytvořenou zálohu</string>\n    <string name=\"reader_info_bar_summary\">Zobrazovat aktuální čas a pokrok ve čtení na vršku displeje</string>\n    <string name=\"show_pages_numbers_summary\">Zobrazovat číslo strany ve spodním rohu</string>\n    <string name=\"download_option_all_chapters\">Všechny kapitoly s překladem %s</string>\n    <string name=\"download_option_whole_manga\">Celá manga</string>\n    <string name=\"download_option_first_n_chapters\">První %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Další nepřečtené %s</string>\n    <string name=\"download_option_all_unread\">Všechny nepřečtené kapitoly</string>\n    <string name=\"download_option_all_unread_b\">Všechny nepřečtené kapitoly (%s)</string>\n    <string name=\"pick_custom_directory\">Vyberte vlastní adresář</string>\n    <string name=\"no_access_to_file\">Nemáte žádný přístup k tomuto souboru nebo adresáři</string>\n    <string name=\"local_manga_directories\">Lokální adresář mang</string>\n    <string name=\"local_storage\">Místní uložiště</string>\n    <string name=\"favourites\">Oblíbené</string>\n    <string name=\"history\">Historie</string>\n    <string name=\"error_occurred\">Vyskytla se chyba</string>\n    <string name=\"add_new_category\">Nová kategorie</string>\n    <string name=\"save\">Uložit</string>\n    <string name=\"search\">Hledat</string>\n    <string name=\"manga_downloading_\">Stahování…</string>\n    <string name=\"updated\">Aktualizováno</string>\n    <string name=\"by_rating\">Hodnocení</string>\n    <string name=\"search_manga\">Hledat mangu</string>\n    <string name=\"popular\">Populární</string>\n    <string name=\"newest\">Nejnovější</string>\n    <string name=\"no_description\">Žádný popis</string>\n    <string name=\"pages\">Strany</string>\n    <string name=\"clear\">Vyčistit</string>\n    <string name=\"save_page\">Uložit stranu</string>\n    <string name=\"page_saved\">Stránka uložena</string>\n    <string name=\"operation_not_supported\">Tato operace není podporována</string>\n    <string name=\"search_on_s\">Hledat na %s</string>\n    <string name=\"internal_storage\">Interní uložiště</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d z %2$d na</string>\n    <string name=\"notification_sound\">Zvuk oznámení</string>\n    <string name=\"search_history_cleared\">Vyčištěno</string>\n    <string name=\"external_storage\">Externí uložiště</string>\n    <string name=\"new_chapters\">Nové kapitoly</string>\n    <string name=\"download\">Stáhnout</string>\n    <string name=\"notifications_settings\">Nastavení oznámení</string>\n    <string name=\"light_indicator\">LED indikátor</string>\n    <string name=\"vibration\">Vibrace</string>\n    <string name=\"favourites_categories\">Oblíbené kategorie</string>\n    <string name=\"remove_category\">Odstranit</string>\n    <string name=\"read_later\">Přečíst později</string>\n    <string name=\"updates\">Aktualizace</string>\n    <string name=\"size_s\">Velikost: %s</string>\n    <string name=\"passwords_mismatch\">Neodpovídající heslo</string>\n    <string name=\"track_sources\">Hledat aktualizace</string>\n    <string name=\"protect_application_summary\">Zeptat se na heslo při zapnutí Kotatsu</string>\n    <string name=\"yesterday\">Včera</string>\n    <string name=\"right_to_left\">Z prava doleva</string>\n    <string name=\"clear_feed\">Vyčistit frontu</string>\n    <string name=\"create_category\">Nová kategorie</string>\n    <string name=\"clear_cookies\">Vyčistit cookies</string>\n    <string name=\"data_restored_with_errors\">Data byla obnovena, ale vyskytly se chyby</string>\n    <string name=\"backup_information\">Můžete vytvořit zálohu vaší historie a oblíbených a obnovit jej</string>\n    <string name=\"cookies_cleared\">Všechny cookies byli odstraněny</string>\n    <string name=\"suggestions_updating\">Aktualizují se návrhy</string>\n    <string name=\"tracker_warning\">Některá zařízení mají jiné systémové chování, to může rozbít procesy na pozadí.</string>\n    <string name=\"read_more\">Číst více</string>\n    <string name=\"about_app_translation_summary\">Přeložte tuto aplikaci</string>\n    <string name=\"confirm\">Potvrdit</string>\n    <string name=\"suggestions_excluded_genres\">Vynechat žánry</string>\n    <string name=\"download_slowdown_summary\">Pomohá předejít zablokování vaší IP adresy</string>\n    <string name=\"canceled\">Zrušeno</string>\n    <string name=\"sync\">Synchronizace</string>\n    <string name=\"show_notification_new_chapters_off\">Nedostanete oznámení ale nové kapitoly budou v seznamu zvýrazněné</string>\n    <string name=\"bookmark_removed\">Záložka odstraněna</string>\n    <string name=\"crash_text\">Něco se pokazilo. Prosím odešlete hlášení o chybě vývojářům aby jste nám ji pomohli opravit.</string>\n    <string name=\"state_finished\">Hotovo</string>\n    <string name=\"select_range\">Vybrat rozsah</string>\n    <string name=\"last_2_hours\">Poslední 2 hodiny</string>\n    <string name=\"no_bookmarks_summary\">Můžete vytvořit záložku při čtení mangy</string>\n    <string name=\"random\">Náhodné</string>\n    <string name=\"import_completed\">Importování dokončeno</string>\n    <string name=\"import_completed_hint\">Můžete odstranit originální soubor z uložiště abyste ušetřili místo</string>\n    <string name=\"folder_with_images_import_description\">Můžete vybrat adresář s archivy nebo obrázky. Každý archiv (nebo podkategorie) bude brán jako kapitola.</string>\n    <string name=\"speed\">Rychlost</string>\n    <string name=\"downloads_wifi_only\">Stahovat pouze přes Wi-Fi</string>\n    <string name=\"sync_host_description\">Můžete použít samostatně hostovaný synchronizační server nebo základní. Neměňte pokud si nejste jisti co děláte.</string>\n    <string name=\"cancel_all\">Zrušit vše</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"port\">Port</string>\n    <string name=\"webtoon_zoom_summary\">Povolit přiblížení v gestu ve webtoon režimu</string>\n    <string name=\"clear_source_cookies_summary\">Vyčistit cookies pouze pro specifikované domény. Ve většině případech bude neplatná autorizace</string>\n    <string name=\"download_option_manual_selection\">Vyberte kapitoly manuálně</string>\n    <string name=\"description\">Popis</string>\n    <string name=\"state_upcoming\">Již brzy</string>\n    <string name=\"no_manga_sources_found\">Na Váš dotaz nebyly nalezeny žádné zdroje mang</string>\n    <string name=\"downloads_settings_info\">Pokud máte problémy s blokováním ze strany serveru, můžete v nastavení zdrojů povolit zpomalení stahování pro jednotlivé zdroje mang</string>\n    <string name=\"content_rating\">Klasifikace obsahu</string>\n    <string name=\"rating_safe\">Bezpečný</string>\n    <string name=\"chapters_grid_view\">Zobrazení v mřížce</string>\n    <string name=\"categories\">Kategorie</string>\n    <string name=\"online_variant\">Online varianta</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"disable_nsfw\">Vypnout NSFW</string>\n    <string name=\"email_password_enter_hint\">Pro pokračování zadejte svůj e-mail a heslo</string>\n    <string name=\"this_month\">Tento měsíc</string>\n    <string name=\"voice_search\">Hlasové vyhledávání</string>\n    <string name=\"related_manga\">Související manga</string>\n    <string name=\"color_light\">Světlý</string>\n    <string name=\"color_dark\">Tmavý</string>\n    <string name=\"color_white\">Bílá</string>\n    <string name=\"background\">Pozadí</string>\n    <string name=\"color_black\">Černá</string>\n    <string name=\"data_not_restored\">Data nebyla obnovena</string>\n    <string name=\"in_progress\">Probíhá</string>\n    <string name=\"captcha_required_summary\">%s vyžaduje vyřešení captcha aby fungovalo správně</string>\n    <string name=\"languages\">Jazyky</string>\n    <string name=\"unknown\">Neznámé</string>\n    <string name=\"too_many_requests_message\">Příliš mnoho dotazů. Zkuste to později</string>\n    <string name=\"related_manga_summary\">Zobrazí seznam souvisejících mang. V některých případech může být nepřesný nebo chybět</string>\n    <string name=\"advanced\">Pokročilý</string>\n    <string name=\"manga_list\">Seznam mang</string>\n    <string name=\"main_screen_sections\">Oddíly hlavní obrazovky</string>\n    <string name=\"on_device\">Na zařízení</string>\n    <string name=\"directories\">Rejstříky</string>\n    <string name=\"items_limit_exceeded\">Nelze přidávat žádné další položky</string>\n    <string name=\"to_top\">Úplně nahoru</string>\n    <string name=\"moved_to_top\">Přesunuto na začátek</string>\n    <string name=\"zoom_out\">Oddálit</string>\n    <string name=\"zoom_in\">Přiblížit</string>\n    <string name=\"reader_zoom_buttons_summary\">Zobrazit či nezobrazit tlačítka ovládání přiblížení v pravém dolním rohu</string>\n    <string name=\"enhanced_colors_summary\">Snižuje banding, ale může ovlivnit výkon</string>\n    <string name=\"state_abandoned\">Upuštěný</string>\n    <string name=\"enhanced_colors\">32-bitový režim barev</string>\n    <string name=\"suggest_new_sources_summary\">Výzva k povolení nově přidaných zdrojů po aktualizaci aplikace</string>\n    <string name=\"suggest_new_sources\">Navrhnout nové zdroje po aktualizaci aplikace</string>\n    <string name=\"periodic_backups\">Pravidelné zálohy</string>\n    <string name=\"backup_frequency\">Frekvence vytváření záloh</string>\n    <string name=\"frequency_every_day\">Každý den</string>\n    <string name=\"frequency_every_2_days\">Každé 2 dny</string>\n    <string name=\"frequency_once_per_week\">Jednou týdně</string>\n    <string name=\"frequency_twice_per_month\">Dvakrát měsíčně</string>\n    <string name=\"frequency_once_per_month\">Jednou měsíčně</string>\n    <string name=\"backups_output_directory\">Výstupní adresář záloh</string>\n    <string name=\"periodic_backups_enable\">Povolit pravidelné zálohy</string>\n    <string name=\"last_successful_backup\">Poslední úspěšná záloha: %s</string>\n    <string name=\"lock_screen_rotation\">Uzamknout otáčení obrazovky</string>\n    <string name=\"content_type_other\">Jiné</string>\n    <string name=\"sources_catalog\">Katalog zdrojů</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"source_enabled\">Zdroj povolen</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"manage_sources\">Spravovat zdroje</string>\n    <string name=\"manual\">Ručně</string>\n    <string name=\"available_d\">Dostupný: %1$d</string>\n    <string name=\"disable_nsfw_summary\">Zakázat NSFW zdroje a skrýt mangy pro dospělé ze seznamu, je-li to možné</string>\n    <string name=\"error_multiple_genres_not_supported\">Filtrování podle více žánrů tento zdroj mang nepodporuje</string>\n    <string name=\"error_multiple_states_not_supported\">Filtrování podle více stavů tento zdroj mang nepodporuje</string>\n    <string name=\"error_search_not_supported\">Hledání není v tomto zdroji mang podporovaný</string>\n    <string name=\"skip\">Přeskočit</string>\n    <string name=\"grayscale\">Odstíny šedi</string>\n    <string name=\"data_not_restored_text\">Ujistěte se, že jste vybrali ten správný zálohový soubor</string>\n    <string name=\"manage_categories\">Spravovat kategorie</string>\n    <string name=\"show\">Ukázat</string>\n    <string name=\"apply\">Použít</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"search_hint\">Zadejte název mangy, žánr nebo název zdroje</string>\n    <string name=\"progress\">Pokrok</string>\n    <string name=\"order_added\">Přidáno</string>\n    <string name=\"error_corrupted_file\">Vrácená data jsou neplatná nebo je soubor poškozen</string>\n    <string name=\"reader_zoom_buttons\">Zobrazit tlačítka pro přiblížení</string>\n    <string name=\"keep_screen_on\">Ponechat obrazovku zapnutou</string>\n    <string name=\"keep_screen_on_summary\">Nevypínat obrazovku během čtení mangy</string>\n    <string name=\"list_options\">Seznam možností</string>\n    <string name=\"by_relevance\">Relevance</string>\n    <string name=\"state_paused\">Pozastavený</string>\n    <string name=\"reader_optimize\">Snížit spotřebu paměti (beta)</string>\n    <string name=\"reader_optimize_summary\">Snížit kvalitu stránek mimo obrazovku pro snížení spotřeby paměti</string>\n    <string name=\"state\">Stav</string>\n    <string name=\"globally\">Globálně</string>\n    <string name=\"this_manga\">Tato manga</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Filtrování podle žánrů nebo lokalizace tento zdroj nepodporuje</string>\n    <string name=\"error_filter_states_genre_not_supported\">Filtrování podle žánrů nebo stavů tento zdroj nepodporuje</string>\n    <string name=\"welcome_text\">Vyberte, které zdroje obsahu chcete povolit. To lze také později změnit v nastavení</string>\n    <string name=\"genres_search_hint\">Začněte psát název žánru</string>\n    <string name=\"sync_auth\">Přihlášení k synchronizačnímu účtu</string>\n    <string name=\"restore\">Obnovit</string>\n    <string name=\"backup_date_\">Datum obnovení: %s</string>\n    <string name=\"by_name_reverse\">Název obráceně</string>\n    <string name=\"suggestions_wifi_only_summary\">Neaktualizujte návrhy pomocí síťových připojení s účtovaným objemem dat</string>\n    <string name=\"tracker_wifi_only_summary\">Nevyhledávejte nové kapitoly pomocí síťových připojení s účtovaným objemem dat</string>\n    <string name=\"color_correction_apply_text\">Tato nastavení lze použít globálně nebo pouze pro aktuální manga. Pokud bude použito globálně, individuální nastavení nebudou přepsána.</string>\n    <string name=\"content_type_comics\">Komiks</string>\n    <string name=\"no_manga_sources_catalog_text\">V této sekci nejsou k dispozici žádné zdroje, případně již byly všechny přidány.\n\\nZůstaňte v kontaktu</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Mohlo by to pomoci se zahájením stahování, pokud s tím máte nějaké problémy</string>\n    <string name=\"genres_exclude\">Vyloučit žánry</string>\n    <string name=\"unsupported_backup_message\">Vyberte prosím správný zálohový soubor Kotatsu</string>\n    <string name=\"last_used\">Naposledy používané</string>\n    <string name=\"volume_\">Díl %d</string>\n    <string name=\"remove_from_history\">Odstranit z historie</string>\n    <string name=\"incognito_mode_hint\">Váš pokrok ve čtení nebude uložen</string>\n    <string name=\"last_read\">Poslední přečtený</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"hours_minutes_short\">%1$d h %2$d m</string>\n    <string name=\"vertical\">Svisle</string>\n    <string name=\"ask_for_dest_dir_every_time\">Pokaždé se ptát na cílovou složku</string>\n    <string name=\"show_menu\">Zobrazit menu</string>\n    <string name=\"mark_as_completed_prompt\">Označit vybranou mangu jako kompletně přečtenou?\n\\n\n\\nPozor: Stávající pokrok ve čtení se ztratí.</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"volume_unknown\">Neznámý díl</string>\n    <string name=\"prev_chapter\">Předchozí kapitola</string>\n    <string name=\"next_chapter\">Následující kapitola</string>\n    <string name=\"prev_page\">Předchozí stránka</string>\n    <string name=\"next_page\">Následující stránka</string>\n    <string name=\"reader_actions\">Akce čtečky</string>\n    <string name=\"reader_actions_summary\">Konfigurace akcí pro oblasti obrazovky, na které lze klepnout</string>\n    <string name=\"switch_pages_volume_buttons\">Povolit tlačítka ovládání hlasitosti</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Používání tlačítek ovládání hlasitosti k přetáčení stránek</string>\n    <string name=\"tap_action\">Akce klepnutím</string>\n    <string name=\"long_tap_action\">Akce dlouhým klepnutím</string>\n    <string name=\"none\">Žádný</string>\n    <string name=\"use_two_pages_landscape\">Použít rozvržení dvou stránek na šířku (beta)</string>\n    <string name=\"default_webtoon_zoom_out\">Výchozí zvětšení pro webtoon</string>\n    <string name=\"reading_time_estimation\">Zobrazit odhadovanou dobu čtení</string>\n    <string name=\"reading_time_estimation_summary\">Odhadovaná doba čtení může být nepřesná</string>\n    <string name=\"pages_saving\">Ukládání stránek</string>\n    <string name=\"default_page_save_dir\">Výchozí složka k ukládání stránek</string>\n    <string name=\"single_cbz_file\">Jediný soubor CBZ</string>\n    <string name=\"preferred_download_format\">Upřednostňovaný formát stahování</string>\n    <string name=\"multiple_cbz_files\">Vícero souborů CBZ</string>\n    <string name=\"clear_stats_confirm\">Opravdu chcete vymazat veškeré statistiky čtení? Tato akci nelze vrátit zpět.</string>\n    <string name=\"pages_read_s\">Přečtené stránky: %s</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" z \\\"%2$s\\\" bude ve Vaší historii a oblíbených položkách nahrazena \\\"%3$s\\\" z \\\"%4$s\\\" (je-li k dispozici)</string>\n    <string name=\"alternatives\">Jiné možnosti</string>\n    <string name=\"migrate\">Přesunout</string>\n    <string name=\"manga_migration\">Přesun mangy</string>\n    <string name=\"migration_completed\">Přesun dokončen</string>\n    <string name=\"delete_read_chapters\">Odstranit přečtené kapitoly</string>\n    <string name=\"delete_read_chapters_prompt\">Toto trvale odstraní veškeré přečtené kapitoly z místního uložiště. Můžete je stáhnout znovu později, ale importované kapitoly budou ztraceny navždy</string>\n    <string name=\"reading_stats\">Statistické údaje čtení</string>\n    <string name=\"other_manga\">Jiné mangy</string>\n    <string name=\"less_than_minute\">Méně než minutu</string>\n    <string name=\"clear_stats\">Vymazat statistiky</string>\n    <string name=\"week\">Týden</string>\n    <string name=\"month\">Měsíc</string>\n    <string name=\"all_time\">Po celou dobu</string>\n    <string name=\"day\">Den</string>\n    <string name=\"three_months\">Tři měsíce</string>\n    <string name=\"empty_stats_text\">Pro zvolené období nejsou k dispozici žádné statistiky</string>\n    <string name=\"show_pages_thumbs_summary\">Povolit záložku \\\"Stránky\\\" na obrazovce podrobností</string>\n    <string name=\"show_pages_thumbs\">Zobrazit náhledy stránek</string>\n    <string name=\"fullscreen_mode\">Režim celé obrazovky</string>\n    <string name=\"reader_fullscreen_summary\">Skrýt systémový stavový a navigační řádek</string>\n    <string name=\"category_hidden_done\">Tato kategorie byla skryta z hlavní obrazovky a je přístupná přes Menu → Spravovat kategorie</string>\n    <string name=\"automatic\">Automatický</string>\n    <string name=\"show_updated\">Zobrazit aktualizované</string>\n    <string name=\"split_by_translations_summary\">Zobrazit kapitoly s různými překlady samostatně, nikoli v jednom seznamu</string>\n    <string name=\"order_oldest\">Nejstarší</string>\n    <string name=\"long_ago_read\">Přečteno před dlouhou dobou</string>\n    <string name=\"unread\">Nepřečteno</string>\n    <string name=\"suggestions_unavailable_text\">Funkce návrhů je vypnutá</string>\n    <string name=\"check_for_new_chapters_disabled\">Zjišťování nových kapitol je vypnuté</string>\n    <string name=\"rating_suggestive\">Sugestivní</string>\n    <string name=\"default_tab\">Výchozí záložka</string>\n    <string name=\"mark_as_completed\">Označit jako dokončený</string>\n    <string name=\"toggle_ui\">Zobrazit/skrýt uživatelské rozhraní</string>\n    <string name=\"statistics\">Statistiky</string>\n    <string name=\"no_chapters_deleted\">Nebyly odstraněny žádné kapitoly</string>\n    <string name=\"chapters_deleted_pattern\">Odstraněn %1$s, smazán %2$s</string>\n    <string name=\"delete_read_chapters_auto\">Odstraňovat přečtené kapitoly automaticky</string>\n    <string name=\"runs_on_app_start\">Spouští se při spuštění aplikace</string>\n    <string name=\"webtoon_gaps\">Mezery v režimu webtoon</string>\n    <string name=\"webtoon_gaps_summary\">Zobrazit svislé mezery mezi stránkami v režimu webtoon</string>\n    <string name=\"location\">Umístění</string>\n    <string name=\"config_reset_confirm\">Obnovit nastavení na výchozí hodnoty? Tuto akci nelze vrátit zpět.</string>\n    <string name=\"search_suggestions\">Návrhy na vyhledávání</string>\n    <string name=\"recent_queries\">Nedávné dotazy</string>\n    <string name=\"suggested_queries\">Navrhované dotazy</string>\n    <string name=\"error_no_data_received\">Ze serveru nebyla přijata žádná data</string>\n    <string name=\"rating_adult\">Dospělý</string>\n    <string name=\"show_labels_in_navbar\">Zobrazit štítky na navigačním panelu</string>\n    <string name=\"stats_cleared\">Statistiky smazány</string>\n    <string name=\"delete_read_chapters_summary\">Odstranit již přečtené kapitoly z místního uložiště k uvolnění paměti</string>\n    <string name=\"split_by_translations\">Rozdělení podle překladů</string>\n    <string name=\"hours_short\">%d h</string>\n    <string name=\"missing_storage_permission\">Přístup k manze na externím úložišti není povolen</string>\n    <string name=\"authors\">Autoři</string>\n    <string name=\"enable_source\">Povolit zdroj</string>\n    <string name=\"unsupported_source\">Tento zdroj mang není podporovaný</string>\n    <string name=\"blocked_by_server_message\">Jste zablokováni serverem. Zkuste použít jiné síťové připojení (VPN, proxy atd.)</string>\n    <string name=\"less_frequently\">Méně často</string>\n    <string name=\"more_frequently\">Častěji</string>\n    <string name=\"frequency_of_check\">Četnost kontrol</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Upevnit uživatelské rozhraní pro navigaci</string>\n    <string name=\"fix\">Upevnit</string>\n    <string name=\"pin_navigation_ui_summary\">Neskrývat navigační panel a zobrazení vyhledávání při posouvání</string>\n    <string name=\"disable_connectivity_check\">Vypnout kontrolu připojení</string>\n    <string name=\"ignore_ssl_errors_summary\">Můžeš vypnout SSL certifikáty pokud se potýkáš s problémy při připojení k internetovým zdrojům. Toto může ovlivnit tvoji bezpečnost. Restart aplikace je požadován po změnění tohoto nastavení.</string>\n    <string name=\"sources_disabled\">Vypnout zdroje</string>\n    <string name=\"disable\">Vypnout</string>\n    <string name=\"_new\">Nový l</string>\n    <string name=\"disable_connectivity_check_summary\">Přeskoč kontrolu připojení pokud s tím máš problémy (např. zapnout offline režim i když jsi připojený k internetu)</string>\n    <string name=\"disable_nsfw_notifications_summary\">Nezobrazovat notifikace o nových NSFW manga kapitolách</string>\n    <string name=\"all_languages\">Všechny jazyky</string>\n    <string name=\"screenshots_block_incognito\">Zablokovat když je privátní režim</string>\n    <string name=\"disable_nsfw_notifications\">Vypnout NSFW oznámení</string>\n    <string name=\"tracker_debug_info\">Log kontroly nových kapitol</string>\n    <string name=\"tracker_debug_info_summary\">Debug informace o kontrole nových kapitol na pozadí</string>\n    <string name=\"unstable_feature\">Nestabilní funkce</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Žádné mangy pomocí vybraných filtrů nebyly nalezeny</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"source_pinned\">Zdroj připnut</string>\n    <string name=\"pages_saved\">Stránky uloženy</string>\n    <string name=\"invalid_server_address_message\">Nesprávná adresa serveru</string>\n    <string name=\"pin\">Pin kód</string>\n    <string name=\"unpin\">Odepnout</string>\n    <string name=\"too_many_requests_message_retry\">Server přetížen. Zkuste to za %s</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"invalid_proxy_configuration\">Nesprávná konfigurace proxy</string>\n    <string name=\"plugin_incompatible_with_cause\">Plugin error:%s\\n Ujistěte se, že používáte nejnovější verzi Kotatsu a pluginu</string>\n    <string name=\"percent_left\">Procent zbývá</string>\n    <string name=\"plugin_incompatible\">Nekompatibilní plugin nebo vnitřní chyba. Ujistěte se, že používáte nejnovější verzi pluginu a aplikace Kotatsu.</string>\n    <string name=\"external_source\">Externí zdroj/plugin</string>\n    <string name=\"retry\">Opakovat</string>\n    <string name=\"connection_ok\">Připojení je v pořádku</string>\n    <string name=\"recent_sources\">Nedávné zdroje</string>\n    <string name=\"image_server\">Preferovaný server pro média</string>\n    <string name=\"crop_pages\">Oříznout stránky</string>\n    <string name=\"show_quick_filters\">Zobrazit rychlé filtry</string>\n    <string name=\"source_unpinned\">Zdroj odepnut</string>\n    <string name=\"sources_unpinned\">Zdroje odepnuty</string>\n    <string name=\"sources_pinned\">Zdroje připnuty</string>\n    <string name=\"percent_read\">Procenta přečtených</string>\n    <string name=\"chapters_read\">Kapitol přečtených</string>\n    <string name=\"chapters_left\">Kapitol zbývajících</string>\n    <string name=\"show_quick_filters_summary\">Povolí možnost filtrovat seznamy mang určitými parametry</string>\n    <string name=\"unstable_feature_summary\">Tato funkce je experimentální. Vytvořte si zálohu před tím, než ji zkusíte</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"recently_added\">Nedávno přidané</string>\n    <string name=\"popular_in_month\">Populární tento měsíc</string>\n    <string name=\"popular_in_year\">Populární tento rok</string>\n    <string name=\"original_language\">Původní jazyk</string>\n    <string name=\"year\">Rok</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"added_long_ago\">Přidáno před dlouhou dobou</string>\n    <string name=\"popular_in_hour\">Populární tuto hodinu</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographics\">Země</string>\n    <string name=\"popular_today\">Populární dnes</string>\n    <string name=\"popular_in_week\">Populární tento týden</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"years\">Roky</string>\n    <string name=\"any\">Jakýkoliv</string>\n    <string name=\"chapters_all\">Všechny</string>\n    <string name=\"ask_every_time\">Vždy se zeptat</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"backup_tg_echo\">Testovací zpráva</string>\n    <string name=\"content_type_novel\">Román</string>\n    <string name=\"error_not_image\">Neplatný formát: očekávan obrázek, ale získán %s</string>\n    <string name=\"telegram_chat_id_summary\">Zadejte ID chatu, kam by měly být odeslány zálohy</string>\n    <string name=\"destination_directory\">Cílový adresář</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) nahrazena \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"manga_fix_prompt\">Tato funkce bude hledat alternativní zdroje pro vybranou mangu. Tento proces potrvá nějaký čas a bude pokračovat na pozadí</string>\n    <string name=\"enable_all_sources\">Povolit všechny zdroje mang</string>\n    <string name=\"enable_all_sources_summary\">Všechny dostupné zdroje mangy budou trvale povoleny</string>\n    <string name=\"delete_old_backups_summary\">Automaticky mazat soubory starých záloh pro ušetření místa na úložišti</string>\n    <string name=\"sort_order_asc\">Vzestupně</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"save_manga_confirm\">Uložit vybranou mangu? Toto může využít připojení a místo na disku</string>\n    <string name=\"download_cellular_confirm\">Povolit stahování přes mobilní data?</string>\n    <string name=\"filter_search_warning\">Tento zdroj nepodporuje hledání s filtry. Filtry byly vymazány</string>\n    <string name=\"error_connection_reset\">Připojení bylo resetováno vzdáleným hostitelem</string>\n    <string name=\"backup_restored_background\">Záloha bude obnovena na pozadí</string>\n    <string name=\"restoring_backup\">Obnovování zálohy</string>\n    <string name=\"show_slider\">Zobrazit posuvník</string>\n    <string name=\"content_type_one_shot\">One shot</string>\n    <string name=\"backup_tg_check\">Zkontrolovat, jestli API funguje</string>\n    <string name=\"backup_tg_id_not_set\">Chat ID není nastaveno</string>\n    <string name=\"open_telegram_bot\">Otevřít Telegram bota</string>\n    <string name=\"not_in_favorites\">Není v oblíbených</string>\n    <string name=\"unpopular\">Nepopulární</string>\n    <string name=\"low_rating\">Nízké hodnocení</string>\n    <string name=\"sort_order_desc\">Sestupně</string>\n    <string name=\"by_date\">Podle Data</string>\n    <string name=\"popularity\">Popularita</string>\n    <string name=\"downloads_background\">Stahování na pozadí</string>\n    <string name=\"download_new_chapters\">Stáhnout nové kapitoly</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga se staženými kapitolami</string>\n    <string name=\"debug\">Ladit</string>\n    <string name=\"download_added\">Stahování přidáno</string>\n    <string name=\"more_options\">Více možností</string>\n    <string name=\"captcha_required_message\">Tento zdroj potřebuje pro pokračování vyřešení captcha</string>\n    <string name=\"updated_long_ago\">Aktualizováno dávno</string>\n    <string name=\"scrobbler_auth_required\">Přihlašte se do %s abyste mohli pokračovat</string>\n    <string name=\"no_alternatives_found\">Pro \\\"%s\\\" nebyly nalezeny žádné alternativy</string>\n    <string name=\"telegram_group\">Telegramová slupina</string>\n    <string name=\"allow_always\">Povolit vždy</string>\n    <string name=\"allow_once\">Pouze tentokrát</string>\n    <string name=\"landscape\">Na šířku</string>\n    <string name=\"delete_old_backups\">Smazat staré zálohy</string>\n    <string name=\"all_sources_enabled\">Všechny zdroje jsou povoleny</string>\n    <string name=\"rating\">Hodnocení</string>\n    <string name=\"source\">Zdroj</string>\n    <string name=\"incognito\">Inkognito</string>\n    <string name=\"reader_info_bar_transparent\">Průhledný informační proužek pro čtenáře</string>\n    <string name=\"handle_links\">Spravovat odkazy</string>\n    <string name=\"handle_links_summary\">Zpracování odkazů na manga z externích aplikací (např. webového prohlížeče). Může být také nutné povolit ji ručně v systémových nastaveních aplikace</string>\n    <string name=\"email\">Email</string>\n    <string name=\"content_type_artist_cg\">Umělec CG</string>\n    <string name=\"content_type_image_set\">Sada obrázků</string>\n    <string name=\"content_type_game_cg\">Hra CG</string>\n    <string name=\"source_code\">Zdrojový kód</string>\n    <string name=\"user_manual\">Uživatelský manuál</string>\n    <string name=\"chapter_selection_hint\">Můžete vybrat kapitoly ke stažení dlouhým stisknutím na ně v listu kapitol.</string>\n    <string name=\"screen_orientation\">Otočení obrazovky</string>\n    <string name=\"portrait\">Na výšku</string>\n    <string name=\"genre\">Žánr</string>\n    <string name=\"access_denied_403\">Přístup odepřen (403)</string>\n    <string name=\"max_backups_count\">Maximální počet záloh</string>\n    <string name=\"no_fix_required\">Pro \\\"%s\\\" není potřeba oprava</string>\n    <string name=\"clear_database\">Vyčistit databázi</string>\n    <string name=\"clear_database_summary\">Odstranění nepoužívaných informací o manze</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"send_backups_telegram\">Poslat zálohy do Telegramu</string>\n    <string name=\"test_connection\">Zkontrolovat připojení</string>\n    <string name=\"open_telegram_bot_summary\">Klikněte pro otevření chatu s Kotatsu Zálohovacím botem</string>\n    <string name=\"error_image_format\">Formát obrázku není podporován %s</string>\n    <string name=\"translation\">Překlad</string>\n    <string name=\"reader_controls_in_bottom_bar\">Ovládací prvky čtečky na spodním panelu</string>\n    <string name=\"chapters_and_pages\">Kapitoly a stránky</string>\n    <string name=\"pages_slider\">Posuvník přepínání stránek</string>\n    <string name=\"screen_rotation_locked\">Otáčení obrazovky bylo uzamčeno</string>\n    <string name=\"screen_rotation_unlocked\">Otáčení obrazovky bylo odemčeno</string>\n    <string name=\"fixing_manga\">Opravování mangy</string>\n    <string name=\"fixed\">Úspěšně opraveno</string>\n    <string name=\"start_download\">Začít stahování</string>\n    <string name=\"save_manga\">Uložit mangu</string>\n    <string name=\"stuck\">Zastaveno</string>\n    <string name=\"download_over_cellular\">Stahování pomocí mobilních dat</string>\n    <string name=\"skip_all\">Přeskočit vše</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"dont_allow\">Nepovolit</string>\n    <string name=\"scrobbler_auth_intro\">Přihlaste se do %s a nastavte integraci. To vám umožní sledovat postup a stav čtení mangy</string>\n    <string name=\"telegram_chat_id\">Telegram chat ID</string>\n    <string name=\"error_disclaimer_report\">Můžete poslat hlášení o chybě vývojářům. Tímto nám pomůžete vyřešit tento problém.</string>\n    <string name=\"search_disabled_sources\">Vyhledat ve vypnutých zdrojích</string>\n    <string name=\"chapter_volume_number\">Svazek %1$s Kapitola %2$s</string>\n    <string name=\"chapter_number\">Kapitola %s</string>\n    <string name=\"unnamed_chapter\">Nepojmenovaná kapitola</string>\n    <string name=\"error_details\">Detaily o chybě</string>\n    <string name=\"error_disclaimer_manga\">Zkuste otevřít mangu ve webovém prohlížeči, abyste zjistliti jestli je dostupná.</string>\n    <string name=\"error_disclaimer_app_outdated\">Vypadá to, že vaše verze Kotatsu je zastaralá. Prosíme, nainstalujte nejnovější verzi pro získání všech dostupných oprav chyb.</string>\n    <string name=\"disable_captcha_notifications\">Vypnout oznámení o captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Nebudete dostávat oznámení o řešení CAPTCHA pro tento zdroj, ale to může vést k rozbití operací na pozadí (hledání nových kapitol, získávání doporučení atd)</string>\n    <string name=\"tags_warnings\">Zvýraznit nebezpečné žánry</string>\n    <string name=\"tags_warnings_summary\">Zvýraznit žánry, které mohou být nevhodné pro většinu uživatelů</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"link_to_manga_in_app\">Odkaz na mangau v Kotatsu</string>\n    <string name=\"clear_browser_data\">Vyčistit data prohlížeče</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Dospělá manga nebude zobrazena v návrzích. Tato funkce může být nepřesná s některými zdroji</string>\n    <string name=\"include_disabled_sources\">Zahrnout vypnuté zdroje</string>\n    <string name=\"suggestions_disabled_sources_summary\">Zobrazit návrhy ze všech zdrojů mangy, včetně vypnutých</string>\n    <string name=\"clear_browser_data_summary\">Vyčistit data prohlížeče, např. cache a cookies. Upozornění: Všude budete odhlášeni a budte muset znovu řešit captcha</string>\n    <string name=\"global_search\">Globální vyhledávání</string>\n    <string name=\"search_everywhere\">Hledat všude</string>\n    <string name=\"expand\">Rozbalit</string>\n    <string name=\"collapse\">Sbalit</string>\n    <string name=\"adblock\">Blokuje reklamy v prohlížeči</string>\n    <string name=\"adblock_summary\">Blokuje reklamy ve vestavěném prohlížeči (beta)</string>\n    <string name=\"manga_override_hint\">Tyto změny ovlivní způsob, jakým se manga v aplikaci zobrazuje</string>\n    <string name=\"dont_ask_again\">Neptat se znovu</string>\n    <string name=\"share_backup\">Sdílet zálohu</string>\n    <string name=\"creating_backup\">Vytváření kopie</string>\n    <string name=\"collapse_long_description\">Sbalit dlouhý popis</string>\n    <string name=\"changelog\">Seznam změn</string>\n    <string name=\"saved_filters\">Uložené filtry</string>\n    <string name=\"enter_name\">Vložte jméno</string>\n    <string name=\"theme_name_expressive\">Výrazný (Test)</string>\n    <string name=\"pull_to_prev_chapter\">Uvolněte pro otevření předešlé kapitoly</string>\n    <string name=\"pull_to_next_chapter\">Uvolněte pro otevření další kapitoly</string>\n    <string name=\"pull_top_no_prev\">Žádná předešlá kapitola</string>\n    <string name=\"pull_bottom_no_next\">Žádná další kapitola</string>\n    <string name=\"frequency_every_6_hours\">Každých 6 hodin</string>\n    <string name=\"reader_navigation_inverted\">Převrátit navigační prvky</string>\n    <string name=\"reader_navigation_inverted_summary\">Vyměňit směr tlačítka hlasitosti a šipek na klávesnici (vlevo/nahoru/dolů/vpravo)</string>\n    <string name=\"two_page_scroll_sensitivity\">Dvoustranná citlivost posouvání</string>\n    <string name=\"enable_pull_gesture_title\">Povolit tahová gesta</string>\n    <string name=\"enable_pull_gesture_summary\">Použijte tahová gesta pro přepínání kapitol v režimu webtoon</string>\n    <string name=\"reader_chapter_toast\">Zobrazit změnu kapitoly pomocí pop-upu</string>\n    <string name=\"reader_chapter_toast_summary\">Zobrazit pop-up s názvem kapitoly, když se změní</string>\n    <string name=\"badges_in_lists\">Odznaky v seznamech</string>\n    <string name=\"link_to_manga_on_s\">Odkaz na mangu na %s</string>\n    <string name=\"no_write_permission_to_file\">Nemá oprávnění k ukládání souboru</string>\n    <string name=\"error_non_file_uri\">Zvolená cesta nemůže být použita, protože neoznačuje soubor nebo adresář</string>\n    <string name=\"use_default_cover\">Použít výchozí přebal</string>\n    <string name=\"pick_manga_page\">Vyberte stranu mangy</string>\n    <string name=\"pick_custom_file\">Vybrat vlastní přebal</string>\n    <string name=\"change_cover\">Změnit přebal</string>\n    <string name=\"page_switch_timer\">Strana se změní kažých ~%d sekund</string>\n    <string name=\"incognito_mode_hint_nsfw\">Tato manga může obsahovat dospělá témata. Chcete použít režim inkognito?</string>\n    <string name=\"incognito_for_nsfw\">Režim inkognito pro NSFW mangu</string>\n    <string name=\"additional_action_required\">Dodatečná akce je nutná</string>\n    <string name=\"hide_from_main_screen\">Skrýt z hlavní obrazovky</string>\n    <string name=\"changelog_summary\">Historie změn pro nedávno vydané verze</string>\n    <string name=\"reader_multitask\">Otevřít čtečku ve vlastním okně</string>\n    <string name=\"reader_multitask_summary\">Umožňuje zároveň otevřít několik různých mang ve vlastních oknech</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"book_effect\">Nažloutlé pozadí (filtr modrého světla)</string>\n    <string name=\"local_storage_cleanup\">Čištění místního úložišťe</string>\n    <string name=\"packup_creation_failed\">Nepodařilo se vytvořit zálohu</string>\n    <string name=\"main_screen\">Hlavní obrazovka</string>\n    <string name=\"main_screen_fab\">Zobrazit plovoucí tlačítko Pokračovat</string>\n    <string name=\"main_screen_fab_summary\">Umožňuje pokračovat ve čtení jedním kliknutím. Toto tlačítko se nezobrazí v režimu inkognito nebo když je historie prázdná</string>\n    <string name=\"error_corrupted_zip\">Poškozený ZIP archiv (%s)</string>\n    <string name=\"discord_rpc\">Discord Rich Presence</string>\n    <string name=\"discord_token\">Discord Token</string>\n    <string name=\"discord_token_summary\">Zadejte svůj Discord Token, abyste zapnuli Rich Presence</string>\n    <string name=\"discord_token_description\">Zadejte svůj Discord Token nebo klikněte na %s, abyste ho získali pomocí prohlížeče</string>\n    <string name=\"discord_token_hint\">Vložte svůj Discord Token zde</string>\n    <string name=\"discord_rpc_summary\">Zobrazit stav čtení na Discordu</string>\n    <string name=\"obtain\">Získat</string>\n    <string name=\"discord_rpc_description\">Čte mangu na Kotatsu - aplikace pro čtení mangy</string>\n    <string name=\"reading_s\">Čtení %s</string>\n    <string name=\"read_on_s\">Číst na %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Nepoužívat RPC pro mangu pro dospělé</string>\n    <string name=\"invalid_token\">Neplatný token: %s</string>\n    <string name=\"show_floating_control_button\">Zobrazit plovoucí ovládací tlačítko</string>\n    <string name=\"unavailable\">Nedostupné</string>\n    <string name=\"manga_restricted_description\">Tato manga není k dispozici od tohoto zdroje. Zkuste ji vyhledat v jiných zdrojích nebo otevřít v prohlížeči pro více informací</string>\n    <string name=\"no_chapters_in_manga\">Tato manga neobsahuje žádné kapitoly</string>\n    <string name=\"chapters_load_failed\">Načítání kapitol selhalo</string>\n    <string name=\"telegram_integration\">Integrace Telegramu</string>\n    <string name=\"test_parser\">Vyzkoušet zdroje mang</string>\n    <string name=\"rename\">Přejmenovat</string>\n    <string name=\"save_filter\">Uložit filtr</string>\n    <string name=\"overwrite\">Nahradit</string>\n    <string name=\"filter_overwrite_confirm\">Filtr pojmenován\\\"%s\\\" již existuje. Chcete ho nahradit?</string>\n    <string name=\"storage_and_network\">Uložiště a síť</string>\n    <string name=\"create_or_restore_backup\">Vytvořit nebo obnovit zálohu</string>\n    <string name=\"data_removal\">Odstranění dat</string>\n    <string name=\"privacy\">Soukromí</string>\n    <string name=\"source_broken_warning\">Tento zdroj byl označen jako rozbitý. Některé funkce nemusí fungovat</string>\n    <string name=\"download_default_directory\">Výchozí adresář pro stahování mangy</string>\n    <string name=\"private_app_directory_warning\">Tento adresář a všechna data v něm budou smazána, pokud odinstalujete aplikaci</string>\n    <string name=\"available_pattern\">%1$s dostupný</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-de/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d Element</item>\n        <item quantity=\"other\">%1$d Elemente</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d neues Kapitel</item>\n        <item quantity=\"other\">%1$d neue Kapitel</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d Kapitel</item>\n        <item quantity=\"other\">%1$d Kapitel</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">Vor %1$d Minute</item>\n        <item quantity=\"other\">Vor %1$d Minuten</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">Vor %1$d Stunde</item>\n        <item quantity=\"other\">Vor %1$d Stunden</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">Vor %1$d Tag</item>\n        <item quantity=\"other\">Vor %1$d Tagen</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">Vor %1$d Monat</item>\n        <item quantity=\"other\">Vor %1$d Monaten</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d Minute</item>\n        <item quantity=\"other\">%1$d Minuten</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d Stunde</item>\n        <item quantity=\"other\">%1$d Stunden</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"remove\">Entfernen</string>\n    <string name=\"theme\">Design</string>\n    <string name=\"pages\">Seiten</string>\n    <string name=\"follow_system\">Wie System</string>\n    <string name=\"dark\">Dunkel</string>\n    <string name=\"light\">Hell</string>\n    <string name=\"filter\">Filter</string>\n    <string name=\"sort_order\">Sortierreihenfolge</string>\n    <string name=\"by_rating\">Bewertung</string>\n    <string name=\"newest\">Neuestes</string>\n    <string name=\"popular\">Beliebt</string>\n    <string name=\"by_name\">Name</string>\n    <string name=\"downloads\">Downloads</string>\n    <string name=\"download_complete\">Heruntergeladen</string>\n    <string name=\"search_manga\">Manga suchen</string>\n    <string name=\"search\">Suchen</string>\n    <string name=\"share_s\">Teilen %s</string>\n    <string name=\"share\">Teilen</string>\n    <string name=\"save\">Speichern</string>\n    <string name=\"add\">Hinzufügen</string>\n    <string name=\"add_new_category\">Neue Kategorie</string>\n    <string name=\"add_to_favourites\">Zu Favoriten hinzufügen</string>\n    <string name=\"you_have_not_favourites_yet\">Noch keine Favoriten</string>\n    <string name=\"read\">Lesen</string>\n    <string name=\"history_is_empty\">Noch kein Verlauf</string>\n    <string name=\"nothing_found\">Nichts gefunden</string>\n    <string name=\"clear_history\">Verlauf löschen</string>\n    <string name=\"try_again\">Erneut versuchen</string>\n    <string name=\"close\">Schließen</string>\n    <string name=\"chapter_d_of_d\">Kapitel %1$d von %2$d</string>\n    <string name=\"remote_sources\">Manga Quellen</string>\n    <string name=\"settings\">Einstellungen</string>\n    <string name=\"list_mode\">Listenmodus</string>\n    <string name=\"grid\">Raster</string>\n    <string name=\"text_clear_updates_feed_prompt\">Den gesamte Aktualisierungsverlauf unwiderruflich löschen\\?</string>\n    <string name=\"no_update_available\">Keine Aktualisierungen verfügbar</string>\n    <string name=\"check_for_updates\">Nach Aktualisierungen suchen</string>\n    <string name=\"track_sources\">Suche nach Aktualisierungen</string>\n    <string name=\"feed_will_update_soon\">Die Feed-Aktualisierung beginnt gleich</string>\n    <string name=\"update\">Aktualisieren</string>\n    <string name=\"updates_feed_cleared\">Gelöscht</string>\n    <string name=\"clear_updates_feed\">Aktualisierungsfeed löschen</string>\n    <string name=\"updates\">Aktualisierungen</string>\n    <string name=\"app_update_available\">Eine neue Version der App ist verfügbar</string>\n    <string name=\"updated\">Aktualisiert</string>\n    <string name=\"preparing_\">Vorbereitung…</string>\n    <string name=\"text_empty_holder_primary\">Hier ist es etwas leer…</string>\n    <string name=\"processing_\">Verarbeiten…</string>\n    <string name=\"manga_downloading_\">Herunterladen…</string>\n    <string name=\"create_shortcut\">Verknüpfung erstellen</string>\n    <string name=\"loading_\">Laden…</string>\n    <string name=\"text_delete_local_manga\">„%s“ Unwiderruflich vom Gerät löschen?</string>\n    <string name=\"_s_deleted_from_local_storage\">„%s“ aus lokalem Speicher gelöscht</string>\n    <string name=\"detailed_list\">Detaillierte Liste</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"chapters\">Kapitel</string>\n    <string name=\"details\">Details</string>\n    <string name=\"network_error\">Netzwerkfehler</string>\n    <string name=\"error_occurred\">Ein Fehler ist aufgetreten</string>\n    <string name=\"history\">Verlauf</string>\n    <string name=\"favourites\">Favoriten</string>\n    <string name=\"local_storage\">Lokaler Speicher</string>\n    <string name=\"manga_save_location\">Download Order</string>\n    <string name=\"text_history_holder_primary\">Die Manga, die du gerade liest, werden hier angezeigt</string>\n    <string name=\"zoom_mode_keep_start\">Am Anfang ausrichten</string>\n    <string name=\"zoom_mode_fit_width\">An Breite anpassen</string>\n    <string name=\"zoom_mode_fit_height\">An Höhe anpassen</string>\n    <string name=\"black_dark_theme_summary\">Spart Energie auf AMOLED-Bildschirmen</string>\n    <string name=\"black_dark_theme\">Reines Schwarz</string>\n    <string name=\"right_to_left\">Von rechts nach links</string>\n    <string name=\"create_category\">Neue Kategorie</string>\n    <string name=\"backup_restore\">Backup und Wiederherstellung</string>\n    <string name=\"data_restored\">Daten wiederhergestellt</string>\n    <string name=\"restore_backup\">Backup wiederherstellen</string>\n    <string name=\"create_backup\">Backup erstellen</string>\n    <string name=\"file_not_found\">Datei nicht gefunden</string>\n    <string name=\"just_now\">Gerade jetzt</string>\n    <string name=\"group\">Gruppe</string>\n    <string name=\"cookies_cleared\">Alle Cookies wurden entfernt</string>\n    <string name=\"clear_cookies\">Cookies löschen</string>\n    <string name=\"default_s\">Standard: %s</string>\n    <string name=\"auth_required\">Melde dich an, um diesen Inhalt anzusehen</string>\n    <string name=\"reverse\">Umkehren</string>\n    <string name=\"check_for_new_chapters\">Nach neuen Kapiteln suchen</string>\n    <string name=\"clear_feed\">Feed löschen</string>\n    <string name=\"sign_in\">Anmelden</string>\n    <string name=\"password_length_hint\">Das Passwort muss mindestens 4 Zeichen lang sein</string>\n    <string name=\"confirm\">Bestätigen</string>\n    <string name=\"protect_application_subtitle\">Gib ein Passwort ein, mit dem die App gestartet werden soll</string>\n    <string name=\"next\">Weiter</string>\n    <string name=\"text_clear_search_history_prompt\">Möchtest du alle letzten Suchanfragen unwiderruflich entfernen\\?</string>\n    <string name=\"read_more\">Mehr erfahren</string>\n    <string name=\"tracker_warning\">Einige Geräte haben ein anderes Systemverhalten, welches womöglich Hintergrundprozesse unterbricht.</string>\n    <string name=\"backup_saved\">Backup gespeichert</string>\n    <string name=\"welcome\">Willkommen</string>\n    <string name=\"remove_category\">Entfernen</string>\n    <string name=\"rotate_screen\">Bildschirm drehen</string>\n    <string name=\"size_s\">Größe: %s</string>\n    <string name=\"new_version_s\">Neue Version: %s</string>\n    <string name=\"search_results\">Suchergebnisse</string>\n    <string name=\"text_feed_holder\">Hier siehst du neue Kapitel der Mangas, welche du liest</string>\n    <string name=\"read_later\">Später lesen</string>\n    <string name=\"favourites_category_empty\">Diese Kategorie ist leer</string>\n    <string name=\"all_favourites\">Alle Favoriten</string>\n    <string name=\"done\">Fertig</string>\n    <string name=\"other_storage\">Sonstiger Speicherort</string>\n    <string name=\"cannot_find_available_storage\">Kein verfügbarer Speicher gefunden</string>\n    <string name=\"not_available\">Nicht verfügbar</string>\n    <string name=\"pages_animation\">Seitenanimation</string>\n    <string name=\"recent_manga\">Neueste Manga</string>\n    <string name=\"manga_shelf\">Regal</string>\n    <string name=\"text_local_holder_secondary\">Speichere etwas aus einem Online-Katalog oder importiere es aus einer Datei.</string>\n    <string name=\"text_local_holder_primary\">Speichere erst etwas</string>\n    <string name=\"text_history_holder_secondary\">Finde etwas zu lesen in der Rubrik „Erkunden“</string>\n    <string name=\"text_search_holder_secondary\">Versuche, die Anfrage umzuformulieren.</string>\n    <string name=\"favourites_categories\">Favoriten-Kategorien</string>\n    <string name=\"vibration\">Vibration</string>\n    <string name=\"light_indicator\">LED-Anzeige</string>\n    <string name=\"notification_sound\">Benachrichtigungston</string>\n    <string name=\"notifications_settings\">Benachrichtigungseinstellungen</string>\n    <string name=\"download\">Herunterladen</string>\n    <string name=\"new_chapters\">Neue Kapitel</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d von %2$d aktiviert</string>\n    <string name=\"notifications\">Benachrichtigungen</string>\n    <string name=\"open_in_browser\">Im Browser öffnen</string>\n    <string name=\"external_storage\">Externer Speicher</string>\n    <string name=\"internal_storage\">Interner Speicher</string>\n    <string name=\"search_history_cleared\">Gelöscht</string>\n    <string name=\"clear_search_history\">Suchverlauf löschen</string>\n    <string name=\"clear_thumbs_cache\">Miniaturansichten Cache löschen</string>\n    <string name=\"error\">Fehler</string>\n    <string name=\"_continue\">Weiter</string>\n    <string name=\"switch_pages\">Seiten wechseln</string>\n    <string name=\"reader_settings\">Leseeinstellungen</string>\n    <string name=\"delete_manga\">Manga löschen</string>\n    <string name=\"search_on_s\">Suche auf %s</string>\n    <string name=\"grid_size\">Rastergröße</string>\n    <string name=\"read_mode\">Lesemodus</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"clear_pages_cache\">Seitencache löschen</string>\n    <string name=\"no_description\">Keine Beschreibung</string>\n    <string name=\"text_file_not_supported\">Wähle entweder eine ZIP- oder CBZ-Datei.</string>\n    <string name=\"operation_not_supported\">Dieser Vorgang wird nicht unterstützt</string>\n    <string name=\"delete\">Löschen</string>\n    <string name=\"_import\">Importieren</string>\n    <string name=\"share_image\">Bild teilen</string>\n    <string name=\"page_saved\">Seite gespeichert</string>\n    <string name=\"save_page\">Seite speichern</string>\n    <string name=\"clear\">Löschen</string>\n    <string name=\"captcha_solve\">Lösen</string>\n    <string name=\"captcha_required\">CAPTCHA erforderlich</string>\n    <string name=\"silent\">Stumm</string>\n    <string name=\"reader_mode_hint\">Die gewählte Konfiguration wird für diesen Manga gespeichert</string>\n    <string name=\"tap_to_try_again\">Tippe, um es erneut zu versuchen</string>\n    <string name=\"today\">Heute</string>\n    <string name=\"long_ago\">Vor langer Zeit</string>\n    <string name=\"yesterday\">Gestern</string>\n    <string name=\"backup_information\">Du kannst ein Backup deines Verlaufs und deiner Favoriten erstellen und wiederherstellen</string>\n    <string name=\"data_restored_with_errors\">Die Daten wurden wiederhergestellt, aber es gibt Fehler</string>\n    <string name=\"data_restored_success\">Alle Daten wiederhergestellt</string>\n    <string name=\"zoom_mode_fit_center\">An Zentrum anpassen</string>\n    <string name=\"scale_mode\">Skalierungsmodus</string>\n    <string name=\"app_version\">Version %s</string>\n    <string name=\"about\">Über</string>\n    <string name=\"passwords_mismatch\">Passwörter stimmen nicht überein</string>\n    <string name=\"repeat_password\">Wiederhole das Passwort</string>\n    <string name=\"protect_application_summary\">Beim Start von Kotatsu nach Passwort fragen</string>\n    <string name=\"protect_application\">App schützen</string>\n    <string name=\"wrong_password\">Falsches Passwort</string>\n    <string name=\"enter_password\">Passwort eingeben</string>\n    <string name=\"dont_check\">Nicht prüfen</string>\n    <string name=\"domain\">Domäne</string>\n    <string name=\"chapter_is_missing\">Das Kapitel fehlt</string>\n    <string name=\"queued\">In Warteschlange</string>\n    <string name=\"about_app_translation\">Übersetzung</string>\n    <string name=\"about_app_translation_summary\">Übersetze die App</string>\n    <string name=\"text_clear_cookies_prompt\">Du wirst von allen Quellen abgemeldet</string>\n    <string name=\"genres\">Genres</string>\n    <string name=\"auth_not_supported_by\">Anmeldung bei %s wird nicht unterstützt</string>\n    <string name=\"auth_complete\">Autorisierung abgeschlossen</string>\n    <string name=\"state_finished\">Beendet</string>\n    <string name=\"state_ongoing\">Fortlaufend</string>\n    <string name=\"system_default\">Standard</string>\n    <string name=\"exclude_nsfw_from_history\">NSFW-Manga vom Verlauf ausschließen</string>\n    <string name=\"show_pages_numbers\">Nummerierte Seiten</string>\n    <string name=\"computing_\">Berechnen…</string>\n    <string name=\"screenshots_allow\">Erlauben</string>\n    <string name=\"screenshots_policy\">Bildschirmfoto-Verhalten</string>\n    <string name=\"screenshots_block_nsfw\">Für NSFW blockieren</string>\n    <string name=\"screenshots_block_all\">Immer blockieren</string>\n    <string name=\"suggestions\">Vorschläge</string>\n    <string name=\"suggestions_enable\">Vorschläge einschalten</string>\n    <string name=\"suggestions_summary\">Schlage Manga basierend auf deinen Vorlieben vor</string>\n    <string name=\"suggestions_info\">Alle Daten werden ausschließlich lokal auf diesem Gerät ausgewertet und nie weiter versendet.</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Keine NSFW-Manga vorschlagen</string>\n    <string name=\"enabled\">Aktiviert</string>\n    <string name=\"text_suggestion_holder\">Beginne Manga zu lesen und du erhältst personalisierte Vorschläge</string>\n    <string name=\"disabled\">Deaktiviert</string>\n    <string name=\"never\">Nie</string>\n    <string name=\"always\">Immer</string>\n    <string name=\"preload_pages\">Seiten vorladen</string>\n    <string name=\"reset_filter\">Filter zurücksetzen</string>\n    <string name=\"only_using_wifi\">Nur über WLAN</string>\n    <string name=\"onboard_text\">Wähle die Sprachen aus, in denen du Mangas lesen möchten. Du kannst das später in den Einstellungen anpassen.</string>\n    <string name=\"logged_in_as\">Angemeldet als %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Verschiedene Sprachen</string>\n    <string name=\"search_chapters\">Kapitel suchen</string>\n    <string name=\"chapters_empty\">Keine Kapitel in diesem Manga</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Aussehen</string>\n    <string name=\"suggestions_updating\">Vorschläge werden aktualisiert</string>\n    <string name=\"suggestions_excluded_genres\">Genres ausschließen</string>\n    <string name=\"suggestions_excluded_genres_summary\">Gebe Genres an, die du nicht in den Vorschlägen sehen möchtest</string>\n    <string name=\"text_delete_local_manga_batch\">Ausgewählte Elemente unwiderruflich vom Gerät löschen\\?</string>\n    <string name=\"removal_completed\">Entfernung abgeschlossen</string>\n    <string name=\"download_slowdown\">Download-Verzögerung</string>\n    <string name=\"local_manga_processing\">Manga-Verarbeitung gespeichert</string>\n    <string name=\"download_slowdown_summary\">Hilft, das Blockieren deiner IP-Adresse zu vermeiden</string>\n    <string name=\"chapters_will_removed_background\">Kapitel werden im Hintergrund entfernt</string>\n    <string name=\"hide\">Ausblenden</string>\n    <string name=\"new_sources_text\">Neue Manga-Quellen sind verfügbar</string>\n    <string name=\"check_new_chapters_title\">Nach neuen Kapiteln suchen und benachrichtigt werden</string>\n    <string name=\"show_notification_new_chapters_on\">Erhalte Benachrichtigungen bei Aktualisierungen der Manga, die du liest</string>\n    <string name=\"notifications_enable\">Benachrichtigungen einschalten</string>\n    <string name=\"empty_favourite_categories\">Keine bevorzugten Kategorien</string>\n    <string name=\"name\">Name</string>\n    <string name=\"edit\">Bearbeiten</string>\n    <string name=\"show_notification_new_chapters_off\">Du wirst keine Benachrichtigungen erhalten, aber neue Kapitel werden in den Listen hervorgehoben</string>\n    <string name=\"edit_category\">Kategorie bearbeiten</string>\n    <string name=\"bookmark_add\">Lesezeichen hinzufügen</string>\n    <string name=\"bookmarks\">Lesezeichen</string>\n    <string name=\"bookmark_removed\">Lesezeichen entfernt</string>\n    <string name=\"removed_from_history\">Aus dem Verlauf entfernt</string>\n    <string name=\"bookmark_remove\">Lesezeichen entfernen</string>\n    <string name=\"bookmark_added\">Lesezeichen hinzugefügt</string>\n    <string name=\"undo\">Rückgängig</string>\n    <string name=\"dns_over_https\">DNS über HTTPS</string>\n    <string name=\"default_mode\">Standard-Modus</string>\n    <string name=\"detect_reader_mode\">Automatische Erkennung des Modus</string>\n    <string name=\"detect_reader_mode_summary\">Automatisch erkennen, ob ein Manga ein Webtoon ist</string>\n    <string name=\"disable_battery_optimization\">Akkuoptimierung deaktivieren</string>\n    <string name=\"disable_battery_optimization_summary\">Hilft bei der Hintergrundaktualisierung</string>\n    <string name=\"send\">Senden</string>\n    <string name=\"crash_text\">Etwas ist schief gelaufen. Bitte sende einen Fehlerbericht an die Entwickler, um uns bei der Behebung zu helfen.</string>\n    <string name=\"disable_all\">Alle deaktivieren</string>\n    <string name=\"use_fingerprint\">Fingerabdruck verwenden, falls verfügbar</string>\n    <string name=\"appwidget_shelf_description\">Manga aus deinen Favoriten</string>\n    <string name=\"appwidget_recent_description\">Deine kürzlich gelesenen Manga</string>\n    <string name=\"report\">Melden</string>\n    <string name=\"tracking\">Externe Plattformen (Tracking)</string>\n    <string name=\"logout\">Abmelden</string>\n    <string name=\"status_planned\">Geplant</string>\n    <string name=\"status_on_hold\">In der Warteschleife</string>\n    <string name=\"show_reading_indicators\">Indikatoren für den Lesefortschritt anzeigen</string>\n    <string name=\"show_all\">Alle anzeigen</string>\n    <string name=\"show_reading_indicators_summary\">Zeige gelesenen Prozentsatz in Verlauf und Favoriten</string>\n    <string name=\"clear_cookies_summary\">Kann bei einigen Probleme helfen. Alle Anmeldungen werden ungültig</string>\n    <string name=\"status_completed\">Abgeschlossen</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Als NSFW markierte Manga werden nicht in den Verlauf aufgenommen und Ihr Fortschritt wird nicht gespeichert</string>\n    <string name=\"data_deletion\">Datenlöschung</string>\n    <string name=\"invalid_domain_message\">Ungültige Domain</string>\n    <string name=\"status_reading\">Lesen</string>\n    <string name=\"select_range\">Bereich auswählen</string>\n    <string name=\"not_found_404\">Inhalt nicht gefunden oder entfernt</string>\n    <string name=\"manga_error_description_pattern\">Fehlerdetails:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Versuche, &lt;a href=%2$s&gt;den Manga in einem Webbrowser zu öffnen&lt;/a&gt;, um sicherzustellen, dass er bei seiner Quelle verfügbar ist&lt;br&gt;2. Stelle sicher, dass du die &lt;a href=kotatsu://about&gt;neueste Kotatsu-Version&lt;/a&gt;&lt;br&gt;3 benutzt. Wenn er verfügbar ist, sende einen Fehlerbericht an die Entwickler.</string>\n    <string name=\"no_bookmarks_yet\">Noch keine Lesezeichen</string>\n    <string name=\"no_manga_sources\">Keine Manga-Quellen</string>\n    <string name=\"no_manga_sources_text\">Manga-Quellen aktivieren, um Manga online zu lesen</string>\n    <string name=\"exit_confirmation\">Beenden bestätigen</string>\n    <string name=\"saved_manga\">Gespeicherte Manga</string>\n    <string name=\"pages_cache\">Seiten-Cache</string>\n    <string name=\"other_cache\">Anderer Cache</string>\n    <string name=\"storage_usage\">Speichernutzung</string>\n    <string name=\"available\">Verfügbar</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"removed_from_favourites\">Aus den Favoriten entfernt</string>\n    <string name=\"options\">Optionen</string>\n    <string name=\"automatic_scroll\">Automatisches Blättern</string>\n    <string name=\"comics_archive\">Comics-Archiv</string>\n    <string name=\"reader_info_pattern\">Kap. %1$d/%2$d S. %3$d/%4$d</string>\n    <string name=\"canceled\">Abgebrochen</string>\n    <string name=\"account_already_exists\">Konto existiert bereits</string>\n    <string name=\"back\">Zurück</string>\n    <string name=\"sync\">Synchronisierung</string>\n    <string name=\"sync_title\">Synchronisiere deine Daten</string>\n    <string name=\"email_enter_hint\">Gib deine E-Mail-Adresse ein, um fortzufahren</string>\n    <string name=\"clear_all_history\">Gesamten Verlauf löschen</string>\n    <string name=\"last_2_hours\">Letzte 2 Stunden</string>\n    <string name=\"history_cleared\">Verlauf gelöscht</string>\n    <string name=\"manage\">Verwalten</string>\n    <string name=\"no_bookmarks_summary\">Du kannst beim Lesen von Mangas Lesezeichen erstellen</string>\n    <string name=\"bookmarks_removed\">Lesezeichen entfernt</string>\n    <string name=\"random\">Zufällig</string>\n    <string name=\"categories_delete_confirm\">Bist du sicher, dass du die ausgewählten Lieblingskategorien löschen möchtest? \\nAlle darin enthaltenen Manga gehen verloren und das kann nicht rückgängig gemacht werden.</string>\n    <string name=\"reorder\">Neu anordnen</string>\n    <string name=\"empty\">Leer</string>\n    <string name=\"explore\">Erkunden</string>\n    <string name=\"confirm_exit\">Drücke zum Beenden erneut Zurück</string>\n    <string name=\"exit_confirmation_summary\">Drücke zweimal Zurück, um die App zu beenden</string>\n    <string name=\"feed\">Feed</string>\n    <string name=\"incognito_mode\">Inkognito-Modus</string>\n    <string name=\"no_chapters\">Keine Kapitel</string>\n    <string name=\"reader_info_bar\">Zeige Informationsleiste beim Lesen</string>\n    <string name=\"folder_with_images\">Ordner mit Bildern</string>\n    <string name=\"importing_manga\">Importiere Manga</string>\n    <string name=\"import_completed\">Import abgeschlossen</string>\n    <string name=\"import_completed_hint\">Du kannst die Originaldatei aus dem Speicher löschen, um Platz zu sparen</string>\n    <string name=\"import_will_start_soon\">Import wird bald beginnen</string>\n    <string name=\"server_error\">Serverseitiger Fehler (%1$d). Bitte versuche es später erneut</string>\n    <string name=\"compact\">Kompakt</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"network_unavailable_hint\">Schalte WLAN oder mobile Daten ein, um Manga online zu lesen</string>\n    <string name=\"clear_new_chapters_counters\">Auch Informationen über neue Kapitel löschen</string>\n    <string name=\"text_unsaved_changes_prompt\">Ungespeicherte Änderungen speichern oder verwerfen\\?</string>\n    <string name=\"discard\">Verwerfen</string>\n    <string name=\"reset\">Zurücksetzen</string>\n    <string name=\"brightness\">Helligkeit</string>\n    <string name=\"color_correction\">Farbkorrektur</string>\n    <string name=\"error_no_space_left\">Kein Platz übrig auf dem Gerät</string>\n    <string name=\"network_unavailable\">Netzwerk ist nicht verfügbar</string>\n    <string name=\"reader_control_ltr\">Ergonomische Lesekontrolle</string>\n    <string name=\"reader_control_ltr_summary\">Tippe auf den rechten Rand oder drücke die rechte Taste, um immer zur nächsten Seite zu wechseln.</string>\n    <string name=\"reader_slider\">Zeige Seitenwechsel-Schieber</string>\n    <string name=\"source_disabled\">Quelle deaktiviert</string>\n    <string name=\"prefetch_content\">Inhalte vorladen</string>\n    <string name=\"mark_as_current\">Als aktuell markieren</string>\n    <string name=\"webtoon_zoom\">Webtoon-Zoom</string>\n    <string name=\"share_logs\">Protokolle teilen</string>\n    <string name=\"enable_logging\">Protokollierung aktivieren</string>\n    <string name=\"language\">Sprache</string>\n    <string name=\"enable_logging_summary\">Einige Aktionen zu Debug-Zwecken aufzeichnen. Aktiviere dies nicht, wenn Du dir nicht sicher bist, was du tust.</string>\n    <string name=\"history_shortcuts\">Zeige Verknüpfungen zu aktuellen Manga</string>\n    <string name=\"history_shortcuts_summary\">Neueste Manga durch langes Drücken auf das Anwendungssymbol verfügbar machen</string>\n    <string name=\"show_suspicious_content\">Verdächtige Inhalte anzeigen</string>\n    <string name=\"status_dropped\">Abgebrochen</string>\n    <string name=\"color_theme\">Farbschema</string>\n    <string name=\"theme_name_dynamic\">Dynamisch</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"nothing_here\">Hier ist nichts</string>\n    <string name=\"services\">Dienste</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"scrobbling_empty_hint\">Um deinen Lesefortschritt zu tracken, wähle Menü → Lesefortschritt tracken auf dem Manga Details Bildschirm.</string>\n    <string name=\"find_similar\">Ähnliche finden</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"allow_unstable_updates_summary\">Benachrichtigungen über instabile Versionen erhalten</string>\n    <string name=\"allow_unstable_updates\">Erlaube instabile Updates</string>\n    <string name=\"got_it\">Alles klar</string>\n    <string name=\"sources_reorder_tip\">Drücke und halte eine Quelle, um diese umzusortieren</string>\n    <string name=\"settings_apply_restart_required\">Bitte starte die App neu, um die Änderungen anzuwenden</string>\n    <string name=\"comics_archive_import_description\">Du kannst eine oder mehrere .cbz oder .zip Dateien auswählen, jede Datei wird als einzelner Manga erkannt.</string>\n    <string name=\"folder_with_images_import_description\">Du kannst einen Ordner mit Archiven oder Bildern auswählen. Jedes Archiv (oder Unterverzeichnis) wird als ein Kapitel erkannt.</string>\n    <string name=\"speed\">Geschwindigkeit</string>\n    <string name=\"show_on_shelf\">Im Regal anzeigen</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"show_in_grid_view\">In Raster-Ansicht anzeigen</string>\n    <string name=\"server_address\">Serveradresse</string>\n    <string name=\"mirror_switching\">Wähle Mirror automatisch</string>\n    <string name=\"download_started\">Download gestartet</string>\n    <string name=\"sync_settings\">Synchronisationseinstellungen</string>\n    <string name=\"sync_host_description\">Du kannst einen Standard- oder einen selbst gehosteten Synchronisations-Server verwenden. Ändere diese Einstellungen nicht, wenn du dich nicht auskennst.</string>\n    <string name=\"ignore_ssl_errors\">Ignoriere SSL Errors</string>\n    <string name=\"mirror_switching_summary\">Automatischer Domain-Wechsel für Manga-Quellen, falls Spiegelserver verfügbar sind</string>\n    <string name=\"status_re_reading\">Erneut Lesen</string>\n    <string name=\"sync_auth_hint\">Du kannst dich mit einem bestehenden Account anmelden oder einen neuen erstellen</string>\n    <string name=\"user_agent\">UserAgent-Kopfzeile</string>\n    <string name=\"enable\">Aktivieren</string>\n    <string name=\"no_thanks\">Nein danke</string>\n    <string name=\"pause\">Pausieren</string>\n    <string name=\"resume\">Fortsetzen</string>\n    <string name=\"paused\">Pausiert</string>\n    <string name=\"remove_completed\">Entfernung abgeschlossen</string>\n    <string name=\"cancel_all\">Alle abbrechen</string>\n    <string name=\"downloads_wifi_only\">Nur über Wi-Fi herunterladen</string>\n    <string name=\"downloads_wifi_only_summary\">Beende das Herunterladen beim Wechsel zu einem Mobilfunknetz</string>\n    <string name=\"suggestions_notifications_summary\">Zeige ab und zu Benachrichtigungen mit Manga-Vorschlägen</string>\n    <string name=\"downloads_removed\">Downloads wurden entfernt</string>\n    <string name=\"suggestion_manga\">Vorschlag: %s</string>\n    <string name=\"more\">Mehr</string>\n    <string name=\"cancel_all_downloads_confirm\">Alle Downloads werden abgebrochen, teilweise heruntergeladene Dateien gehen verloren</string>\n    <string name=\"remove_completed_downloads_confirm\">Dein Downloads-Verlauf wird unwiderruflich gelöscht</string>\n    <string name=\"text_downloads_list_holder\">Du hast keine Downloads</string>\n    <string name=\"downloads_resumed\">Downloads wurden fortgesetzt</string>\n    <string name=\"downloads_paused\">Downloads wurden pausiert</string>\n    <string name=\"downloads_cancelled\">Downloads wurden abgebrochen</string>\n    <string name=\"suggestions_enable_prompt\">Willst du personalisierte Manga-Vorschläge erhalten\\?</string>\n    <string name=\"web_view_unavailable\">WebView nicht verfügbar: Bitte prüfen, ob WebView installiert ist</string>\n    <string name=\"address\">Adresse</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"invalid_value_message\">Ungültiger Wert</string>\n    <string name=\"type\">Typ</string>\n    <string name=\"downloaded\">Heruntergeladen</string>\n    <string name=\"images_proxy_title\">Bildoptimierungsproxy</string>\n    <string name=\"username\">Benutzername</string>\n    <string name=\"password\">Passwort</string>\n    <string name=\"authorization_optional\">Berechtigung (optional)</string>\n    <string name=\"invalid_port_number\">Ungültige Portnummer</string>\n    <string name=\"network\">Netzwerk</string>\n    <string name=\"data_and_privacy\">Daten und Datenschutz</string>\n    <string name=\"restore_summary\">Zuvor erstellte Sicherung wiederherstellen</string>\n    <string name=\"reader_info_bar_summary\">Die aktuelle Uhrzeit und den Lesefortschritt oben auf dem Bildschirm anzeigen</string>\n    <string name=\"show_pages_numbers_summary\">Seitenzahlen in der unteren Ecke anzeigen</string>\n    <string name=\"download_option_whole_manga\">Das ganze Manga</string>\n    <string name=\"download_option_first_n_chapters\">Erste %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Nächste ungelesene %s</string>\n    <string name=\"download_option_all_unread\">Alle ungelesenen Kapitel</string>\n    <string name=\"download_option_all_unread_b\">Alle ungelesenen Kapitel (%s)</string>\n    <string name=\"download_option_manual_selection\">Kapitel manuell auswählen</string>\n    <string name=\"description\">Beschreibung</string>\n    <string name=\"this_month\">Dieser Monat</string>\n    <string name=\"voice_search\">Sprachsuche</string>\n    <string name=\"invert_colors\">Farben umkehren</string>\n    <string name=\"pick_custom_directory\">Eigenes Verzeichnis wählen</string>\n    <string name=\"no_access_to_file\">Auf diese Datei oder dieses Verzeichnis kann nicht zugegriffen werden</string>\n    <string name=\"local_manga_directories\">Lokale Manga-Verzeichnisse</string>\n    <string name=\"download_option_all_chapters\">Alle Kapitel mit Übersetzung %s</string>\n    <string name=\"color_light\">Hell</string>\n    <string name=\"color_dark\">Dunkel</string>\n    <string name=\"color_white\">Weiß</string>\n    <string name=\"color_black\">Schwarz</string>\n    <string name=\"background\">Hintergrund</string>\n    <string name=\"data_not_restored\">Daten wurden nicht wiederhergestellt</string>\n    <string name=\"data_not_restored_text\">Bitte sicherstellen, dass die richtige Sicherungsdatei ausgewählt wurde</string>\n    <string name=\"search_hint\">Mangatitel, Genre oder Quellname eingeben</string>\n    <string name=\"progress\">Fortschritt</string>\n    <string name=\"order_added\">Hinzugefügt</string>\n    <string name=\"suggestions_wifi_only_summary\">Keine Vorschläge über getaktete Netzverbindungen aktualisieren</string>\n    <string name=\"tracker_wifi_only_summary\">Nicht über getaktete Netzverbindungen auf neue Kapitel prüfen</string>\n    <string name=\"manage_categories\">Kategorien verwalten</string>\n    <string name=\"show\">Anzeigen</string>\n    <string name=\"images_procy_description\">Wenn möglich bitte den Dienst wsrv.nl nutzen, um den Datenverkehr zu reduzieren und das Laden von Bildern zu beschleunigen</string>\n    <string name=\"clear_network_cache\">Netzwerk-Cache löschen</string>\n    <string name=\"captcha_required_summary\">%s erfordert ein Captcha, das gelöst werden muss, um richtig zu funktionieren</string>\n    <string name=\"languages\">Sprachen</string>\n    <string name=\"unknown\">Unbekannt</string>\n    <string name=\"in_progress\">In Arbeit</string>\n    <string name=\"related_manga\">Verwandte Manga</string>\n    <string name=\"clear_source_cookies_summary\">Cookies nur für bestimmte Domain löschen. In den meisten Fällen wird die Genehmigung ungültig</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"manage_sources\">Quellen verwalten</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_other\">Sonstige</string>\n    <string name=\"sources_catalog\">Quellenkatalog</string>\n    <string name=\"source_enabled\">Quelle aktiviert</string>\n    <string name=\"keep_screen_on\">Bildschirm eingeschaltet lassen</string>\n    <string name=\"lock_screen_rotation\">Bildschirm-Ausrichtung sperren</string>\n    <string name=\"manga_list\">Manga liste</string>\n    <string name=\"disable_nsfw\">NSFW deaktivieren</string>\n    <string name=\"too_many_requests_message\">Zu viele Anfragen. Probier es später erneut</string>\n    <string name=\"items_limit_exceeded\">Es können keine weiteren Elemente hinzugefügt werden</string>\n    <string name=\"zoom_out\">Herauszoomen</string>\n    <string name=\"reader_zoom_buttons\">Zoom - Schaltflächen anzeigen</string>\n    <string name=\"periodic_backups\">Periodische Backups</string>\n    <string name=\"backup_frequency\">Häufigkeit der Backup-Erstellung</string>\n    <string name=\"error_multiple_genres_not_supported\">Filtern nach mehreren Genres wird von dieser Manga-Quelle nicht unterstützt</string>\n    <string name=\"webtoon_zoom_summary\">Zoom-Geste in Webtoon-Modus erlauben</string>\n    <string name=\"available_d\">Verfügbar: %1$d</string>\n    <string name=\"on_device\">Auf dem Gerät</string>\n    <string name=\"directories\">Verzeichnisse</string>\n    <string name=\"to_top\">Nach oben</string>\n    <string name=\"moved_to_top\">Nach oben verschoben</string>\n    <string name=\"state_paused\">Pausiert</string>\n    <string name=\"zoom_in\">Hereinzoomen</string>\n    <string name=\"reader_zoom_buttons_summary\">Ob die Zoom - Schaltflächen in der unteren rechten Ecke angezeigt werden sollen</string>\n    <string name=\"reader_optimize\">Speicherverbrauch reduzieren (beta)</string>\n    <string name=\"reader_optimize_summary\">Qualität von nicht sichtbaren Seiten verringern, um den Speicherverbrauch zu reduzieren</string>\n    <string name=\"state\">Zustand</string>\n    <string name=\"error_multiple_states_not_supported\">Filtern nach mehreren Zuständen wird von dieser Manga-Quelle nicht unterstützt</string>\n    <string name=\"error_search_not_supported\">Die Suchfunktion wird von dieser Manga-Quelle nicht unterstützt</string>\n    <string name=\"enhanced_colors\">32-bit Farbmodus</string>\n    <string name=\"suggest_new_sources\">Neue Quellen nach einem Update vorschlagen</string>\n    <string name=\"by_relevance\">Relevanz</string>\n    <string name=\"categories\">Kategorien</string>\n    <string name=\"frequency_every_day\">Täglich</string>\n    <string name=\"frequency_every_2_days\">Alle 2 Tage</string>\n    <string name=\"frequency_twice_per_month\">Zweimal pro Monat</string>\n    <string name=\"frequency_once_per_week\">Einmal pro Woche</string>\n    <string name=\"frequency_once_per_month\">Einmal pro Monat</string>\n    <string name=\"periodic_backups_enable\">Periodische Backups aktivieren</string>\n    <string name=\"backups_output_directory\">Speicher-Verzeichnis für Backups</string>\n    <string name=\"last_successful_backup\">Letztes erfolgreiches Backup: %s</string>\n    <string name=\"state_upcoming\">Erwartet</string>\n    <string name=\"by_name_reverse\">Name rückwärts</string>\n    <string name=\"advanced\">Erweitert</string>\n    <string name=\"content_type_comics\">Comics</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"no_manga_sources_found\">Keine verfügbaren Manga-Quellen für deinen Suchbegriff gefunden</string>\n    <string name=\"welcome_text\">Bitte wähle aus, welche Inhalte-Quellen du aktivieren möchtest. Dies kannst du auch später in den Einstellungen konfigurieren</string>\n    <string name=\"sync_auth\">Melde dich beim Sync - Account an</string>\n    <string name=\"downloads_settings_info\">Du kannst die Download-Verlangsamung für jede Manga-Quelle einzeln in den Quelleneinstellungen aktivieren, wenn du Probleme mit der serverseitigen Blockierung hast</string>\n    <string name=\"skip\">Überspringen</string>\n    <string name=\"restore\">Wiederherstellen</string>\n    <string name=\"backup_date_\">Letzte Sicherung: %s</string>\n    <string name=\"content_rating\">Einstufung (Inhalt)</string>\n    <string name=\"genres_exclude\">Genres ausschließen</string>\n    <string name=\"rating_safe\">Sicher</string>\n    <string name=\"rating_suggestive\">Suggestiv</string>\n    <string name=\"rating_adult\">Erwachsene</string>\n    <string name=\"online_variant\">Online-Variante</string>\n    <string name=\"keep_screen_on_summary\">Bildschirm nicht ausschalten, während du Manga liest</string>\n    <string name=\"default_tab\">Standard-Reiter</string>\n    <string name=\"related_manga_summary\">Zeige eine Liste ähnlicher Manga. Diese kann in einigen Fällen ungenau sein oder fehlen</string>\n    <string name=\"grayscale\">Graustufen</string>\n    <string name=\"globally\">Global</string>\n    <string name=\"this_manga\">Dieses Manga</string>\n    <string name=\"color_correction_apply_text\">Diese Einstellungen können global oder nur auf das aktuelle Manga angewendet werden. Werden sie global angewendet, bleiben individuelle Einstellungen erhalten.</string>\n    <string name=\"apply\">Anwenden</string>\n    <string name=\"manual\">Anleitung</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"error_filter_states_genre_not_supported\">Die Filterung nach Genre und Status wird von dieser Quelle nicht unterstützt</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Die Filterung nach Genre und Lokalisation wird von dieser Quelle nicht unterstützt</string>\n    <string name=\"error_corrupted_file\">Es wurden ungültige Daten zurückgegeben oder die Datei ist beschädigt</string>\n    <string name=\"genres_search_hint\">Beginne den Genre-Namen einzutippen</string>\n    <string name=\"no_manga_sources_catalog_text\">In diesem Abschnitt sind keine Quellen verfügbar, oder es wurde bereits alles hinzugefügt.\n\\nBleibe dran für neue Quellen</string>\n    <string name=\"mark_as_completed\">Als fertig markieren</string>\n    <string name=\"mark_as_completed_prompt\">Ausgewählte Manga als vollständig gelesen markieren?\n\\n\n\\nAchtung: Der aktuelle Lesefortschritt geht verloren.</string>\n    <string name=\"main_screen_sections\">Hauptbildschirm - Abschnitte</string>\n    <string name=\"disable_nsfw_summary\">Deaktiviere NSFW-Quellen und verstecke 18+ Manga aus der Liste, wenn möglich</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Könnte dir damit helfen, den Download zu starten, wenn du Probleme damit hast</string>\n    <string name=\"state_abandoned\">Verworfen</string>\n    <string name=\"enhanced_colors_summary\">Reduziert das Banding (harte Farbverläufe), kann aber die Leistung beeinträchtigen</string>\n    <string name=\"suggest_new_sources_summary\">Aufforderung zur Aktivierung neu hinzugefügter Quellen nach Aktualisierung der Anwendung</string>\n    <string name=\"list_options\">Listenoptionen</string>\n    <string name=\"volume_\">Speicher %d</string>\n    <string name=\"volume_unknown\">Unbekannte Speicher Volumen</string>\n    <string name=\"email_password_enter_hint\">Gebe deine E-Mail-Adresse und Passwort an, um fortzufahren</string>\n    <string name=\"incognito_mode_hint\">Deine Lese fortschritt, wird nicht gespeichert</string>\n    <string name=\"vertical\">Vertikal</string>\n    <string name=\"category_hidden_done\">Diese Kategorie wurde vom Hauptbildschirm ausgeblendet und ist verfügbar unter: Menü → Kategorien verwalten</string>\n    <string name=\"show_menu\">Menü anzeigen</string>\n    <string name=\"toggle_ui\">UI anzeigen/ausblenden</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"prev_chapter\">Vorheriges Kapitel</string>\n    <string name=\"next_page\">Nächste Seite</string>\n    <string name=\"reader_actions\">Leseraktionen</string>\n    <string name=\"reader_actions_summary\">Einstellungen für berührbare Bildschirmbereiche</string>\n    <string name=\"switch_pages_volume_buttons\">Aktiviere Lautstärke Knöpfe</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Lautstärkeknöpfe zum Seitenwechsel benutzen</string>\n    <string name=\"tap_action\">Tippen Aktion</string>\n    <string name=\"long_tap_action\">Langtippen Aktion</string>\n    <string name=\"none\">Keine</string>\n    <string name=\"config_reset_confirm\">Einstellungen auf Standardwerte zurücksetzen? Diese Aktion kann nicht rückgängig gemacht werden.</string>\n    <string name=\"use_two_pages_landscape\">Zwei-Seiten-Layout im Querformat benutzen (experimentell)</string>\n    <string name=\"next_chapter\">Nächstes Kapitel</string>\n    <string name=\"prev_page\">Vorherige Seite</string>\n    <string name=\"last_read\">Zuletzt gelesen</string>\n    <string name=\"automatic\">Automatisch</string>\n    <string name=\"show_labels_in_navbar\">Beschriftungen in der Navigationsleiste anzeigen</string>\n    <string name=\"pages_saving\">Seiten werden gespeichert</string>\n    <string name=\"ask_for_dest_dir_every_time\">Jedes Mal nach dem Zielverzeichnis fragen</string>\n    <string name=\"default_page_save_dir\">Standard Speicherordner für Seiten</string>\n    <string name=\"remove_from_history\">Aus der Historie entfernen</string>\n    <string name=\"preferred_download_format\">Bevorzugtes Download-Format</string>\n    <string name=\"fullscreen_mode\">Vollbildmodus</string>\n    <string name=\"reader_fullscreen_summary\">Systemstatus- und Navigationsleisten ausblenden</string>\n    <string name=\"single_cbz_file\">Eine CBZ-Datei</string>\n    <string name=\"multiple_cbz_files\">Mehrere CBZ-Dateien</string>\n    <string name=\"suggestions_unavailable_text\">Die Vorschlagsfunktion ist deaktiviert</string>\n    <string name=\"check_for_new_chapters_disabled\">Die Überprüfung auf neue Kapitel ist deaktiviert</string>\n    <string name=\"reading_time_estimation\">Geschätzte Lesezeit anzeigen</string>\n    <string name=\"reading_time_estimation_summary\">Die geschätzte Zeitangabe kann ungenau sein</string>\n    <string name=\"location\">Speicherort</string>\n    <string name=\"default_webtoon_zoom_out\">Standard Zoom Out für Webtoons</string>\n    <string name=\"other_manga\">Andere Mangas</string>\n    <string name=\"less_than_minute\">Weniger als eine Minute</string>\n    <string name=\"migration_completed\">Migrierung abgeschlossen</string>\n    <string name=\"chapters_grid_view\">Rasteransicht</string>\n    <string name=\"reading_stats\">Lesestatistiken</string>\n    <string name=\"statistics\">Statistiken</string>\n    <string name=\"clear_stats\">Statistiken löschen</string>\n    <string name=\"stats_cleared\">Statistiken gelöscht</string>\n    <string name=\"clear_stats_confirm\">Willst du wirklich alle Statistiken löschen? Die Prozedur kann nicht rückgängig gemacht werden.</string>\n    <string name=\"all_time\">Immer</string>\n    <string name=\"day\">Tag</string>\n    <string name=\"alternatives\">Alternativen</string>\n    <string name=\"migrate\">Migrieren</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" aus \\\"%2$s\\\" wird mit \\\"%3$s\\\" aus \\\"%4$s\\\" in deinem Verlauf und Favoriten (sofern vorhanden) ersetzt</string>\n    <string name=\"manga_migration\">Manga Migrierung</string>\n    <string name=\"delete_read_chapters_auto\">Lösche gelesene Kapitel automatisch</string>\n    <string name=\"runs_on_app_start\">Wird ausgeführt wenn die App gestartet wird</string>\n    <string name=\"week\">Woche</string>\n    <string name=\"month\">Monat</string>\n    <string name=\"empty_stats_text\">Es sind keine Statistiken für die ausgewählte Zeitspanne vorhanden</string>\n    <string name=\"three_months\">Drei Monate</string>\n    <string name=\"pages_read_s\">Gelesene Seiten:%s</string>\n    <string name=\"split_by_translations\">Aufgeteilt nach Übersetzungen</string>\n    <string name=\"split_by_translations_summary\">Zeige Kapitel mit unterschiedlichen Übersetzungen separiert anstatt in einer Liste</string>\n    <string name=\"order_oldest\">Älteste</string>\n    <string name=\"long_ago_read\">vor einer langen Zeit gelesen</string>\n    <string name=\"unread\">Ungelesen</string>\n    <string name=\"delete_read_chapters\">Gelesene Kapitel löschen</string>\n    <string name=\"no_chapters_deleted\">Keine Kapitel wurden gelöscht</string>\n    <string name=\"delete_read_chapters_summary\">Lösche schon gelesene Kapitel um Speicher freizugeben</string>\n    <string name=\"delete_read_chapters_prompt\">Die Aktion wird alle als gelesen markierte Kapitel dauerhaft von deinem Speicher löschen. Du kannst sie später neu herunterladen, aber die importierten Kapitel können trotzdem verloren sein</string>\n    <string name=\"chapters_deleted_pattern\">%1$s entfernt, %2$s gelöscht</string>\n    <string name=\"enable_source\">Quelle aktivieren</string>\n    <string name=\"unsupported_source\">Diese Manga-Quelle wird nicht unterstützt</string>\n    <string name=\"show_pages_thumbs\">Seitenvorschau anzeigen</string>\n    <string name=\"unsupported_backup_message\">Bitte wähle eine richtige Kotatsu-Backup-Datei aus</string>\n    <string name=\"show_pages_thumbs_summary\">Registerkarte \\\"Seiten\\\" auf dem Detailbildschirm aktivieren</string>\n    <string name=\"error_no_data_received\">Keine Daten vom Server erhalten</string>\n    <string name=\"last_used\">Zuletzt verwendet</string>\n    <string name=\"hours_short\">%d h</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"invalid_server_address_message\">Ungültige Server-Addresse</string>\n    <string name=\"pages_saved\">Seiten wurden gespeichert</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Es gibt keine Manga, die deinen Filtern entsprechen.</string>\n    <string name=\"too_many_requests_message_retry\">Zu viele Anfragen. Versuche es erneut in %s</string>\n    <string name=\"less_frequently\">Seltener</string>\n    <string name=\"hours_minutes_short\">%1$d h %2$d m</string>\n    <string name=\"show_updated\">Zeige aktualisierte</string>\n    <string name=\"webtoon_gaps\">Lücken im Webtoon Modus</string>\n    <string name=\"webtoon_gaps_summary\">Vertikale Abstände zwischen den Seiten im Webtoon-Modus anzeigen</string>\n    <string name=\"more_frequently\">Häufiger</string>\n    <string name=\"retry\">Erneut versuchen</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"missing_storage_permission\">Es gibt keine Berechtigung für den Zugriff auf Manga auf externem Speicher</string>\n    <string name=\"frequency_of_check\">Häufigkeit der Überprüfung</string>\n    <string name=\"search_suggestions\">Such Vorschläge</string>\n    <string name=\"fix\">reparieren</string>\n    <string name=\"recent_queries\">Kürzliche Suchen</string>\n    <string name=\"suggested_queries\">Vorgeschlagene Suchen</string>\n    <string name=\"authors\">Autoren</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"blocked_by_server_message\">Du wurdest vom Server blockiert. Versuche, eine andere Netzwerkverbindung zu benutzen (VPN, Proxy, etc.)</string>\n    <string name=\"disable\">Deaktivieren</string>\n    <string name=\"sources_disabled\">Quellen deaktiviert</string>\n    <string name=\"disable_nsfw_notifications\">Deaktiviere NSFW Benachrichtigungen</string>\n    <string name=\"disable_nsfw_notifications_summary\">Zeige keine Benachrichtigungen bezüglich NSFW Manga updates</string>\n    <string name=\"_new\">Neues</string>\n    <string name=\"all_languages\">Alle Sprachen</string>\n    <string name=\"screenshots_block_incognito\">Blockieren, wenn im Inkognito-Modus</string>\n    <string name=\"image_server\">Bevorzugter Server</string>\n    <string name=\"pin\">Pinnen</string>\n    <string name=\"unpin\">Entpinnen</string>\n    <string name=\"source_pinned\">Quelle gepinnt</string>\n    <string name=\"percent_read\">Prozent gelesen</string>\n    <string name=\"percent_left\">Prozent übrig</string>\n    <string name=\"chapters_read\">Kapitel gelesen</string>\n    <string name=\"chapters_left\">Kapitel übrig</string>\n    <string name=\"plugin_incompatible\">Inkompatibles Plugin oder Interner Fehler. Stelle sicher, dass du die letzte Version von Kotatsu und die des Plugins verwendest</string>\n    <string name=\"reader_navigation_inverted\">Steuerung umkehren</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-el/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d μέρα πριν</item>\n        <item quantity=\"other\">%1$d μέρες πριν</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d στοιχείο</item>\n        <item quantity=\"other\">%1$d στοιχεία</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d νέο κεφάλαιο</item>\n        <item quantity=\"other\">%1$d νέα κεφάλαια</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d ώρα πριν</item>\n        <item quantity=\"other\">%1$d ώρες πριν</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d κεφάλαιο</item>\n        <item quantity=\"other\">%1$d κεφάλαια</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d λεπτό πριν</item>\n        <item quantity=\"other\">%1$d λεπτά πριν</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d μήνας πριν</item>\n        <item quantity=\"other\">%1$d μήνες πριν</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-el/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"local_storage\">Τοπικός χώρος αποθήκευσης</string>\n    <string name=\"favourites\">Αγαπημένα</string>\n    <string name=\"history\">Ιστορικό</string>\n    <string name=\"error_occurred\">Προέκυψε σφάλμα</string>\n    <string name=\"try_again\">Επανάληψη</string>\n    <string name=\"grid\">Πλέγμα</string>\n    <string name=\"list_mode\">Εμφάνιση ως λίστα</string>\n    <string name=\"settings\">Ρυθμίσεις</string>\n    <string name=\"remote_sources\">Πηγές manga</string>\n    <string name=\"computing_\">Επεξεργασία…</string>\n    <string name=\"close\">Κλείσιμο</string>\n    <string name=\"clear_history\">Εκκαθάριση ιστορικού</string>\n    <string name=\"nothing_found\">Δεν βρέθηκε τίποτα</string>\n    <string name=\"history_is_empty\">Κενό ιστορικό</string>\n    <string name=\"read\">Διάβασε</string>\n    <string name=\"add_to_favourites\">Προσθήκη στα αγαπημένα</string>\n    <string name=\"add_new_category\">Νέα κατηγορία</string>\n    <string name=\"save\">Αποθήκευση</string>\n    <string name=\"share\">Κοινοποιήση</string>\n    <string name=\"create_shortcut\">Δημιουργία συντόμευσης…</string>\n    <string name=\"share_s\">Κοινοποίηση %s</string>\n    <string name=\"search\">Αναζήτηση</string>\n    <string name=\"search_manga\">Αναζήτηση manga</string>\n    <string name=\"manga_downloading_\">Λήψη…</string>\n    <string name=\"download_complete\">Κατεβασμένο</string>\n    <string name=\"downloads\">Λήψεις</string>\n    <string name=\"updated\">Ενημερωμένο</string>\n    <string name=\"newest\">Νεότερο</string>\n    <string name=\"by_rating\">Βαθμολογία</string>\n    <string name=\"filter\">Φίλτρο</string>\n    <string name=\"dark\">Σκοτεινό</string>\n    <string name=\"follow_system\">Όπως στο σύστημα</string>\n    <string name=\"clear\">Εκκαθάριση</string>\n    <string name=\"remove\">Διαγραφή</string>\n    <string name=\"save_page\">Αποθήκευση σελίδας</string>\n    <string name=\"page_saved\">Αποθηκευμένα</string>\n    <string name=\"share_image\">Κοινοποίηση εικόνας</string>\n    <string name=\"_import\">Εισαγωγή</string>\n    <string name=\"delete\">Διαγραφή</string>\n    <string name=\"text_file_not_supported\">Επιλέξτε ένα αρχείο ZIP ή CBZ.</string>\n    <string name=\"no_description\">Χωρίς περιγραφή</string>\n    <string name=\"clear_pages_cache\">Εκκαθάριση προσωρινής μνήμης σελίδων</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Τυπικό</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"search_on_s\">Αναζήτηση στο %s</string>\n    <string name=\"delete_manga\">Διαγραφή manga</string>\n    <string name=\"text_delete_local_manga\">Διαγραφή του \\\"%s\\\" από τη συσκευή;</string>\n    <string name=\"reader_settings\">Ρυθμίσεις εργαλείου ανάγνωσης</string>\n    <string name=\"switch_pages\">Αλλαγή σελίδων</string>\n    <string name=\"network_error\">Σφάλμα δικτύου</string>\n    <string name=\"chapters\">Κεφάλαια</string>\n    <string name=\"details\">Πληροφορίες</string>\n    <string name=\"list\">Λίστα</string>\n    <string name=\"detailed_list\">Λεπτομερής λίστα</string>\n    <string name=\"loading_\">Φόρτωση…</string>\n    <string name=\"chapter_d_of_d\">Κεφάλαιο %1$d από %2$d</string>\n    <string name=\"you_have_not_favourites_yet\">Δεν υπάρχουν αγαπημένα</string>\n    <string name=\"add\">Προσθήκη</string>\n    <string name=\"processing_\">Επεξεργασία…</string>\n    <string name=\"by_name\">Όνομα</string>\n    <string name=\"popular\">Δημοφιλή</string>\n    <string name=\"sort_order\">Τρόπος ταξινόμησης</string>\n    <string name=\"theme\">Θέμα</string>\n    <string name=\"light\">Φωτεινό</string>\n    <string name=\"pages\">Σελίδες</string>\n    <string name=\"_s_deleted_from_local_storage\">Το \\\"%s\\\" διαγράφηκε από τον τοπικό χώρο αποθήκευσης</string>\n    <string name=\"operation_not_supported\">Αυτή η λειτουργία δεν υποστηρίζεται</string>\n    <string name=\"read_mode\">Λειτουργία ανάγνωσης</string>\n    <string name=\"grid_size\">Μέγεθος πλέγματος</string>\n    <string name=\"theme_name_mamimi\">Μαμίμι</string>\n    <string name=\"theme_name_kanade\">Κανάντε</string>\n    <string name=\"nothing_here\">Δεν υπάρχει τίποτα εδώ</string>\n    <string name=\"theme_name_miku\">Μίκου</string>\n    <string name=\"theme_name_asuka\">Άσουκα</string>\n    <string name=\"theme_name_mion\">Μίον</string>\n    <string name=\"theme_name_rikka\">Ρίκκα</string>\n    <string name=\"theme_name_sakura\">Σάκουρα</string>\n    <string name=\"notifications\">Ειδοποιήσεις</string>\n    <string name=\"vibration\">Δόνηση</string>\n    <string name=\"favourites_categories\">Αγαπημένες κατηγορίες</string>\n    <string name=\"manga_save_location\">Φάκελος λήψεων</string>\n    <string name=\"not_available\">Μη διαθέσιμο</string>\n    <string name=\"all_favourites\">Όλα τα αγαπημένα</string>\n    <string name=\"favourites_category_empty\">Άδεια κατηγορία</string>\n    <string name=\"read_later\">Θα διαβαστούν αργότερα</string>\n    <string name=\"updates\">Ενημερώσεις</string>\n    <string name=\"search_results\">Αποτελέσματα αναζήτησης</string>\n    <string name=\"new_version_s\">Νέα έκδοση: %s</string>\n    <string name=\"size_s\">Μέγεθος: %s</string>\n    <string name=\"clear_updates_feed\">Εκκαθάριση τροφοδοτικού ενημερώσεων</string>\n    <string name=\"updates_feed_cleared\">Καθαρίστηκε</string>\n    <string name=\"rotate_screen\">Περιστροφή οθόνης</string>\n    <string name=\"update\">Ενημέρωση</string>\n    <string name=\"feed_will_update_soon\">Η ενημέρωση του τροφοδοτικού θα αρχίσει σύντομα</string>\n    <string name=\"dont_check\">Μην κοιτάξεις</string>\n    <string name=\"enter_password\">Εισαγωγή κωδικού</string>\n    <string name=\"check_for_updates\">Έλεγχος για ενημερώσεις</string>\n    <string name=\"right_to_left\">Δεξιά προς τα αριστερά</string>\n    <string name=\"create_category\">Νέα κατηγορία</string>\n    <string name=\"scale_mode\">Είδος κλίμακας</string>\n    <string name=\"zoom_mode_fit_center\">Γέμισμα στο κέντρο</string>\n    <string name=\"zoom_mode_fit_height\">Γέμισμα βάσει ύψους</string>\n    <string name=\"zoom_mode_fit_width\">Γέμισμα βάσει πλάτους</string>\n    <string name=\"zoom_mode_keep_start\">Κράτα το κατά την εκκίνηση</string>\n    <string name=\"black_dark_theme\">Μαύρο</string>\n    <string name=\"black_dark_theme_summary\">Λιγότερη κατανάλωση ενέργειας σε συσκευές με οθόνες AMOLED</string>\n    <string name=\"backup_restore\">Αντίγραφο ασφαλείας και επαναφορά</string>\n    <string name=\"create_backup\">Δημιουργία αντιγράφου ασφαλείας</string>\n    <string name=\"restore_backup\">Επαναφορά αντιγράφου ασφαλείας</string>\n    <string name=\"data_restored\">Επαναφέρθηκε</string>\n    <string name=\"preparing_\">Ετοιμάζεται…</string>\n    <string name=\"file_not_found\">Δεν βρέθηκε αρχείο</string>\n    <string name=\"data_restored_with_errors\">Τα δεδομένα επαναφέρθηκαν, αλλά υπήρξαν σφάλματα</string>\n    <string name=\"backup_information\">Μπορείς να δημιουργήσεις αντίγραφα ασφαλείας του ιστορικού σου και των αγαπημένων σου και να τα επαναφέρεις</string>\n    <string name=\"cookies_cleared\">Όλα τα cookies καθαρίστηκαν</string>\n    <string name=\"check_for_new_chapters\">Έλεγχος για νέα κεφάλαια</string>\n    <string name=\"genres\">Είδη</string>\n    <string name=\"suggestions_info\">Όλα τα δεδομένα αναλύονται στην συσκευή αυτή και δεν στέλνονται πουθενά αλλού.</string>\n    <string name=\"text_suggestion_holder\">Ξεκίνα να διαβάσεις manga και θα λαμβάνεις εξατομικευμένες προτάσεις</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Να μην προτείνονται manga που είναι NSFW</string>\n    <string name=\"search_chapters\">Βρες το κεφάλαιο</string>\n    <string name=\"chapters_empty\">Αυτό το manga δεν έχει κεφάλαια</string>\n    <string name=\"chapters_will_removed_background\">Τα κεφάλαια θα διαγράφονται στο παρασκήνιο</string>\n    <string name=\"account_already_exists\">Ο λογαριασμός υπάρχει ήδη</string>\n    <string name=\"back\">Πίσω</string>\n    <string name=\"hide\">Κρύψε</string>\n    <string name=\"check_new_chapters_title\">Έλεγξε για τυχόν νέα κεφάλαια και ενημέρωσε με</string>\n    <string name=\"clear_thumbs_cache\">Εκκαθάριση προσωρινής μνήμης σκίτσων</string>\n    <string name=\"clear_search_history\">Εκκαθάριση ιστορικού αναζήτησης</string>\n    <string name=\"search_history_cleared\">Καθαρίστηκε</string>\n    <string name=\"internal_storage\">Εσωτερικός χώρος αποθήκευσης</string>\n    <string name=\"external_storage\">Εξωτερικός χώρος αποθήκευσης</string>\n    <string name=\"domain\">Τομέας</string>\n    <string name=\"app_update_available\">Μια νέα έκδοση της εφαρμογής έγινε διαθέσιμη</string>\n    <string name=\"open_in_browser\">Άνοιγμα μέσω περιηγητή</string>\n    <string name=\"text_empty_holder_primary\">Λίγο άδειο το μέρος…</string>\n    <string name=\"text_search_holder_secondary\">Προσπάθησε να αναδιατυπώσεις την αναζήτηση.</string>\n    <string name=\"text_history_holder_primary\">Ό,τι διαβάζεις θα εμφανίζεται εδώ</string>\n    <string name=\"text_history_holder_secondary\">Βρες κάτι να διαβάσεις στο τμήμα «Εξερεύνηση»</string>\n    <string name=\"text_local_holder_primary\">Αποθήκευσε κάτι πρώτα</string>\n    <string name=\"text_local_holder_secondary\">Αποθήκευσε κάτι από διαδικτυακές πηγές ή εισήγαγε το από αρχείο.</string>\n    <string name=\"manga_shelf\">Ράφι</string>\n    <string name=\"recent_manga\">Πρόσφατα</string>\n    <string name=\"pages_animation\">Κίνηση σελίδας</string>\n    <string name=\"wrong_password\">Λανθασμένος κωδικός</string>\n    <string name=\"protect_application\">Προστασία εφαρμογής</string>\n    <string name=\"protect_application_summary\">Να ζητείται κωδικός κατά την εκκίνηση του Kotatsu</string>\n    <string name=\"repeat_password\">Επανάληψη κωδικού</string>\n    <string name=\"passwords_mismatch\">Ασύμφωνοι κωδικοί</string>\n    <string name=\"about\">Σχετικά</string>\n    <string name=\"app_version\">Έκδοση %s</string>\n    <string name=\"just_now\">Μόλις τώρα</string>\n    <string name=\"group\">Ομάδα</string>\n    <string name=\"today\">Σήμερα</string>\n    <string name=\"yesterday\">Χθες</string>\n    <string name=\"long_ago\">Πριν πολύ καιρό</string>\n    <string name=\"tap_to_try_again\">Πάτα για να ξαναπροσπαθήσεις</string>\n    <string name=\"reader_mode_hint\">Η επιλεγμένες ρυθμίσεις θα αποθηκευτούν για το παρόν manga</string>\n    <string name=\"silent\">Αθόρυβο</string>\n    <string name=\"read_more\">Διάβασε περισσότερα</string>\n    <string name=\"queued\">Στην ουρά</string>\n    <string name=\"chapter_is_missing\">Λείπει το κεφάλαιο</string>\n    <string name=\"about_app_translation_summary\">Μετάφρασε την εφαρμογή</string>\n    <string name=\"about_app_translation\">Μετάφραση</string>\n    <string name=\"auth_complete\">Εξουσιοδοτημένο</string>\n    <string name=\"auth_not_supported_by\">Η σύνδεση στο %s δεν υποστηρίζεται</string>\n    <string name=\"text_clear_cookies_prompt\">Θα αποσυνδεθείς από όλες τις πηγές</string>\n    <string name=\"state_finished\">Ολοκληρώθηκε</string>\n    <string name=\"state_ongoing\">Σε εξέλιξη</string>\n    <string name=\"system_default\">Προεπιλεγμένο</string>\n    <string name=\"exclude_nsfw_from_history\">Να μην συμπεριλαμβάνονται στο ιστορικό manga που είναι NSFW</string>\n    <string name=\"show_pages_numbers\">Αριθμημένες σελίδες</string>\n    <string name=\"enabled\">Ενεργοποιημένο</string>\n    <string name=\"onboard_text\">Επίλεξε γλώσσες στις οποίες θέλεις να διαβάσεις manga. Μπορείς να αλλάξεις την επιλογή σου αργότερα από τις ρυθμίσεις.</string>\n    <string name=\"never\">Ποτέ</string>\n    <string name=\"preload_pages\">Προφόρτωση σελίδων</string>\n    <string name=\"logged_in_as\">Συνδέθηκες ως %s</string>\n    <string name=\"always\">Πάντα</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Διάφορες γλώσσες</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Εμφάνιση</string>\n    <string name=\"suggestions_updating\">Οι προτάσεις ενημερώνονται</string>\n    <string name=\"suggestions_excluded_genres\">Να μην συμπεριλαμβάνονται τα είδη</string>\n    <string name=\"suggestions_excluded_genres_summary\">Ξεκαθάρισε ποια είδη δεν θες να βλέπεις στα προτεινόμενα</string>\n    <string name=\"text_delete_local_manga_batch\">Διαγραφή των επιλεγμένων στοιχείων από την συσκευή;</string>\n    <string name=\"removal_completed\">Η εκκαθάριση ολοκληρώθηκε</string>\n    <string name=\"sync\">Συγχρονισμός</string>\n    <string name=\"sync_title\">Συγχρόνισε τα δεδομένα σου</string>\n    <string name=\"email_enter_hint\">Εισήγαγε το email σου για να προχωρήσεις</string>\n    <string name=\"new_sources_text\">Νέες πηγές manga είναι διαθέσιμες</string>\n    <string name=\"notifications_enable\">Ενεργοποίηση ειδοποιήσεων</string>\n    <string name=\"download\">Λήψη</string>\n    <string name=\"notifications_settings\">Ρυθμίσεις ειδοποιήσεων</string>\n    <string name=\"done\">Ολοκληρώθηκε</string>\n    <string name=\"reverse\">Αντιστροφή</string>\n    <string name=\"sign_in\">Σύνδεση</string>\n    <string name=\"auth_required\">Συνδέσου για να δεις το περιεχόμενο</string>\n    <string name=\"reset_filter\">Επαναφορά φίλτρου</string>\n    <string name=\"download_slowdown\">Καθυστέρηση λήψεων</string>\n    <string name=\"local_manga_processing\">Επεξεργασία αποθηκευμένων manga</string>\n    <string name=\"canceled\">Ακυρώθηκε</string>\n    <string name=\"_continue\">Συνέχισε</string>\n    <string name=\"error\">Σφάλμα</string>\n    <string name=\"new_chapters\">Νέα κεφάλαια</string>\n    <string name=\"notification_sound\">Ήχος ειδοποιήσεων</string>\n    <string name=\"cannot_find_available_storage\">Μη διαθέσιμος χώρος αποθήκευσης</string>\n    <string name=\"light_indicator\">Δείκτης LED</string>\n    <string name=\"other_storage\">Άλλος χώρος αποθήκευσης</string>\n    <string name=\"remove_category\">Αφαίρεση</string>\n    <string name=\"text_feed_holder\">Νέα κεφάλαια από ό,τι διαβάζεις θα φαίνονται εδώ</string>\n    <string name=\"track_sources\">Ψάξε για ενημερώσεις</string>\n    <string name=\"no_update_available\">Δεν είναι διαθέσιμη κάποια ενημέρωση</string>\n    <string name=\"data_restored_success\">Όλα τα δεδομένα επαναφέρθηκαν</string>\n    <string name=\"protect_application_subtitle\">Εισήγαγε κωδικό με τον οποίο θα ανοίγεις την εφαρμογή</string>\n    <string name=\"captcha_required\">Απαιτείται έλεγχος CAPTCHA</string>\n    <string name=\"captcha_solve\">Επίλυση</string>\n    <string name=\"clear_cookies\">Εκκαθάριση cookies</string>\n    <string name=\"clear_feed\">Εκκαθάριση τροφοδοτικού</string>\n    <string name=\"text_clear_updates_feed_prompt\">Εκκαθάριση ολόκληρου του ιστορικού;</string>\n    <string name=\"next\">Επόμενο</string>\n    <string name=\"confirm\">Επιβεβαίωση</string>\n    <string name=\"welcome\">Καλώς ήρθες</string>\n    <string name=\"default_s\">Προεπιλεγμένο: %s</string>\n    <string name=\"text_clear_search_history_prompt\">Εκκαθάριση όλων των πρόσφατων αναζητήσεων;</string>\n    <string name=\"backup_saved\">Το αντίγραφο ασφαλείας αποθηκεύτηκε</string>\n    <string name=\"password_length_hint\">Ο κωδικός πρέπει να αποτελείται από 4, ή περισσότερους, χαρακτήρες</string>\n    <string name=\"tracker_warning\">Τα συστήματα μερικών συσκευών λειτουργούν διαφορετικά, και αυτό μπορεί να επηρεάσει τις λειτουργίες στο παρασκήνιο.</string>\n    <string name=\"screenshots_policy\">Πολιτική στιγμιότυπων οθόνης</string>\n    <string name=\"screenshots_block_nsfw\">Να μην επιτρέπονται σε NSFW manga</string>\n    <string name=\"screenshots_allow\">Επίτρεψε</string>\n    <string name=\"screenshots_block_all\">Να απαγορεύονται πάντα</string>\n    <string name=\"suggestions_summary\">Να προτείνονται manga βάσει των προτιμήσεων σου</string>\n    <string name=\"disabled\">Απενεργοποιημένο</string>\n    <string name=\"suggestions\">Προτάσεις</string>\n    <string name=\"only_using_wifi\">Μόνο μέσω Wi-Fi</string>\n    <string name=\"suggestions_enable\">Ενεργοποίηση προτάσεων</string>\n    <string name=\"download_slowdown_summary\">Βοηθάει στην αποφυγή μπλοκαρίσματος της διεύθυνσης IP σου</string>\n    <string name=\"show_notification_new_chapters_on\">Θα λαμβάνεις ειδοποιήσεις για ενημερώσεις σχετικά με τα manga που διαβάζεις</string>\n    <string name=\"show_notification_new_chapters_off\">Δεν θα λαμβάνεις ειδοποιήσεις αλλά νέα κεφάλαια θα φαίνονται στην λίστα</string>\n    <string name=\"state_upcoming\">Ανερχόμενο</string>\n    <string name=\"by_name_reverse\">Όνομα - αντεστρεμένα</string>\n    <string name=\"advanced\">Για προχωρημένους</string>\n    <string name=\"catalog\">Κατάλογος</string>\n    <string name=\"manage_sources\">Διαχείριση πηγών</string>\n    <string name=\"content_type_comics\">Comics</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_other\">Άλλα</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"sources_catalog\">Κατάλογος πηγών</string>\n    <string name=\"source_enabled\">Η πηγή ενεργοποιήθηκε</string>\n    <string name=\"no_manga_sources_catalog_text\">Δεν υπάρχουν διαθέσιμες πηγές σε αυτό το τμήμα, ή πιθανώς όλες έχουν προστεθεί. \n\\nΜείνετε σε επαφή</string>\n    <string name=\"no_manga_sources_found\">Η αναζήτηση σας δεν επέφερε διαθέσιμα manga</string>\n    <string name=\"invalid_port_number\">Μη έγκυρος αριθμός θύρας</string>\n    <string name=\"user_agent\">Επικεφαλίδα UserAgent</string>\n    <string name=\"history_cleared\">Το ιστορικό καθαρίστηκε</string>\n    <string name=\"no_bookmarks_summary\">Μπορείς να χρησιμοποιήσεις σελιδοδείκτη όσο διαβάζεις manga</string>\n    <string name=\"random\">Τυχαίο</string>\n    <string name=\"exit_confirmation\">Επιβεβαίωση εξόδου</string>\n    <string name=\"saved_manga\">Αποθηκευμένα manga</string>\n    <string name=\"pages_cache\">Προσωρινή μνήμη σελίδων</string>\n    <string name=\"other_cache\">Προσωρινή μνήμη άλλων</string>\n    <string name=\"removed_from_favourites\">Αφαιρέθηκε από τα αγαπημένα</string>\n    <string name=\"no_chapters\">Δεν έχει κεφάλαια</string>\n    <string name=\"reader_info_pattern\">Κεφ. %1$d/%2$d Σελ. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Εμφάνιση μπάρας πληροφοριών στο εργαλείο ανάγνωσης</string>\n    <string name=\"import_completed_hint\">Μπορείς να διαγράψεις το αρχικό αρχείο από τον χώρο αποθήκευσης για να απελευθερώσεις χώρο</string>\n    <string name=\"import_will_start_soon\">Η εισαγωγή θα ξεκινήσει σύντομα</string>\n    <string name=\"reset\">Επαναφορά</string>\n    <string name=\"compact\">Συμπαγής</string>\n    <string name=\"language\">Γλώσσα</string>\n    <string name=\"scrobbling_empty_hint\">Για να καταγράψεις την πρόοδο σου, επίλεξε Μενού → Καταγραφή στην οθόνη λεπτομερειών για το manga.</string>\n    <string name=\"services\">Υπηρεσίες</string>\n    <string name=\"allow_unstable_updates\">Επίτρεψε ασταθείς ενημερώσεις</string>\n    <string name=\"sources_reorder_tip\">Πάτα παρατεταμένα ένα στοιχείο για να το αναταξινομήσεις</string>\n    <string name=\"settings_apply_restart_required\">Κάνε επανεκκίνηση της εφαρμογής για να εφαρμοστούν οι αλλαγές, παρακαλώ</string>\n    <string name=\"comics_archive_import_description\">Μπορείς να επιλέξεις ένα ή περισσότερα .cbz ή .zip αρχεία, κάθε ένα θα αναγνωρίζεται ως ξεχωριστό manga.</string>\n    <string name=\"speed\">Ταχύτητα</string>\n    <string name=\"show_on_shelf\">Εμφάνιση στο Ράφι</string>\n    <string name=\"find_similar\">Βρες παρόμοια</string>\n    <string name=\"ignore_ssl_errors\">Αγνόησε σφάλματα SSL</string>\n    <string name=\"no_thanks\">Όχι ευχαριστώ</string>\n    <string name=\"cancel_all_downloads_confirm\">Όλες οι τρέχουσες λήψεις θα ακυρωθούν, και δεδομένα που έχουν ληφθεί μερικώς θα χαθούν</string>\n    <string name=\"downloads_cancelled\">Οι λήψεις ακυρώθηκαν</string>\n    <string name=\"proxy\">Διακομιστής μεσολάβησης</string>\n    <string name=\"images_proxy_title\">Διακομιστής μεσολάβησης βελτιστοποίησης εικόνων</string>\n    <string name=\"invert_colors\">Αντιστροφή χρωμάτων</string>\n    <string name=\"username\">Όνομα χρήστη</string>\n    <string name=\"network\">Δίκτυο</string>\n    <string name=\"data_and_privacy\">Δεδομένα και ιδιωτικότητα</string>\n    <string name=\"restore_summary\">Επανάφερε κάποιο αντίγραφο ασφαλείας</string>\n    <string name=\"webtoon_zoom_summary\">Επίτρεψε την χειρονομία μεγέθυνσης στην λειτουργία Webtoon</string>\n    <string name=\"show_pages_numbers_summary\">Εμφάνιση αριθμού σελίδων στην κάτω γωνία</string>\n    <string name=\"detect_reader_mode\">Αυτόματη αναγνώριση κατάλληλης λειτουργίας ανάγνωσης</string>\n    <string name=\"bookmarks_removed\">Οι σελιδοδείκτες αφαιρέθηκαν</string>\n    <string name=\"no_manga_sources\">Δεν υπάρχουν πηγές για manga</string>\n    <string name=\"no_manga_sources_text\">Ενεργοποίησε πηγές για manga ώστε να διαβάσεις manga διαδικτυακά</string>\n    <string name=\"reorder\">Αναταξινόμηση</string>\n    <string name=\"empty\">Άδειο</string>\n    <string name=\"explore\">Εξερεύνησε</string>\n    <string name=\"confirm_exit\">Πάτα Πίσω ξανά για να επιστρέψεις στην προηγούμενη οθόνη</string>\n    <string name=\"exit_confirmation_summary\">Πάτα Πίσω δύο φορές για να βγεις από την εφαρμογή</string>\n    <string name=\"storage_usage\">Χρησιμοποίηση χώρου αποθήκευσης</string>\n    <string name=\"available\">Διαθέσιμο</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"options\">Επιλογές</string>\n    <string name=\"not_found_404\">Το περιεχόμενο δεν βρέθηκε, και πιθανώς να έχει αφαιρεθεί</string>\n    <string name=\"incognito_mode\">Λειτουργία Incognito</string>\n    <string name=\"automatic_scroll\">Αυτόματη ολίσθηση</string>\n    <string name=\"comics_archive\">Αρχειοθήκη για κόμικς</string>\n    <string name=\"folder_with_images\">Φάκελος με εικόνες</string>\n    <string name=\"importing_manga\">Εισαγωγή manga σε εξέλιξη</string>\n    <string name=\"import_completed\">Η εισαγωγή ολοκληρώθηκε</string>\n    <string name=\"feed\">Τροφοδοτικό</string>\n    <string name=\"history_shortcuts\">Εμφάνιση συντομεύσεων σχετικά με πρόσφατα manga</string>\n    <string name=\"history_shortcuts_summary\">Κάνε πρόσφατα manga διαθέσιμα πατώντας παρατεταμένα το εικονίδιο της εφαρμογής</string>\n    <string name=\"contrast\">Αντίθεση</string>\n    <string name=\"reader_control_ltr_summary\">Το πάτημα της δεξιάς πλευράς, ή του κουμπιού μείωσης της έντασης, πάντα προχωράει στην επόμενη σελίδα</string>\n    <string name=\"reader_control_ltr\">Εργονομικός χειρισμός του εργαλείου ανάγνωσης</string>\n    <string name=\"color_correction\">Χρωματική διόρθωση</string>\n    <string name=\"brightness\">Φωτεινότητα</string>\n    <string name=\"text_unsaved_changes_prompt\">Αποθήκευση ή αναίρεση των μη αποθηκευμένων επιλογών;</string>\n    <string name=\"discard\">Απόρριψη</string>\n    <string name=\"error_no_space_left\">Δεν έχει μείνει επαρκής αποθηκευτικός χώρος στην συσκευή</string>\n    <string name=\"reader_slider\">Εμφάνιση σύρτη αλλαγής σελίδων</string>\n    <string name=\"webtoon_zoom\">Μεγέθυνση Webtoon</string>\n    <string name=\"network_unavailable\">Δεν υπάρχει διαθέσιμο δίκτυο</string>\n    <string name=\"server_error\">Σφάλμα διακομιστή (%1$d). Παρακαλώ ξαναπροσπαθήστε αργότερα</string>\n    <string name=\"clear_new_chapters_counters\">Επιπλέον, σβήσε πληροφορίες περί νέων κεφαλαίων</string>\n    <string name=\"network_unavailable_hint\">Άνοιξε το Wi-Fi ή τα δεδομένα για να διαβάσεις manga διαδικτυακώς</string>\n    <string name=\"welcome_text\">Παρακαλώ επίλεξε ποιες πηγές περιεχομένου θα ήθελες να ενεργοποιήσεις. Αυτό μπορεί να ρυθμιστεί ναι αργότερα από τις ρυθμίσεις</string>\n    <string name=\"sync_auth\">Συνδέσου στον λογαριασμό συγχρονισμού</string>\n    <string name=\"downloads_settings_info\">Μπορείς να ενεργοποιήσεις την καθυστέρηση λήψεων για κάθε πηγή manga ξεχωριστά μέσω των ρυθμίσεων των πηγών αν αντιμετωπίζεις προβλήματα με μπλοκάρισμα από πλευράς του διακομιστή</string>\n    <string name=\"prefetch_content\">Προφόρτωση περιεχομένου</string>\n    <string name=\"skip\">Προσπέραση</string>\n    <string name=\"restore\">Επαναφορά</string>\n    <string name=\"backup_date_\">Ημερομηνία δημιουργίας αντιγράφου ασφαλείας: %s</string>\n    <string name=\"content_rating\">Ηλικιακή βαθμολογία περιεχομένου</string>\n    <string name=\"genres_exclude\">Να μην συμπεριλαμβάνονται τα είδη</string>\n    <string name=\"rating_safe\">Ασφαλές</string>\n    <string name=\"rating_suggestive\">Άσεμνο</string>\n    <string name=\"rating_adult\">Για ενήλικες</string>\n    <string name=\"clear_source_cookies_summary\">Σβήσε τα cookies για τους επιλεγμένους τομέες μόνο. Στις περισσότερες περιπτώσεις θα ακυρωθεί η εξουσιοδότηση</string>\n    <string name=\"online_variant\">Διαδικτυακή έκδοση</string>\n    <string name=\"keep_screen_on\">Κράτα την οθόνη ανοιχτή</string>\n    <string name=\"keep_screen_on_summary\">Να μην κλείνει η οθόνη κατά την ανάγνωση manga</string>\n    <string name=\"lock_screen_rotation\">Κλείδωσε την περιστροφή οθόνης</string>\n    <string name=\"allow_unstable_updates_summary\">Λάβε ειδοποιήσεις για ασταθείς εκδόσεις</string>\n    <string name=\"download_started\">Η λήψη ξεκίνησε</string>\n    <string name=\"manga_list\">Λίστα manga</string>\n    <string name=\"theme_name_dynamic\">Δυναμικό</string>\n    <string name=\"disable_nsfw\">Απενεργοποιήση NSFW</string>\n    <string name=\"got_it\">Έγινε</string>\n    <string name=\"default_tab\">Προεπιλεγμένη καρτέλα</string>\n    <string name=\"download_option_all_chapters\">Όλα τα μεταφρασμένα κεφάλαια %s</string>\n    <string name=\"download_option_whole_manga\">Ολόκληρο το manga</string>\n    <string name=\"download_option_first_n_chapters\">Πρώτα %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Τα επόμενα μη αναγνωσμένα %s</string>\n    <string name=\"download_option_all_unread\">Όλα τα μη αναγνωσμένα κεφάλαια</string>\n    <string name=\"download_option_all_unread_b\">Όλα τα μην αναγνωσμένα κεφάλαια (%s)</string>\n    <string name=\"download_option_manual_selection\">Χειροκίνητη επιλογή κεφαλαίων</string>\n    <string name=\"description\">Περιγραφή</string>\n    <string name=\"this_month\">Αυτόν τον μήνα</string>\n    <string name=\"voice_search\">Φωνητική αναζήτηση</string>\n    <string name=\"related_manga\">Σχετικά manga</string>\n    <string name=\"color_light\">Φωτεινό</string>\n    <string name=\"color_white\">Άσπρο</string>\n    <string name=\"color_black\">Μαύρο</string>\n    <string name=\"background\">Παρασκήνιο</string>\n    <string name=\"data_not_restored\">Τα δεδομένα δεν επαναφέρθηκαν</string>\n    <string name=\"suggestions_wifi_only_summary\">Να μην ενημερώνονται οι προτιμήσεις κατά την χρήση μετρημένων συνδέσεων δικτύου</string>\n    <string name=\"search_hint\">Εισήγαγε τίτλο manga, είδος, ή το όνομα πηγής</string>\n    <string name=\"progress\">Πρόοδος</string>\n    <string name=\"order_added\">Προσθήκη</string>\n    <string name=\"manage_categories\">Διαχείριση κατηγοριών</string>\n    <string name=\"show\">Εμφάνισε</string>\n    <string name=\"downloaded\">Ληφθέντα</string>\n    <string name=\"too_many_requests_message\">Υπερβολικά πολλά αιτήματα. Ξαναδοκιμάστε αργότερα</string>\n    <string name=\"manage\">Διαχείριση</string>\n    <string name=\"no_bookmarks_yet\">Δεν έχεις κάποιον σελιδοδείκτη ακόμα</string>\n    <string name=\"share_logs\">Κοινοποίηση logs</string>\n    <string name=\"no_access_to_file\">Δεν έχεις πρόσβαση σε αυτό το αρχείο ή directory</string>\n    <string name=\"local_manga_directories\">Directory για τοπικά manga</string>\n    <string name=\"captcha_required_summary\">%s απαιτεί την επίλυση ενός CAPTCHA για να λειτουργήσει κανονικά</string>\n    <string name=\"password\">Κωδικός</string>\n    <string name=\"authorization_optional\">Εξουσιοδότηση (προαιρετική)</string>\n    <string name=\"source_disabled\">Η πηγή απενεργοποιήθηκε</string>\n    <string name=\"grayscale\">Κλίμακα του γκρι</string>\n    <string name=\"globally\">Σε όλη την έκταση της εφαρμογής</string>\n    <string name=\"this_manga\">Αυτό το manga</string>\n    <string name=\"color_correction_apply_text\">Αυτές οι ρυθμίσεις μπορούν να εφαρμοστούν σε όλη την έκταση της βιβλιοθήκης ή μόνο για αυτό το manga. Αν εφαρμοστούν σε όλη την έκταση της βιβλιοθήκης, οι ξεχωριστές ρυθμίσεις δεν θα επηρεαστούν.</string>\n    <string name=\"apply\">Εφαρμογή</string>\n    <string name=\"type\">Τύπος</string>\n    <string name=\"address\">Διεύθυνση</string>\n    <string name=\"port\">Θύρα</string>\n    <string name=\"mark_as_current\">Σημείωσε ως τρέχων</string>\n    <string name=\"show_suspicious_content\">Εμφάνιση ύποπτου περιεχομένου</string>\n    <string name=\"color_theme\">Χρωματικός συνδυασμός</string>\n    <string name=\"state_abandoned\">Παρατήθηκε</string>\n    <string name=\"sync_settings\">Ρυθμίσεις συγχρονισμού</string>\n    <string name=\"server_address\">Διεύθυνση διακομιστή</string>\n    <string name=\"manual\">Χειροκίνητα</string>\n    <string name=\"available_d\">Διαθέσιμα: %1$d</string>\n    <string name=\"disable_nsfw_summary\">Απενεργοποίηση NSFW πηγών και απόκρυψη manga για ενηλίκους από την λίστα αν είναι δυνατό</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"mirror_switching\">Αυτόματη επιλογή mirror</string>\n    <string name=\"pause\">Παύση</string>\n    <string name=\"resume\">Συνέχιση</string>\n    <string name=\"paused\">Σε παύση</string>\n    <string name=\"cancel_all\">Ακύρωση όλων</string>\n    <string name=\"downloads_wifi_only\">Λήψη μόνο μέσω Wi-Fi</string>\n    <string name=\"downloads_wifi_only_summary\">Σταματημός της λήψης με την αλλαγή σε δεδομένα</string>\n    <string name=\"suggestion_manga\">Προτάσεις: %s</string>\n    <string name=\"suggestions_notifications_summary\">Ενίοτε εμφάνιση ειδοποιήσεων με προτεινόμενα manga</string>\n    <string name=\"more\">Περισσότερα</string>\n    <string name=\"enable\">Ενεργοποίησε</string>\n    <string name=\"remove_completed\">Αφαίρεσε τα ολοκληρωμένα</string>\n    <string name=\"languages\">Γλώσσες</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Το φιλτράρισμα δια είδους και τοποθεσίας δεν υποστηρίζεται από αυτήν την πηγή</string>\n    <string name=\"error_filter_states_genre_not_supported\">Το φιλτράρισμα δια είδους και κατάστασης δεν υποστηρίζεται από αυτήν την πηγή</string>\n    <string name=\"invalid_value_message\">Μη έγκυρη τιμή</string>\n    <string name=\"error_corrupted_file\">Μη έγκυρα δεδομένα επιστρέφονται ή το αρχείο είναι διεφθαρμένο</string>\n    <string name=\"in_progress\">Σε εξέλιξη</string>\n    <string name=\"on_device\">Στην συσκευή</string>\n    <string name=\"directories\">Directories</string>\n    <string name=\"main_screen_sections\">Τμήματα αρχικής οθόνης</string>\n    <string name=\"to_top\">Στην κορυφή</string>\n    <string name=\"moved_to_top\">Κινήθηκε στην κορυφή</string>\n    <string name=\"genres_search_hint\">Ξεκίνα να γράφεις το όνομα του είδους</string>\n    <string name=\"state_paused\">Σε παύση</string>\n    <string name=\"zoom_out\">Σμίκρυνση</string>\n    <string name=\"zoom_in\">Μεγέθυνση</string>\n    <string name=\"reader_zoom_buttons\">Εμφάνιση κουμπιών μεγέθυνσης</string>\n    <string name=\"reader_zoom_buttons_summary\">Εμφάνιση κουμπιών μεγέθυνσης στην κάτω δεξιά γωνία ή όχι</string>\n    <string name=\"reader_optimize\">Μείωση κατανάλωσης μνήμης (beta)</string>\n    <string name=\"mark_as_completed\">Σημείωσε ως ολοκληρωμένο</string>\n    <string name=\"mark_as_completed_prompt\">Σημείωση του επιλεγμένου manga ως ολοκληρωμένου;\n\\n\n\\nΠροσοχή: η πρόοδος σου θα χαθεί.</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Ενδέχεται να βοηθήσει με την εκκίνηση της λήψης αν αντιμετωπίζεις προβλήματα σχετικά με αυτήν</string>\n    <string name=\"categories_delete_confirm\">Είσαι σίγουρος πως θες να διαγράψεις τις επιλεγμένες αγαπημένες κατηγορίες; \\nΌλα τα manga που ανήκουν σε αυτές θα χαθούν, και αυτό είναι μη αναστρέψιμο.</string>\n    <string name=\"downloads_resumed\">Οι λήψεις έχουν ξαναξεκινήσει</string>\n    <string name=\"downloads_paused\">Οι λήψεις παύθηκαν</string>\n    <string name=\"remove_completed_downloads_confirm\">Το ιστορικό λήψεων σου θα διαγραφεί</string>\n    <string name=\"text_downloads_list_holder\">Δεν έχεις κάποια λήψη</string>\n    <string name=\"downloads_removed\">Οι λήψεις αφαιρέθηκαν</string>\n    <string name=\"suggestions_enable_prompt\">Θέλεις να λαμβάνεις εξατομικευμένες προτάσεις για manga;</string>\n    <string name=\"web_view_unavailable\">WebView μη διαθέσιμο: έλεγξε αν ο WebView πάροχος είναι εγκατεστημένος.</string>\n    <string name=\"manga_error_description_pattern\">Πληροφορίες σφάλματος:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Προσπάθησε να &lt;a href=%2$s&gt;ανοίξεις το manga μέσω ενός περιηγητή&lt;/a&gt; για να βεβαιωθείς για την διαθεσιμότητα του στην πηγή.&lt;br&gt;2. Βεβαιώσου πως χρησιμοποιείς την &lt;a href=kotatsu://about&gt;πιο πρόσφατη έκδοση του Kotatsu.&lt;/a&gt;&lt;br&gt;3. Αν είναι διαθέσιμο, στείλε αναφορά του σφάλματος στους δημιουργούς.</string>\n    <string name=\"clear_network_cache\">Καθάρισε προσωρινή μνήμη δικτύου</string>\n    <string name=\"images_procy_description\">Χρησιμοποίησε την υπηρεσία wsrv.nl για να μειώσεις την κίνηση δικτύου και να αυξήσεις την ταχύτητα φόρτωσης εικόνων αν είναι δυνατό</string>\n    <string name=\"reader_info_bar_summary\">Εμφάνιση ώρας και προόδου στο πάνω μέρος της οθόνης</string>\n    <string name=\"enable_logging\">Ενεργοποίηση καταγραφής logs</string>\n    <string name=\"pick_custom_directory\">Διάλεξε directory της επιλογής σου</string>\n    <string name=\"enable_logging_summary\">Καταγραφή ορισμένων ενεργειών που προορίζονται για την εκσφαλμάτωση της εφαρμογής. Μην το ενεργοποιήσεις αν δεν είσαι βέβαιος για το τι κάνεις</string>\n    <string name=\"show_in_grid_view\">Εμφάνιση με μορφή πλέγματος</string>\n    <string name=\"mirror_switching_summary\">Αυτόματη αλλαγή τομέων για πήγες manga σε περίπτωση σφαλμάτων αν υπάρχει διαθεσιμότητα mirror</string>\n    <string name=\"folder_with_images_import_description\">Μπορείς να επιλέξεις ένα directory με φακέλους αρχειοθέτησης ή εικόνες. Κάθε φάκελος αρχειοθέτησης (ή subdirectory) θα αναγνωρίζεται ως κεφάλαιο.</string>\n    <string name=\"sync_host_description\">Μπορείς να χρησιμοποιήσεις έναν self-hosted διακομιστή συγχρονισμού, ή έναν από τους προεπιλεγμένους. Μην πειράξεις αυτή την ρύθμιση αν δεν ξέρεις τι κάνεις.</string>\n    <string name=\"sync_auth_hint\">Μπορείς να συνδεθείς σε έναν προϋπάρχον λογαριασμό ή να φτιάξεις έναν καινούριο</string>\n    <string name=\"color_dark\">Σκοτεινό</string>\n    <string name=\"data_not_restored_text\">Βεβαιώσου πως έχεις επιλέξει το σωστό αντίγραφο ασφαλείας</string>\n    <string name=\"tracker_wifi_only_summary\">Να μην γίνεται έλεγχος για νέα κεφάλαια κατά την χρήση μετρημένων συνδέσεων δικτύου</string>\n    <string name=\"unknown\">Άγνωστο</string>\n    <string name=\"related_manga_summary\">Εμφάνιση λίστας σχετικών manga. Υπάρχει το ενδεχόμενο ανακρίβειας ή πλήρους απώλειας</string>\n    <string name=\"items_limit_exceeded\">Δεν μπορούν να προστεθούν περαιτέρω στοιχεία</string>\n    <string name=\"reader_optimize_summary\">Μείωση της ποιότητας των άφαντων σελίδων για την μείωση της χρησιμοποιούμενης μνήμης</string>\n    <string name=\"state\">Κατάσταση</string>\n    <string name=\"error_multiple_genres_not_supported\">Το φιλτράρισμα δια πολλών ειδών δεν υποστηρίζεται από αυτήν την πηγή manga</string>\n    <string name=\"error_multiple_states_not_supported\">Το φιλτράρισμα δια πολλών καταστάσεων δεν υποστηρίζεται από αυτήν την πηγή manga</string>\n    <string name=\"error_search_not_supported\">Η αναζήτηση δεν υποστηρίζεται από αυτήν την πηγή manga</string>\n    <string name=\"enhanced_colors_summary\">Μειώνει την ένταση της χρωματικής ζώνης, αλλά μπορεί να επηρεάσει την επίδοση</string>\n    <string name=\"enhanced_colors\">Λειτουργία χρώματος 32-bit</string>\n    <string name=\"suggest_new_sources\">Να προτείνονται νέες πηγές μετά από ενημέρωση της εφαρμογής</string>\n    <string name=\"suggest_new_sources_summary\">Εντολή για ενεργοποίηση καινούριων πηγών μετά την ενημέρωση της εφαρμογής</string>\n    <string name=\"list_options\">Επιλογές λίστας</string>\n    <string name=\"by_relevance\">Σχετικότητα</string>\n    <string name=\"categories\">Κατηγορίες</string>\n    <string name=\"periodic_backups\">Περιοδική δημιουργία αντιγράφων ασφαλείας</string>\n    <string name=\"backup_frequency\">Συχνότητα δημιουργίας αντιγράφων ασφαλείας</string>\n    <string name=\"frequency_every_day\">Κάθε μέρα</string>\n    <string name=\"frequency_every_2_days\">Κάθε δύο μέρες</string>\n    <string name=\"frequency_once_per_week\">Μία φορά την εβδομάδα</string>\n    <string name=\"frequency_twice_per_month\">Δύο φορές τον μήνα</string>\n    <string name=\"frequency_once_per_month\">Μία φορά τον μήνα</string>\n    <string name=\"periodic_backups_enable\">Ενεργοποίηση περιοδικής δημιουργίας αντιγράφων ασφαλείας</string>\n    <string name=\"backups_output_directory\">Directory όπου θα αποθηκεύονται τα αντίγραφα ασφαλείας</string>\n    <string name=\"last_successful_backup\">Τελευταία επιτυχής δημιουργία αντιγράφου ασφαλείας: %s</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d από %2$d ενεργές</string>\n    <string name=\"edit_category\">Επεξεργασία κατηγορίας</string>\n    <string name=\"tracking\">Καταγραφή</string>\n    <string name=\"empty_favourite_categories\">Χωρίς αγαπημένες κατηγορίες</string>\n    <string name=\"logout\">Αποσύνδεση</string>\n    <string name=\"bookmarks\">Σελιδοδείκτες</string>\n    <string name=\"bookmark_removed\">Σελιδοδείκτης αφαιρέθηκε</string>\n    <string name=\"bookmark_added\">Σελιδοδείκτης προστέθηκε</string>\n    <string name=\"undo\">Αναίρεση</string>\n    <string name=\"removed_from_history\">Αφαιρέθηκε από το ιστορικό</string>\n    <string name=\"detect_reader_mode_summary\">Αυτόματη ανίχνευση αν το manga είναι webtoon</string>\n    <string name=\"status_planned\">Προγραμματισμένο</string>\n    <string name=\"status_reading\">Ανάγνωση</string>\n    <string name=\"status_re_reading\">Επανάναγνωση</string>\n    <string name=\"status_completed\">Ολοκληρώθηκε</string>\n    <string name=\"status_on_hold\">Σε αναμονή</string>\n    <string name=\"status_dropped\">Διακόπηκε</string>\n    <string name=\"disable_all\">Απενεργοποίηση όλων</string>\n    <string name=\"use_fingerprint\">Χρήση ανιχνευτή δακτυλικού αποτυπώματος αν διατίθεται</string>\n    <string name=\"show_reading_indicators_summary\">Εμφάνιση ποσοστού ανάγνωσης στο ιστορικό και τα αγαπημένα</string>\n    <string name=\"name\">Όνομα</string>\n    <string name=\"dns_over_https\">DNS μέσω HTTPS</string>\n    <string name=\"default_mode\">Προεπιλεγμένη λειτουργία</string>\n    <string name=\"disable_battery_optimization\">Απενεργοποίηση βελτιστοποίησης μπαταρίας</string>\n    <string name=\"disable_battery_optimization_summary\">Βοηθά στους ελέγχους ενημερώσεων στο παρασκήνιο</string>\n    <string name=\"crash_text\">Κάτι πήγε στραβά. Παρακαλούμε υποβάλετε αναφορά σφάλματος στους προγραμματιστές για να μας βοηθήσετε να το διορθώσουμε.</string>\n    <string name=\"send\">Αποστολή</string>\n    <string name=\"report\">Αναφορά</string>\n    <string name=\"show_reading_indicators\">Εμφάνιση δεικτών προόδου ανάγνωσης</string>\n    <string name=\"data_deletion\">Διαγραφή δεδομένων</string>\n    <string name=\"show_all\">Εμφάνιση όλων</string>\n    <string name=\"invalid_domain_message\">Μη έγκυρο domain</string>\n    <string name=\"select_range\">Επιλογή εύρους</string>\n    <string name=\"last_2_hours\">Τελευταίες 2 ώρες</string>\n    <string name=\"edit\">Επεξεργασία</string>\n    <string name=\"appwidget_shelf_description\">Manga από τα αγαπημένα σας</string>\n    <string name=\"bookmark_add\">Προσθήκη σελιδοδείκτη</string>\n    <string name=\"bookmark_remove\">Αφαίρεση σελιδοδείκτη</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga που χαρακτηρίζονται ως NSFW δεν θα προστίθενται ποτέ στο ιστορικό και η πρόοδος σου δεν θα αποθηκεύεται</string>\n    <string name=\"clear_all_history\">Εκκαθάριση όλου του ιστορικού</string>\n    <string name=\"appwidget_recent_description\">Τα πρόσφατα διαβασμένα manga σου</string>\n    <string name=\"clear_cookies_summary\">Μπορεί να βοηθήσει σε περίπτωση κάποιων προβλημάτων. Όλες οι εξουσιοδοτήσεις θα ανακληθούν</string>\n    <string name=\"category_hidden_done\">Αυτή η κατηγορία αποκρύφτηκε από την αρχική οθόνη και είναι προσβάσιμη μέσω του Μενού → Διαχείριση κατηγοριών</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-en-rGB/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"favourites\">Favourites</string>\n    <string name=\"you_have_not_favourites_yet\">No favourites yet</string>\n    <string name=\"add_to_favourites\">Favourite this</string>\n    <string name=\"favourites_categories\">Favourite categories</string>\n    <string name=\"all_favourites\">All favourites</string>\n    <string name=\"backup_information\">You can create backup of your history and favourites and restore it</string>\n    <string name=\"empty_favourite_categories\">No favourite categories</string>\n    <string name=\"appwidget_shelf_description\">Manga from your favourites</string>\n    <string name=\"show_reading_indicators_summary\">Show percentage read in history and favourites</string>\n    <string name=\"categories_delete_confirm\">Are you sure you want to delete the selected favourite categories?\\nAll manga in it will be lost and this cannot be undone.</string>\n    <string name=\"removed_from_favourites\">Removed from favourites</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" from \\\"%2$s\\\" will be replaced with \\\"%3$s\\\" from \\\"%4$s\\\" in your history and favourites (if present)</string>\n    <string name=\"not_in_favorites\">Not in favourites</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-enm/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-es/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d elemento</item>\n        <item quantity=\"many\">%1$d elementos</item>\n        <item quantity=\"other\">%1$d elementos</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nuevo capítulo</item>\n        <item quantity=\"many\">%1$d nuevos capítulos</item>\n        <item quantity=\"other\">%1$d nuevos capítulos</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d capítulo</item>\n        <item quantity=\"many\">%1$d capítulos</item>\n        <item quantity=\"other\">%1$d capítulos</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">hace %1$d minuto</item>\n        <item quantity=\"many\">hace %1$d minutos</item>\n        <item quantity=\"other\">hace %1$d minutos</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">hace %1$d hora</item>\n        <item quantity=\"many\">hace %1$d horas</item>\n        <item quantity=\"other\">hace %1$d horas</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">hace %1$d día</item>\n        <item quantity=\"many\">hace %1$d días</item>\n        <item quantity=\"other\">hace %1$d días</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">Hace %1$d mes</item>\n        <item quantity=\"many\">Hace %1$d meses</item>\n        <item quantity=\"other\">Hace %1$d meses</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d hora</item>\n        <item quantity=\"many\">%1$d horas</item>\n        <item quantity=\"other\">%1$d horas</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuto</item>\n        <item quantity=\"many\">%1$d minutos</item>\n        <item quantity=\"other\">%1$d minutos</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"local_storage\">Dispositivo</string>\n    <string name=\"favourites\">Favoritos</string>\n    <string name=\"history\">Historial</string>\n    <string name=\"error_occurred\">Ocurrió un error</string>\n    <string name=\"network_error\">Error en la red</string>\n    <string name=\"details\">Detalles</string>\n    <string name=\"chapters\">Capítulos</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"detailed_list\">Lista detallada</string>\n    <string name=\"grid\">Cuadrículas</string>\n    <string name=\"list_mode\">Vista de lista</string>\n    <string name=\"settings\">Ajustes</string>\n    <string name=\"remote_sources\">Fuentes del manga</string>\n    <string name=\"loading_\">Cargando…</string>\n    <string name=\"chapter_d_of_d\">Capítulo %1$d de %2$d</string>\n    <string name=\"close\">Cerrar</string>\n    <string name=\"try_again\">Inténtalo de nuevo</string>\n    <string name=\"clear_history\">Borrar historial</string>\n    <string name=\"nothing_found\">No se encontró nada</string>\n    <string name=\"history_is_empty\">Aún sin historial</string>\n    <string name=\"read\">Leer</string>\n    <string name=\"you_have_not_favourites_yet\">Aún no hay favoritos</string>\n    <string name=\"add_to_favourites\">Añadir a favoritos</string>\n    <string name=\"add_new_category\">Nueva categoría</string>\n    <string name=\"add\">Añadir</string>\n    <string name=\"save\">Guardar</string>\n    <string name=\"share\">Compartir</string>\n    <string name=\"create_shortcut\">Crear acceso directo</string>\n    <string name=\"share_s\">Compartir %s</string>\n    <string name=\"search\">Buscar</string>\n    <string name=\"search_manga\">Buscar manga</string>\n    <string name=\"manga_downloading_\">Descargando…</string>\n    <string name=\"processing_\">Procesando…</string>\n    <string name=\"download_complete\">Descargado</string>\n    <string name=\"downloads\">Descargas</string>\n    <string name=\"by_name\">Nombre</string>\n    <string name=\"popular\">Popular</string>\n    <string name=\"updated\">Actualización</string>\n    <string name=\"newest\">Recientes</string>\n    <string name=\"by_rating\">Calificación</string>\n    <string name=\"sort_order\">Orden de clasificación</string>\n    <string name=\"filter\">Filtrar</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"light\">Claro</string>\n    <string name=\"dark\">Oscuro</string>\n    <string name=\"follow_system\">De acuerdo al sistema</string>\n    <string name=\"pages\">Páginas</string>\n    <string name=\"clear\">Limpiar</string>\n    <string name=\"remove\">Eliminar</string>\n    <string name=\"_s_deleted_from_local_storage\">«%s» borrado del almacenamiento local</string>\n    <string name=\"save_page\">Guardar página</string>\n    <string name=\"page_saved\">Página guardada</string>\n    <string name=\"share_image\">Compartir imagen</string>\n    <string name=\"_import\">Importar</string>\n    <string name=\"delete\">Borrar</string>\n    <string name=\"operation_not_supported\">Esta operación no está admitida</string>\n    <string name=\"text_file_not_supported\">Elija un archivo ZIP o bien un archivo CBZ.</string>\n    <string name=\"no_description\">Sin descripción</string>\n    <string name=\"clear_pages_cache\">Borrar caché de página</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Estándar</string>\n    <string name=\"webtoon\">Sitio web</string>\n    <string name=\"read_mode\">Modo de lectura</string>\n    <string name=\"grid_size\">Tamaño de la cuadrícula</string>\n    <string name=\"search_on_s\">Buscar en %s</string>\n    <string name=\"delete_manga\">Borrar manga</string>\n    <string name=\"text_delete_local_manga\">¿Borrar permanentemente \\\"%s\\\" del dispositivo?</string>\n    <string name=\"reader_settings\">Ajustes del lector</string>\n    <string name=\"switch_pages\">Cambiar de página</string>\n    <string name=\"_continue\">Continuar</string>\n    <string name=\"error\">Error</string>\n    <string name=\"clear_thumbs_cache\">Borrar la caché de miniaturas</string>\n    <string name=\"clear_search_history\">Borrar el historial de búsqueda</string>\n    <string name=\"search_history_cleared\">Historial de búsqueda borrado</string>\n    <string name=\"internal_storage\">Almacenamiento interno</string>\n    <string name=\"external_storage\">Almacenamiento externo</string>\n    <string name=\"domain\">Dominio</string>\n    <string name=\"app_update_available\">Una nueva versión de la aplicación está disponible</string>\n    <string name=\"open_in_browser\">Abrir en navegador web</string>\n    <string name=\"notifications\">Notificaciones</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Activado %1$d de %2$d</string>\n    <string name=\"new_chapters\">Nuevos capítulos</string>\n    <string name=\"download\">Descargar</string>\n    <string name=\"notifications_settings\">Configuración de las notificaciones</string>\n    <string name=\"notification_sound\">Sonido de las notificaciones</string>\n    <string name=\"light_indicator\">Indicador LED</string>\n    <string name=\"vibration\">Vibración</string>\n    <string name=\"favourites_categories\">Categorías favoritas</string>\n    <string name=\"remove_category\">Remover</string>\n    <string name=\"manga_shelf\">Estante</string>\n    <string name=\"recent_manga\">Reciente</string>\n    <string name=\"pages_animation\">Animación de página</string>\n    <string name=\"manga_save_location\">Carpeta de descargas</string>\n    <string name=\"not_available\">No disponible</string>\n    <string name=\"cannot_find_available_storage\">No hay almacenamiento disponible</string>\n    <string name=\"other_storage\">Otro almacenamiento</string>\n    <string name=\"done\">Aceptar</string>\n    <string name=\"all_favourites\">Todos los favoritos</string>\n    <string name=\"favourites_category_empty\">Categoría vacía</string>\n    <string name=\"read_later\">Leer más tarde</string>\n    <string name=\"updates\">Actualizaciones</string>\n    <string name=\"text_feed_holder\">Aquí se muestran los nuevos capítulos de lo que está leyendo</string>\n    <string name=\"search_results\">Resultados de la búsqueda</string>\n    <string name=\"new_version_s\">Nueva versión: %s</string>\n    <string name=\"size_s\">Tamaño: %s</string>\n    <string name=\"clear_updates_feed\">Borrar actualizaciones</string>\n    <string name=\"updates_feed_cleared\">Borrar el feed de actualizaciones</string>\n    <string name=\"rotate_screen\">Girar pantalla</string>\n    <string name=\"update\">Actualizar</string>\n    <string name=\"feed_will_update_soon\">La actualización de la alimentación comenzará pronto</string>\n    <string name=\"track_sources\">Comprueba las actualizaciones del manga</string>\n    <string name=\"dont_check\">No comprobar</string>\n    <string name=\"enter_password\">Introducir contraseña</string>\n    <string name=\"wrong_password\">Contraseña incorrecta</string>\n    <string name=\"protect_application\">Proteger aplicación</string>\n    <string name=\"protect_application_summary\">Pedir una contraseña al iniciar Kotatsu</string>\n    <string name=\"repeat_password\">Repite la contraseña</string>\n    <string name=\"passwords_mismatch\">Las contraseñas no coinciden</string>\n    <string name=\"about\">Acerca de</string>\n    <string name=\"app_version\">Versión %s</string>\n    <string name=\"check_for_updates\">Comprobar actualizaciones</string>\n    <string name=\"no_update_available\">No hay actualizaciones disponibles</string>\n    <string name=\"right_to_left\">De derecha a izquierda</string>\n    <string name=\"create_category\">Nueva categoría</string>\n    <string name=\"scale_mode\">Modo de escala</string>\n    <string name=\"zoom_mode_fit_center\">Ajuste al centro</string>\n    <string name=\"zoom_mode_fit_height\">Ajuste a la altura</string>\n    <string name=\"zoom_mode_fit_width\">Ajuste a la anchura</string>\n    <string name=\"zoom_mode_keep_start\">Mantener al iniciar</string>\n    <string name=\"black_dark_theme\">Tema oscuro auténtico</string>\n    <string name=\"black_dark_theme_summary\">Útil para pantallas AMOLED</string>\n    <string name=\"backup_restore\">Respaldo y restauración</string>\n    <string name=\"create_backup\">Crear copia de seguridad de datos</string>\n    <string name=\"restore_backup\">Restaurar desde la copia de seguridad</string>\n    <string name=\"data_restored\">Datos restaurados</string>\n    <string name=\"preparing_\">Preparando…</string>\n    <string name=\"file_not_found\">Archivo no encontrado</string>\n    <string name=\"data_restored_success\">Todos los datos fueron restaurados con éxito</string>\n    <string name=\"data_restored_with_errors\">Los datos fueron restaurados, pero hay errores</string>\n    <string name=\"backup_information\">Puedes crear una copia de seguridad de tu historial y favoritos para restaurarla</string>\n    <string name=\"just_now\">Ahora mismo</string>\n    <string name=\"yesterday\">Ayer</string>\n    <string name=\"long_ago\">Hace mucho tiempo</string>\n    <string name=\"group\">Grupo</string>\n    <string name=\"today\">Hoy</string>\n    <string name=\"tap_to_try_again\">Toca para volver a intentar</string>\n    <string name=\"reader_mode_hint\">Se recordará la configuración elegida para este manga</string>\n    <string name=\"silent\">Silenciar</string>\n    <string name=\"captcha_required\">El captcha es obligatorio</string>\n    <string name=\"captcha_solve\">Resolver</string>\n    <string name=\"clear_cookies\">Borrar cookies</string>\n    <string name=\"cookies_cleared\">Se han eliminado todas las cookies</string>\n    <string name=\"clear_feed\">Limpiar feed</string>\n    <string name=\"text_clear_updates_feed_prompt\">Todo el historial de actualizaciones se borrará y esta acción no se puede deshacer. ¿Está seguro\\?</string>\n    <string name=\"check_for_new_chapters\">Comprobación de nuevos capítulos</string>\n    <string name=\"reverse\">Invertir</string>\n    <string name=\"sign_in\">Iniciar sesión</string>\n    <string name=\"auth_required\">Debes autorizar para ver este contenido</string>\n    <string name=\"default_s\">Por defecto: %s</string>\n    <string name=\"next\">Siguiente</string>\n    <string name=\"protect_application_subtitle\">Introduce la contraseña que se requerirá cuando se inicie la aplicación</string>\n    <string name=\"confirm\">Confirmar</string>\n    <string name=\"password_length_hint\">La contraseña debe tener al menos 4 caracteres</string>\n    <string name=\"text_local_holder_secondary\">Guarda algo de un catálogo en línea o impórtalo desde un archivo.</string>\n    <string name=\"text_local_holder_primary\">Todavía no tienes ningún manga guardado</string>\n    <string name=\"text_history_holder_secondary\">Encuentra qué leer en la sección «Explorar»</string>\n    <string name=\"text_history_holder_primary\">Lo que leas se mostrará aquí</string>\n    <string name=\"text_empty_holder_primary\">Está un poco vacío aquí…</string>\n    <string name=\"chapter_is_missing\">Falta un capítulo</string>\n    <string name=\"about_app_translation_summary\">Traducir esta aplicación</string>\n    <string name=\"about_app_translation\">Traducción</string>\n    <string name=\"queued\">En la cola</string>\n    <string name=\"read_more\">Leer más</string>\n    <string name=\"backup_saved\">Copia de seguridad guardada correctamente</string>\n    <string name=\"welcome\">Bienvenido/a</string>\n    <string name=\"genres\">Géneros</string>\n    <string name=\"text_search_holder_secondary\">Intenta reformular la consulta.</string>\n    <string name=\"text_clear_search_history_prompt\">¿Realmente quiere eliminar todas las consultas de búsqueda recientes\\?</string>\n    <string name=\"state_finished\">Terminado</string>\n    <string name=\"state_ongoing\">En curso</string>\n    <string name=\"auth_complete\">Autorizado</string>\n    <string name=\"system_default\">Por defecto</string>\n    <string name=\"tracker_warning\">Algunos fabricantes pueden cambiar el comportamiento del sistema, lo que podría interrumpir las tareas en segundo plano.</string>\n    <string name=\"auth_not_supported_by\">No se admite iniciar sesión en %s</string>\n    <string name=\"text_clear_cookies_prompt\">Serás desconectado de todas las fuentes</string>\n    <string name=\"exclude_nsfw_from_history\">Excluye manga NSFW del historial</string>\n    <string name=\"show_pages_numbers\">Mostrar los números de páginas</string>\n    <string name=\"computing_\">Informática…</string>\n    <string name=\"screenshots_policy\">Política de capturas de pantalla</string>\n    <string name=\"screenshots_allow\">Permitir</string>\n    <string name=\"screenshots_block_all\">Bloquear siempre</string>\n    <string name=\"suggestions\">Sugerencias</string>\n    <string name=\"suggestions_enable\">Activar sugerencias</string>\n    <string name=\"suggestions_summary\">Sugiere mangas según tus preferencias</string>\n    <string name=\"suggestions_info\">Todos los datos sólo se analizan localmente en este dispositivo y nunca se envían a ninguna parte.</string>\n    <string name=\"text_suggestion_holder\">Empieza a leer manga y recibirás sugerencias personalizadas</string>\n    <string name=\"exclude_nsfw_from_suggestions\">No sugerir manga NSFW</string>\n    <string name=\"enabled\">Activado</string>\n    <string name=\"disabled\">Desactivado</string>\n    <string name=\"screenshots_block_nsfw\">Bloqueo en NSFW</string>\n    <string name=\"logged_in_as\">Conectado como %s</string>\n    <string name=\"onboard_text\">Selecciona los idiomas en los que quieres leer mangas. Puedes cambiarlo más tarde en los ajustes.</string>\n    <string name=\"reset_filter\">Reiniciar el filtro</string>\n    <string name=\"never\">Nunca</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"always\">Siempre</string>\n    <string name=\"preload_pages\">Precargar las páginas</string>\n    <string name=\"only_using_wifi\">Sólo en Wi-Fi</string>\n    <string name=\"various_languages\">Varios idiomas</string>\n    <string name=\"search_chapters\">Buscar capítulo</string>\n    <string name=\"chapters_empty\">No hay capítulos en este manga</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Aspecto</string>\n    <string name=\"suggestions_updating\">Actualización de las sugerencias</string>\n    <string name=\"suggestions_excluded_genres\">Excluir géneros</string>\n    <string name=\"suggestions_excluded_genres_summary\">Especifica los géneros que no quieres ver en las sugerencias</string>\n    <string name=\"removal_completed\">Remoción completada</string>\n    <string name=\"text_delete_local_manga_batch\">¿Eliminar elementos seleccionados del dispositivo de forma permanente\\?</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"download_slowdown\">Ralentización de la descarga</string>\n    <string name=\"new_sources_text\">Hay nuevas fuentes de manga disponibles</string>\n    <string name=\"download_slowdown_summary\">Ayuda a evitar el bloqueo de tu dirección IP</string>\n    <string name=\"local_manga_processing\">Procesamiento de manga guardado</string>\n    <string name=\"check_new_chapters_title\">Comprueba si hay nuevos capítulos y notifica sobre ellos</string>\n    <string name=\"show_notification_new_chapters_on\">Recibirás notificaciones sobre las actualizaciones del manga que estés leyendo</string>\n    <string name=\"bookmark_add\">Añadir marcador</string>\n    <string name=\"bookmarks\">Marcadores</string>\n    <string name=\"undo\">Deshacer</string>\n    <string name=\"bookmark_removed\">Marcador eliminado</string>\n    <string name=\"removed_from_history\">Eliminado del historial</string>\n    <string name=\"bookmark_remove\">Eliminar marcador</string>\n    <string name=\"bookmark_added\">Añadido a favoritos</string>\n    <string name=\"dns_over_https\">DNS mediante HTTPS</string>\n    <string name=\"default_mode\">Modo predeterminado</string>\n    <string name=\"detect_reader_mode\">Autodetectar modo de lectura</string>\n    <string name=\"detect_reader_mode_summary\">Detecta automáticamente si el manga es un webtoon</string>\n    <string name=\"chapters_will_removed_background\">Los capítulos se eliminarán en segundo plano</string>\n    <string name=\"show_notification_new_chapters_off\">No recibirás notificaciones, pero los nuevos capítulos aparecerán destacados en las listas</string>\n    <string name=\"notifications_enable\">Activar las notificaciones</string>\n    <string name=\"empty_favourite_categories\">No hay categorías favoritas</string>\n    <string name=\"name\">Nombre</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"edit_category\">Editar categoría</string>\n    <string name=\"disable_all\">Desactivar todo</string>\n    <string name=\"disable_battery_optimization\">Desactivar la optimización de la batería</string>\n    <string name=\"send\">Enviar</string>\n    <string name=\"crash_text\">Algo ha ido mal. Por favor, envía un informe de errores a los desarrolladores para ayudarnos a solucionarlo.</string>\n    <string name=\"use_fingerprint\">Usar la biométria si está disponible</string>\n    <string name=\"appwidget_shelf_description\">Mangas de tus favoritos</string>\n    <string name=\"appwidget_recent_description\">Sus mangas recientemente leídos</string>\n    <string name=\"logout\">Cerrar sesión</string>\n    <string name=\"status_planned\">Planeado</string>\n    <string name=\"status_reading\">Lectura</string>\n    <string name=\"status_dropped\">Abandonado</string>\n    <string name=\"status_on_hold\">En espera</string>\n    <string name=\"report\">Informar</string>\n    <string name=\"tracking\">Seguimiento</string>\n    <string name=\"disable_battery_optimization_summary\">Ayuda con las comprobaciones de las actualizaciones en segundo plano</string>\n    <string name=\"status_re_reading\">Relectura</string>\n    <string name=\"status_completed\">Completado</string>\n    <string name=\"show_reading_indicators\">Mostrar indicadores de progreso en la lectura</string>\n    <string name=\"data_deletion\">Eliminación de datos</string>\n    <string name=\"show_reading_indicators_summary\">Mostrar porcentaje de lectura en el historial y en los favoritos</string>\n    <string name=\"exclude_nsfw_from_history_summary\">El manga marcado como NSFW nunca se añadirá al historial y no se guardará tu progreso</string>\n    <string name=\"clear_cookies_summary\">Puede ayudar en caso de algunos problemas. Todas las autorizaciones serán invalidadas</string>\n    <string name=\"show_all\">Mostrar todo</string>\n    <string name=\"invalid_domain_message\">Dominio no válido</string>\n    <string name=\"account_already_exists\">La cuenta ya existe</string>\n    <string name=\"sync\">Sincronización</string>\n    <string name=\"email_enter_hint\">Introduce tu correo electrónico para continuar</string>\n    <string name=\"last_2_hours\">Últimas 2 horas</string>\n    <string name=\"no_bookmarks_yet\">Todavía no hay marcadores</string>\n    <string name=\"no_bookmarks_summary\">Puedes crear marcadores mientras lees el manga</string>\n    <string name=\"bookmarks_removed\">Marcadores eliminados</string>\n    <string name=\"no_manga_sources_text\">Habilitar las fuentes de manga para leer manga en línea</string>\n    <string name=\"random\">Aleatorio</string>\n    <string name=\"reorder\">Reordenar</string>\n    <string name=\"empty\">Vacío</string>\n    <string name=\"other_cache\">Otro caché</string>\n    <string name=\"storage_usage\">Uso del almacenamiento</string>\n    <string name=\"available\">Disponible</string>\n    <string name=\"removed_from_favourites\">Eliminado de los favoritos</string>\n    <string name=\"options\">Opciones</string>\n    <string name=\"incognito_mode\">Modo incógnito</string>\n    <string name=\"no_chapters\">No hay capítulos</string>\n    <string name=\"reader_info_bar\">Mostrar la barra de información en el lector</string>\n    <string name=\"importing_manga\">Importando el manga</string>\n    <string name=\"import_completed\">Importación completada</string>\n    <string name=\"import_will_start_soon\">La importación comenzará pronto</string>\n    <string name=\"canceled\">Cancelado</string>\n    <string name=\"back\">Atrás</string>\n    <string name=\"sync_title\">Sincronizar tus datos</string>\n    <string name=\"clear_all_history\">Borrar todo el historial</string>\n    <string name=\"no_manga_sources\">Sin fuentes de manga</string>\n    <string name=\"history_cleared\">Historial borrado</string>\n    <string name=\"manage\">Gestionar</string>\n    <string name=\"confirm_exit\">Pulsa de nuevo «Atrás» para salir</string>\n    <string name=\"folder_with_images\">Carpeta con imágenes</string>\n    <string name=\"categories_delete_confirm\">¿Estás seguro de que quieres eliminar las categorías favoritas seleccionadas? \\nTodos los mangas en ella se perderán y esto no se puede deshacer.</string>\n    <string name=\"explore\">Explorar</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"exit_confirmation_summary\">Pulse dos veces «Atrás» para salir de la aplicación</string>\n    <string name=\"exit_confirmation\">Confirmación de salida</string>\n    <string name=\"pages_cache\">Caché de páginas</string>\n    <string name=\"reader_info_pattern\">Cap.%1$d/%2$d Pg.%3$d/%4$d</string>\n    <string name=\"saved_manga\">Manga guardado</string>\n    <string name=\"automatic_scroll\">Desplazamiento automático</string>\n    <string name=\"comics_archive\">Archivo de cómics</string>\n    <string name=\"import_completed_hint\">Puedes eliminar el archivo original del almacenamiento para ahorrar espacio</string>\n    <string name=\"select_range\">Selecciona el rango</string>\n    <string name=\"not_found_404\">Contenido no encontrado o eliminado</string>\n    <string name=\"manga_error_description_pattern\">Detalles del error:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Intenta &lt;a href=%2$s&gt;abrir el manga en un navegador web&lt;/a&gt; para asegurarte de que esté disponible en tu fuente&lt;br&gt;2. Asegúrete de estar utilizando la &lt;a href=kotatsu://about&gt;última versión de Kotatsu&lt;/a&gt;&lt;br&gt;3. Si está disponible, envía un informe de error a los desarrolladores.</string>\n    <string name=\"history_shortcuts_summary\">Hacer que los mangas recientes estén disponibles mediante una pulsación larga en el icono de la aplicación</string>\n    <string name=\"feed\">Fuente</string>\n    <string name=\"history_shortcuts\">Mostrar los accesos directos a los mangas recientes</string>\n    <string name=\"reader_control_ltr_summary\">No modifique el método de paso de página al modo de lectura ya configurado. Por ejemplo: presionar el botón derecho siempre pasa a la página siguiente. Esta configuración solo es válida para dispositivos de hardware</string>\n    <string name=\"reader_control_ltr\">Control ergonómico del lector</string>\n    <string name=\"color_correction\">Corrección del color</string>\n    <string name=\"brightness\">Brillo</string>\n    <string name=\"contrast\">Contraste</string>\n    <string name=\"reset\">Restablecer</string>\n    <string name=\"text_unsaved_changes_prompt\">¿Guardar o descartar los cambios no guardados\\?</string>\n    <string name=\"discard\">Descartar</string>\n    <string name=\"error_no_space_left\">Sin espacio en dispositivo</string>\n    <string name=\"webtoon_zoom\">Zoom de webtoon</string>\n    <string name=\"reader_slider\">Mostrar el deslizador de cambio de página</string>\n    <string name=\"server_error\">Error del servidor (%1$d). Vuelva a intentarlo más tarde</string>\n    <string name=\"clear_new_chapters_counters\">Información clara sobre los nuevos capítulos</string>\n    <string name=\"network_unavailable\">La red no está disponible</string>\n    <string name=\"compact\">Compacta</string>\n    <string name=\"network_unavailable_hint\">Enciende la Wi-Fi o la red móvil para leer los mangas en línea</string>\n    <string name=\"source_disabled\">Fuente desactivada</string>\n    <string name=\"prefetch_content\">Precargar el contenido</string>\n    <string name=\"mark_as_current\">Marcar como actual</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"share_logs\">Compartir los registros</string>\n    <string name=\"enable_logging_summary\">Grabar algunas interaciones para de depurar los fallos. No lo actives si no estás seguro de lo que estás haciendo</string>\n    <string name=\"enable_logging\">Activar el registro</string>\n    <string name=\"show_suspicious_content\">Mostrar contenido sospechoso</string>\n    <string name=\"theme_name_dynamic\">Dinámico</string>\n    <string name=\"color_theme\">Esquema de colores</string>\n    <string name=\"show_in_grid_view\">Mostrar en vista de cuadrícula</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"nothing_here\">No hay nada aquí</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Canadé</string>\n    <string name=\"scrobbling_empty_hint\">Para realizar un seguimiento del progreso de la lectura, seleccione Menú → Seguimiento en la pantalla de detalles del manga.</string>\n    <string name=\"services\">Servicios</string>\n    <string name=\"allow_unstable_updates_summary\">Recibir notificaciones sobre versiones inestables</string>\n    <string name=\"allow_unstable_updates\">Permitir actualizaciones inestables</string>\n    <string name=\"download_started\">Descarga iniciada</string>\n    <string name=\"user_agent\">Encabezado del agente de usuario</string>\n    <string name=\"settings_apply_restart_required\">Por favor, reinicie la aplicación para aplicar estos cambios</string>\n    <string name=\"sources_reorder_tip\">Mantén pulsado un elemento para reordenarlos</string>\n    <string name=\"comics_archive_import_description\">Puedes seleccionar uno o más archivos .cbz o .zip, cada archivo será reconocido como un manga independiente.</string>\n    <string name=\"folder_with_images_import_description\">Puedes seleccionar un directorio con unos archivos o imágenes. Cada archivo (o subdirectorio) se reconocerá como un capítulo.</string>\n    <string name=\"speed\">Velocidad</string>\n    <string name=\"show_on_shelf\">Mostrar en la estantería</string>\n    <string name=\"got_it\">Entendido</string>\n    <string name=\"sync_auth_hint\">Puedes acceder a una cuenta existente o crear una nueva</string>\n    <string name=\"find_similar\">Buscar similares</string>\n    <string name=\"sync_settings\">Parámetros de sincronización</string>\n    <string name=\"server_address\">Dirección del servidor</string>\n    <string name=\"ignore_ssl_errors\">Ignorar los errores SSL</string>\n    <string name=\"mirror_switching\">Elige un espejo automáticamente</string>\n    <string name=\"mirror_switching_summary\">Cambio automático de los dominios para las fuentes del manga en caso de error si hay réplicas disponibles</string>\n    <string name=\"pause\">Pausar</string>\n    <string name=\"resume\">Reanudar</string>\n    <string name=\"remove_completed\">Eliminación completa</string>\n    <string name=\"cancel_all\">Cancelar todo</string>\n    <string name=\"downloads_wifi_only\">Descargar solo por WiFi</string>\n    <string name=\"downloads_wifi_only_summary\">Detener la descarga al cambiar a una red móvil</string>\n    <string name=\"sync_host_description\">Puedes utilizar un servidor de sincronización propio o uno predeterminado. No cambies esto si no estás seguro de lo que haces.</string>\n    <string name=\"paused\">Pausado</string>\n    <string name=\"enable\">Activar</string>\n    <string name=\"no_thanks\">No, gracias</string>\n    <string name=\"text_downloads_list_holder\">No tienes descargas</string>\n    <string name=\"suggestion_manga\">Sugerencia: %s</string>\n    <string name=\"suggestions_notifications_summary\">Algunas veces muestra las notificaciones con el manga sugerido</string>\n    <string name=\"downloads_resumed\">Las descargas se han reanudado</string>\n    <string name=\"more\">Más</string>\n    <string name=\"cancel_all_downloads_confirm\">Todas las descargas activas se cancelarán, los datos descargados parcialmente se perderán</string>\n    <string name=\"remove_completed_downloads_confirm\">Tu historial de descargas se eliminará de forma permanente. Ningún archivo descargado se verá afectado</string>\n    <string name=\"downloads_paused\">Se han pausado las descargas</string>\n    <string name=\"downloads_removed\">Las descargas se han eliminado</string>\n    <string name=\"downloads_cancelled\">Las descargas se han cancelado</string>\n    <string name=\"suggestions_enable_prompt\">¿Quieres recibir sugerencias sobre mangas personalizadas\\?</string>\n    <string name=\"web_view_unavailable\">WebView no está disponible: comprueba si el proveedor de WebView está instalado</string>\n    <string name=\"clear_network_cache\">Borrar la caché de la red</string>\n    <string name=\"type\">Tipo</string>\n    <string name=\"address\">Dirección</string>\n    <string name=\"port\">Puerto</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"invalid_value_message\">Valor no válido</string>\n    <string name=\"images_proxy_title\">Optimización de imágenes proxy</string>\n    <string name=\"username\">Nombre de usuario</string>\n    <string name=\"downloaded\">Descargado</string>\n    <string name=\"authorization_optional\">Autorización (opcional)</string>\n    <string name=\"images_procy_description\">Utiliza el servicio wsrv.nl para reducir el uso del tráfico y acelerar la carga de imágenes si es posible</string>\n    <string name=\"password\">Contraseña</string>\n    <string name=\"invert_colors\">Invertir los colores</string>\n    <string name=\"invalid_port_number\">Número de puerto incorrecto</string>\n    <string name=\"network\">Red</string>\n    <string name=\"data_and_privacy\">Datos y privacidad</string>\n    <string name=\"webtoon_zoom_summary\">Permitir el gesto de acercamiento en el modo webtoon</string>\n    <string name=\"show_pages_numbers_summary\">Mostrar el números de la página en la esquina inferior</string>\n    <string name=\"restore_summary\">Restaurar una copia de seguridad creada anteriormente</string>\n    <string name=\"reader_info_bar_summary\">Muestra la hora actual y el progreso de la lectura en la parte superior de la pantalla</string>\n    <string name=\"clear_source_cookies_summary\">Borrar las cookies solo para el dominio especificado. En la mayoría de los casos invalidará la autorización</string>\n    <string name=\"download_option_whole_manga\">El manga completo</string>\n    <string name=\"download_option_first_n_chapters\">Primero %s</string>\n    <string name=\"download_option_all_unread\">Todos los capítulos sin leer</string>\n    <string name=\"download_option_all_unread_b\">Todos los capítulos sin leer (%s)</string>\n    <string name=\"download_option_manual_selection\">Selección manual de los capítulos</string>\n    <string name=\"download_option_all_chapters\">Todos los capítulos con traducción %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Siguiente %s sin leer</string>\n    <string name=\"pick_custom_directory\">Elegir un directorio personalizado</string>\n    <string name=\"no_access_to_file\">No tienes acceso a este archivo o directorio</string>\n    <string name=\"local_manga_directories\">Directorios locales del manga</string>\n    <string name=\"description\">Descripción</string>\n    <string name=\"this_month\">Este mes</string>\n    <string name=\"voice_search\">Buscar por voz</string>\n    <string name=\"related_manga\">Manga relacionado</string>\n    <string name=\"color_light\">Claro</string>\n    <string name=\"color_dark\">Oscuro</string>\n    <string name=\"color_white\">Blanco</string>\n    <string name=\"color_black\">Negro</string>\n    <string name=\"data_not_restored\">Los datos no se han restaurado</string>\n    <string name=\"data_not_restored_text\">Asegúrate de haber seleccionado el archivo de la copia de seguridad correcto</string>\n    <string name=\"background\">Fondo</string>\n    <string name=\"tracker_wifi_only_summary\">No compruebe si hay nuevos capítulos utilizando conexiones de red medidas</string>\n    <string name=\"search_hint\">Introduce el título del manga, el género o el nombre de la fuente</string>\n    <string name=\"progress\">Progresos</string>\n    <string name=\"order_added\">Añadido</string>\n    <string name=\"manage_categories\">Gestionar las categorías</string>\n    <string name=\"show\">Mostrar</string>\n    <string name=\"captcha_required_summary\">%s requiere un captcha para funcionar correctamente</string>\n    <string name=\"languages\">Idiomas</string>\n    <string name=\"suggestions_wifi_only_summary\">No actualices las sugerencias utilizando conexiones de red medidas</string>\n    <string name=\"unknown\">Desconocido</string>\n    <string name=\"in_progress\">En proceso</string>\n    <string name=\"disable_nsfw\">Desactivar NSFW</string>\n    <string name=\"too_many_requests_message\">Demasiadas solicitudes. Vuelve a intentarlo más tarde</string>\n    <string name=\"related_manga_summary\">Muestra una lista de mangas relacionados. En algunos casos puede ser inexacta o faltar</string>\n    <string name=\"advanced\">Avanzado</string>\n    <string name=\"manga_list\">Lista de mangas</string>\n    <string name=\"error_corrupted_file\">Los datos que devuelve el archivo no son válidos o el archivo está dañado</string>\n    <string name=\"on_device\">En el dispositivo</string>\n    <string name=\"moved_to_top\">Movido hacia arriba</string>\n    <string name=\"items_limit_exceeded\">No se pueden añadir más elementos</string>\n    <string name=\"directories\">Directorios</string>\n    <string name=\"main_screen_sections\">Secciones de la pantalla principal</string>\n    <string name=\"to_top\">Hasta arriba</string>\n    <string name=\"zoom_in\">Acercar</string>\n    <string name=\"reader_zoom_buttons_summary\">Mostrar o no los botones de control del zoom en la esquina inferior derecha</string>\n    <string name=\"reader_zoom_buttons\">Mostrar los botones del zoom</string>\n    <string name=\"zoom_out\">Alejar</string>\n    <string name=\"keep_screen_on\">Mantener pantalla encendida</string>\n    <string name=\"keep_screen_on_summary\">No apagues la pantalla mientras lees manga</string>\n    <string name=\"state_abandoned\">Abandonó</string>\n    <string name=\"suggest_new_sources\">Sugerir nuevas fuentes tras actualizar la aplicación</string>\n    <string name=\"enhanced_colors\">Modo de color de 32 bits</string>\n    <string name=\"suggest_new_sources_summary\">Solicitud para habilitar las nuevas fuentes añadidas tras actualizar la aplicación</string>\n    <string name=\"categories\">Categorías</string>\n    <string name=\"list_options\">Lista de opciones</string>\n    <string name=\"enhanced_colors_summary\">Reduce el banding, pero puede afectar al rendimiento</string>\n    <string name=\"by_relevance\">Relevancia</string>\n    <string name=\"online_variant\">Variante en línea</string>\n    <string name=\"frequency_every_day\">Cada día</string>\n    <string name=\"backup_frequency\">Frecuencia de creación de las copias de seguridad</string>\n    <string name=\"periodic_backups_enable\">Activar las copias de seguridad periódicas</string>\n    <string name=\"frequency_every_2_days\">Cada 2 días</string>\n    <string name=\"frequency_once_per_week\">Una vez a la semana</string>\n    <string name=\"periodic_backups\">Copias de seguridad periódicas</string>\n    <string name=\"frequency_twice_per_month\">Dos veces al mes</string>\n    <string name=\"frequency_once_per_month\">Una vez al mes</string>\n    <string name=\"backups_output_directory\">Directorio para guardar la copia de seguridad</string>\n    <string name=\"last_successful_backup\">La última copia de seguridad correcta: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">Rotación de la pantalla de bloqueo</string>\n    <string name=\"sources_catalog\">Catálogo de las fuentes</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Cómic</string>\n    <string name=\"no_manga_sources_found\">No se han encontrado fuentes de manga disponibles en su búsqueda</string>\n    <string name=\"source_enabled\">Fuente habilitada</string>\n    <string name=\"no_manga_sources_catalog_text\">No hay fuentes disponibles en esta sección, o puede que ya se hayan añadido todas.\n\\nPermanezca atento</string>\n    <string name=\"content_type_other\">Otros</string>\n    <string name=\"catalog\">Catálogo</string>\n    <string name=\"manage_sources\">Gestionar las fuentes</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"disable_nsfw_summary\">Desactivar las fuentes NSFW y ocultar el manga para adultos de la lista si es posible</string>\n    <string name=\"available_d\">Disponible: %1$d</string>\n    <string name=\"state_paused\">Pausado</string>\n    <string name=\"error_multiple_states_not_supported\">El filtrado por múltiples estados no es compatible con esta fuente del manga</string>\n    <string name=\"reader_optimize\">Reducir el consumo de memoria (beta)</string>\n    <string name=\"error_multiple_genres_not_supported\">Esta fuente del manga no permite filtrar por varios géneros</string>\n    <string name=\"error_search_not_supported\">La búsqueda no es compatible con esta fuente del manga</string>\n    <string name=\"reader_optimize_summary\">Reduce la resolución de las páginas fuera de pantalla para usar menos memoria</string>\n    <string name=\"state\">Estado</string>\n    <string name=\"downloads_settings_info\">Puedes activar la ralentización de la descarga para cada fuente de manga individualmente en la configuración de la fuente si tienes problemas con el bloqueo del servidor</string>\n    <string name=\"apply\">Aplicar</string>\n    <string name=\"globally\">Globalmente</string>\n    <string name=\"this_manga\">Este manga</string>\n    <string name=\"skip\">Omitir</string>\n    <string name=\"color_correction_apply_text\">Estos ajustes pueden aplicarse globalmente o sólo al manga actual. Si se aplican globalmente, los ajustes individuales no se anularán.</string>\n    <string name=\"grayscale\">Escala de grises</string>\n    <string name=\"error_filter_states_genre_not_supported\">Esta fuente no permite filtrar por géneros ni por estados</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Esta fuente no permite filtrar por géneros ni por configuración regional</string>\n    <string name=\"genres_search_hint\">Empieza a escribir el nombre del género</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Puede que te ayude a iniciar la descarga si tienes algún problema</string>\n    <string name=\"welcome_text\">Por favor, seleccione las fuentes de contenido que desea habilitar. Esto también se puede configurar más tarde en la configuración</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"backup_date_\">Fecha de la copia de seguridad: %s</string>\n    <string name=\"sync_auth\">Iniciar sesión en la cuenta de sincronización</string>\n    <string name=\"by_name_reverse\">Nombre invertido</string>\n    <string name=\"state_upcoming\">Próximamente</string>\n    <string name=\"rating_safe\">Seguro</string>\n    <string name=\"rating_suggestive\">Sugestivo</string>\n    <string name=\"genres_exclude\">Excluir los géneros</string>\n    <string name=\"rating_adult\">Adulto</string>\n    <string name=\"content_rating\">Clasificación del contenido</string>\n    <string name=\"default_tab\">Pestaña por defecto</string>\n    <string name=\"mark_as_completed\">Marcar como completado</string>\n    <string name=\"mark_as_completed_prompt\">¿Marcar el manga seleccionado como completamente leído?\n\\n\n\\nAdvertencia: el progreso de lectura actual se perderá.</string>\n    <string name=\"category_hidden_done\">Esta categoría estaba oculta en la pantalla principal y es accesible a través de Menú → Gestionar categorías</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"volume_unknown\">Volumen desconocido</string>\n    <string name=\"volume_\">Volumen %d</string>\n    <string name=\"incognito_mode_hint\">Tu progreso en la lectura no se guardará</string>\n    <string name=\"vertical\">Vertical</string>\n    <string name=\"last_read\">Último leído</string>\n    <string name=\"show_menu\">Mostrar menú</string>\n    <string name=\"toggle_ui\">Mostrar/esconder interfaz</string>\n    <string name=\"next_chapter\">Siguiente capítulo</string>\n    <string name=\"prev_page\">Página anterior</string>\n    <string name=\"next_page\">Página siguiente</string>\n    <string name=\"reader_actions\">Acciones del lector</string>\n    <string name=\"reader_actions_summary\">Configurara acciones para zonas clickeables</string>\n    <string name=\"switch_pages_volume_buttons\">Activar botones de volumen</string>\n    <string name=\"tap_action\">Acción de click</string>\n    <string name=\"long_tap_action\">Acción de click largo</string>\n    <string name=\"prev_chapter\">Capítulo anterior</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Utilizar botones de volumen para cambiar de página</string>\n    <string name=\"email_password_enter_hint\">Introduzca su correo electrónico y contraseña para continuar</string>\n    <string name=\"use_two_pages_landscape\">Utilizar un diseño de dos páginas en orientación horizontal (beta)</string>\n    <string name=\"none\">Ninguno</string>\n    <string name=\"config_reset_confirm\">¿Restablecer los valores por defecto? Esta acción no se puede deshacer.</string>\n    <string name=\"default_webtoon_zoom_out\">Alejar el zoom del webtoon predeterminado</string>\n    <string name=\"fullscreen_mode\">Modo de pantalla completa</string>\n    <string name=\"reader_fullscreen_summary\">Ocultar las barras de estado y navegación del sistema</string>\n    <string name=\"reading_time_estimation\">Mostrar el tiempo estimado de lectura</string>\n    <string name=\"reading_time_estimation_summary\">El valor estimado puede ser inexacto</string>\n    <string name=\"check_for_new_chapters_disabled\">La búsqueda de nuevos capítulos está desactivada</string>\n    <string name=\"suggestions_unavailable_text\">Sugerencias desactivadas</string>\n    <string name=\"show_labels_in_navbar\">Mostrar etiquetas en la barra de navegación</string>\n    <string name=\"ask_for_dest_dir_every_time\">Preguntar siempre por el directorio de destino</string>\n    <string name=\"remove_from_history\">Eliminar del historial</string>\n    <string name=\"pages_saving\">Guardando páginas</string>\n    <string name=\"default_page_save_dir\">Directorio predeterminado para guardar páginas</string>\n    <string name=\"location\">Ubicación</string>\n    <string name=\"automatic\">Automático</string>\n    <string name=\"single_cbz_file\">Un único archivo CBZ</string>\n    <string name=\"multiple_cbz_files\">Varios archivos CBZ</string>\n    <string name=\"preferred_download_format\">Formato preferido para la descarga</string>\n    <string name=\"reading_stats\">Estadística de lectura</string>\n    <string name=\"other_manga\">Otros mangas</string>\n    <string name=\"less_than_minute\">Menos de un minuto</string>\n    <string name=\"statistics\">Estadísticas</string>\n    <string name=\"stats_cleared\">Estadísticas borradas</string>\n    <string name=\"week\">Semana</string>\n    <string name=\"all_time\">Siempre</string>\n    <string name=\"empty_stats_text\">No hay estadísticas para el periodo seleccionado</string>\n    <string name=\"pages_read_s\">Páginas leídas: %s</string>\n    <string name=\"clear_stats\">Borrar las estadísticas</string>\n    <string name=\"clear_stats_confirm\">¿De verdad quieres borrar todas las estadísticas de lectura? Esta acción no se puede deshacer.</string>\n    <string name=\"month\">Mes</string>\n    <string name=\"day\">Día</string>\n    <string name=\"three_months\">Tres meses</string>\n    <string name=\"delete_read_chapters\">Borrar capítulos leídos</string>\n    <string name=\"no_chapters_deleted\">No se han eliminado capítulos</string>\n    <string name=\"chapters_deleted_pattern\">Eliminado %1$s, borrado %2$s</string>\n    <string name=\"delete_read_chapters_summary\">Borrar los capítulos que ya has leído de la memoria local para liberar espacio</string>\n    <string name=\"delete_read_chapters_prompt\">Esto borrará permanentemente todos los capítulos marcados como leídos de tu almacenamiento local. Puede volver a descargarlo más tarde, pero los capítulos importados se perderán para siempre</string>\n    <string name=\"alternatives\">Alternativas</string>\n    <string name=\"migrate\">Migrar</string>\n    <string name=\"migrate_confirmation\">El manga \\\"%1$s\\\" de \\\"%2$s\\\" será sustituido por \\\"%3$s\\\" de \\\"%4$s\\\" en tu historial y favoritos (si existen)</string>\n    <string name=\"manga_migration\">Migración del manga</string>\n    <string name=\"migration_completed\">Migración realizada</string>\n    <string name=\"delete_read_chapters_auto\">Borrar automáticamente los capítulos leídos</string>\n    <string name=\"runs_on_app_start\">Se ejecuta cuando se inicia la aplicación</string>\n    <string name=\"chapters_grid_view\">Vista en cuadrícula</string>\n    <string name=\"split_by_translations\">Desglose por traducciones</string>\n    <string name=\"split_by_translations_summary\">Mostrar los capítulos con diferentes traducciones por separado, en lugar de en una sola lista</string>\n    <string name=\"order_oldest\">Más antigua</string>\n    <string name=\"long_ago_read\">Leído hace mucho tiempo</string>\n    <string name=\"unread\">Sin leer</string>\n    <string name=\"enable_source\">Activar fuente</string>\n    <string name=\"unsupported_source\">Esta fuente del manga no es compatible</string>\n    <string name=\"show_pages_thumbs\">Mostrar miniaturas de las páginas</string>\n    <string name=\"show_pages_thumbs_summary\">Habilite la pestaña \\\"Páginas\\\" en la pantalla de detalles</string>\n    <string name=\"error_no_data_received\">No se recibieron datos del servidor</string>\n    <string name=\"unsupported_backup_message\">Por favor, seleccione un archivo de copia de seguridad de Kotatsu adecuado</string>\n    <string name=\"hours_short\">%d hr.</string>\n    <string name=\"hours_minutes_short\">%1$d hr. %2$d min</string>\n    <string name=\"minutes_short\">%d min</string>\n    <string name=\"fix\">Solucionar</string>\n    <string name=\"missing_storage_permission\">No hay permiso para acceder al manga en el almacenamiento externo</string>\n    <string name=\"last_used\">Utilizado por última vez</string>\n    <string name=\"show_updated\">Mostrar lo actualizado</string>\n    <string name=\"webtoon_gaps_summary\">Mostrar espacios verticales entre páginas en modo webtoon</string>\n    <string name=\"webtoon_gaps\">Huecos en el modo webtoon</string>\n    <string name=\"search_suggestions\">Sugerencias de búsqueda</string>\n    <string name=\"recent_queries\">Consultas recientes</string>\n    <string name=\"suggested_queries\">Consultas sugeridas</string>\n    <string name=\"authors\">Autores</string>\n    <string name=\"less_frequently\">Poco frecuente</string>\n    <string name=\"more_frequently\">Más frecuentemente</string>\n    <string name=\"frequency_of_check\">Frecuencia del control</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui_summary\">No ocultar la barra de navegación y la vista de búsqueda al desplazarse</string>\n    <string name=\"pin_navigation_ui\">Pin de la interfaz de usuario</string>\n    <string name=\"blocked_by_server_message\">Estás bloqueado por el servidor. Intente utilizar una conexión de red diferente (VPN, Proxy, etc.)</string>\n    <string name=\"disable\">Desactivar</string>\n    <string name=\"sources_disabled\">Fuentes deshabilitadas</string>\n    <string name=\"disable_connectivity_check\">Desactivar el control de conectividad</string>\n    <string name=\"ignore_ssl_errors_summary\">Puede desactivar la verificación de certificados SSL en caso de que tenga problemas relacionados con SSL al acceder a recursos de red. Esto puede afectar a su seguridad. Es necesario reiniciar la aplicación después de cambiar esta configuración.</string>\n    <string name=\"disable_connectivity_check_summary\">Omitir la comprobación de la conectividad en caso de que tenga problemas con ella (por ejemplo, si pasa al modo sin conexión aunque la red esté conectada)</string>\n    <string name=\"disable_nsfw_notifications\">Deshabilitar notificaciones NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">No mostrar notificaciones sobre actualizaciones de manga NSFW</string>\n    <string name=\"tracker_debug_info\">Comprobando el registro de nuevos capítulos</string>\n    <string name=\"tracker_debug_info_summary\">Información de depuración sobre verificaciones de antecedentes para nuevos capítulos</string>\n    <string name=\"_new\">Nuevos</string>\n    <string name=\"all_languages\">Todos los idiomas</string>\n    <string name=\"screenshots_block_incognito\">Bloquear en modo incógnito</string>\n    <string name=\"image_server\">Servidor de imágenes preferido</string>\n    <string name=\"crop_pages\">Páginas de recortes</string>\n    <string name=\"recent_sources\">Fuentes recientes</string>\n    <string name=\"pin\">Fijar</string>\n    <string name=\"unpin\">No fijar</string>\n    <string name=\"source_pinned\">Fuente fijada</string>\n    <string name=\"source_unpinned\">Fuente no fijada</string>\n    <string name=\"sources_unpinned\">Fuentes no fijadas</string>\n    <string name=\"sources_pinned\">Fuentes ancladas</string>\n    <string name=\"percent_read\">Porcentaje leído</string>\n    <string name=\"percent_left\">Porcentaje restante</string>\n    <string name=\"chapters_read\">Capítulos leídos</string>\n    <string name=\"chapters_left\">Capítulos restantes</string>\n    <string name=\"external_source\">Externo/plugin</string>\n    <string name=\"plugin_incompatible\">Complemento incompatible o error interno. Asegúrate de estar usando la última versión del complemento y de Kotatsu</string>\n    <string name=\"connection_ok\">La conexión está bien</string>\n    <string name=\"text_empty_holder_secondary_filtered\">No hay manga que coincida con los filtros que seleccionaste</string>\n    <string name=\"invalid_proxy_configuration\">Configuración del proxy no válida</string>\n    <string name=\"show_quick_filters\">Mostrar filtros rápidos</string>\n    <string name=\"show_quick_filters_summary\">Proporciona la posibilidad de filtrar listas de manga por ciertos parámetros</string>\n    <string name=\"invalid_server_address_message\">Dirección del servidor no válida</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"too_many_requests_message_retry\">Demasiadas peticiones. Inténtalo de nuevo después de %s</string>\n    <string name=\"retry\">Reintentar</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"skip_all\">Omitir todo</string>\n    <string name=\"stuck\">Fijado</string>\n    <string name=\"not_in_favorites\">No está en favoritos</string>\n    <string name=\"updated_long_ago\">Actualizado hace mucho tiempo</string>\n    <string name=\"unpopular\">Impopular</string>\n    <string name=\"low_rating\">Valoración baja</string>\n    <string name=\"sort_order_asc\">Ascendente</string>\n    <string name=\"sort_order_desc\">Descendente</string>\n    <string name=\"by_date\">Fecha</string>\n    <string name=\"popularity\">Popularidad</string>\n    <string name=\"scrobbler_auth_required\">Iniciar sesión en %s para continuar</string>\n    <string name=\"scrobbler_auth_intro\">Inicia sesión para configurar la integración con %s . Esto te permitirá seguir tu progreso de lectura del manga</string>\n    <string name=\"unstable_feature\">Función inestable</string>\n    <string name=\"unstable_feature_summary\">Esta función es experimental. Por favor, asegúrate de tener una copia de seguridad para evitar la pérdida de datos</string>\n    <string name=\"downloads_background\">Descargas en segundo plano</string>\n    <string name=\"download_new_chapters\">Descargar nuevos capítulos</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga con capítulos descargados</string>\n    <string name=\"fixing_manga\">Corregir el manga</string>\n    <string name=\"no_fix_required\">No es necesario corregir \\\"%s\\\"</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) sustituido con \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"fixed\">Corregido correctamente</string>\n    <string name=\"no_alternatives_found\">No se han encontrado alternativas para \\\"%s\\\"</string>\n    <string name=\"manga_fix_prompt\">Esto buscará fuentes alternativas para el manga seleccionado. La tarea tardará un tiempo y se realizará en segundo plano</string>\n    <string name=\"popular_in_hour\">Populares ahora</string>\n    <string name=\"popular_today\">Populares hoy</string>\n    <string name=\"popular_in_week\">Populares esta semana</string>\n    <string name=\"popular_in_month\">Populares este mes</string>\n    <string name=\"demographic_shounen\">Shōnen</string>\n    <string name=\"demographic_shoujo\">Shōjo</string>\n    <string name=\"years\">Años</string>\n    <string name=\"any\">Cualquiera</string>\n    <string name=\"content_type_novel\">Novedoso</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"added_long_ago\">Añadido hace tiempo</string>\n    <string name=\"recently_added\">Añadido recientemente</string>\n    <string name=\"popular_in_year\">Populares este año</string>\n    <string name=\"original_language\">Idioma original</string>\n    <string name=\"year\">Año</string>\n    <string name=\"demographics\">Demografía</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"filter_search_warning\">Esta fuente no admite búsquedas con filtros. Se han eliminado los filtros</string>\n    <string name=\"content_type_one_shot\">Un disparo</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_artist_cg\">Artista CG</string>\n    <string name=\"content_type_doujinshi\">Dōjinshi</string>\n    <string name=\"content_type_game_cg\">Juego CG</string>\n    <string name=\"content_type_image_set\">Conjunto de imágenes</string>\n    <string name=\"user_manual\">Manual de usuario</string>\n    <string name=\"source_code\">Código fuente</string>\n    <string name=\"telegram_group\">Grupo de Telegram</string>\n    <string name=\"debug\">Depurar</string>\n    <string name=\"error_image_format\">Formato de imagen no compatible: %s</string>\n    <string name=\"start_download\">Iniciar descarga</string>\n    <string name=\"save_manga\">Guardar manga</string>\n    <string name=\"save_manga_confirm\">¿Guardar el manga seleccionado? Esto puede consumir tráfico y espacio en disco</string>\n    <string name=\"genre\">Género</string>\n    <string name=\"more_options\">Más opciones</string>\n    <string name=\"chapter_selection_hint\">Puede seleccionar capítulos para descargar haciendo una pulsación prolongada en el elemento de la lista de capítulos.</string>\n    <string name=\"chapters_all\">Todos</string>\n    <string name=\"download_added\">Descarga añadida</string>\n    <string name=\"destination_directory\">Directorio de destino</string>\n    <string name=\"dont_allow\">No permitir</string>\n    <string name=\"allow_always\">Permitir siempre</string>\n    <string name=\"allow_once\">Una vez</string>\n    <string name=\"ask_every_time\">Preguntar siempre</string>\n    <string name=\"download_over_cellular\">Descarga a través de la red móvil</string>\n    <string name=\"download_cellular_confirm\">¿Permitir descargas a través de la red móvil?</string>\n    <string name=\"screen_orientation\">Orientación de la pantalla</string>\n    <string name=\"landscape\">Horizontal</string>\n    <string name=\"portrait\">Vertical</string>\n    <string name=\"plugin_incompatible_with_cause\">Error del plugin: %s\\n Asegúrese de que está utilizando la última versión del plugin y Kotatsu</string>\n    <string name=\"access_denied_403\">Acceso denegado (403)</string>\n    <string name=\"max_backups_count\">Número máximo de copias de seguridad</string>\n    <string name=\"error_not_image\">Formato no válido: se esperaba una imagen pero se obtuvo %s</string>\n    <string name=\"pages_saved\">Páginas guardadas</string>\n    <string name=\"delete_old_backups_summary\">Elimina automáticamente los archivos de copia de seguridad antiguos para ahorrar espacio de almacenamiento</string>\n    <string name=\"delete_old_backups\">Eliminar copias de seguridad antiguas</string>\n    <string name=\"email\">Correo electrónico</string>\n    <string name=\"handle_links\">Gestionar enlaces</string>\n    <string name=\"handle_links_summary\">Gestionar enlaces de manga desde aplicaciones externas (por ejemplo, navegador web). También puede ser necesario habilitarlo manualmente en la configuración de la aplicación</string>\n    <string name=\"captcha_required_message\">Esta fuente requiere resolver un captcha para continuar</string>\n    <string name=\"incognito\">Incógnito</string>\n    <string name=\"telegram_chat_id\">Identificación del chat de Telegram</string>\n    <string name=\"backup_tg_echo\">¡La copia de seguridad de Kotatsu en Telegram está funcionando!</string>\n    <string name=\"backup_tg_id_not_set\">El ID del chat no está configurado</string>\n    <string name=\"backup_tg_check\">Comprueba si la API funciona</string>\n    <string name=\"error_connection_reset\">Conexión restablecida por el host remoto</string>\n    <string name=\"show_slider\">Mostrar control deslizante</string>\n    <string name=\"open_telegram_bot\">Abre el bot de Telegram</string>\n    <string name=\"translation\">Traducción</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"rating\">Calificación</string>\n    <string name=\"source\">Fuente</string>\n    <string name=\"test_connection\">Prueba de la conexión</string>\n    <string name=\"open_telegram_bot_summary\">Presione para abrir el chat con Kotatsu Backup Bot</string>\n    <string name=\"send_backups_telegram\">Enviar copias de seguridad por Telegram</string>\n    <string name=\"telegram_chat_id_summary\">Ingrese el ID del chat donde se deben enviar las copias de seguridad</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"use_default_cover\">Usar portada por defecto</string>\n    <string name=\"pick_custom_file\">Elegir archivo personalizado</string>\n    <string name=\"change_cover\">Cambiar portada</string>\n    <string name=\"page_switch_timer\">La página cambiará cada ~%d segundos</string>\n    <string name=\"dont_ask_again\">No volver a preguntar</string>\n    <string name=\"incognito_mode_hint_nsfw\">Este manga puede contener contenido adulto. ¿Quieres usar el modo incógnito?</string>\n    <string name=\"expand\">Expandir</string>\n    <string name=\"enable_all_sources_summary\">Todas las fuentes de manga disponibles se habilitarán permanentemente</string>\n    <string name=\"all_sources_enabled\">Todas las fuentes están habilitadas</string>\n    <string name=\"backup_restored_background\">La copia de seguridad se restaurará en segundo plano</string>\n    <string name=\"restoring_backup\">Restaurando copia de seguridad</string>\n    <string name=\"screen_rotation_locked\">La rotación de pantalla ha sido bloqueada</string>\n    <string name=\"screen_rotation_unlocked\">La rotación de pantalla se ha desbloqueado</string>\n    <string name=\"simple\">Simple</string>\n    <string name=\"global_search\">Búsqueda global</string>\n    <string name=\"clear_database\">Limpiar base de datos</string>\n    <string name=\"clear_database_summary\">Eliminar información en desuso del manga</string>\n    <string name=\"enable_all_sources\">Habilitar todas las fuentes de manga</string>\n    <string name=\"reader_info_bar_transparent\">Barra de información del lector transparente</string>\n    <string name=\"reader_controls_in_bottom_bar\">Controles del lector en la barra inferior</string>\n    <string name=\"chapters_and_pages\">Capítulos y páginas</string>\n    <string name=\"pages_slider\">Deslizar para cambiar de página</string>\n    <string name=\"badges_in_lists\">Insignias en listas</string>\n    <string name=\"search_everywhere\">Buscar en todas partes</string>\n    <string name=\"disable_captcha_notifications\">Deshabilitar las notificaciones de captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">No recibirás notificaciones sobre la resolución de CAPTCHA para esta fuente, pero esto puede provocar la interrupción de las operaciones en segundo plano (comprobación de nuevos capítulos, obtención de recomendaciones, etc.)</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Capítulo %2$s</string>\n    <string name=\"chapter_number\">Capítulo %s</string>\n    <string name=\"unnamed_chapter\">Capítulo sin título</string>\n    <string name=\"search_disabled_sources\">Buscar en fuentes deshabilitadas</string>\n    <string name=\"error_details\">Detalles del error</string>\n    <string name=\"error_disclaimer_manga\">Intenta abrir el manga en un navegador web para asegurarte de que está disponible en su fuente.</string>\n    <string name=\"error_disclaimer_app_outdated\">Parece que tu versión de Kotatsu está desactualizada. Instala la última versión para obtener todas las correcciones disponibles.</string>\n    <string name=\"error_disclaimer_report\">Puede enviar un informe de error a los desarrolladores. Esto nos ayudará a investigar y solucionar el problema.</string>\n    <string name=\"link_to_manga_on_s\">Enlace al manga en %s</string>\n    <string name=\"link_to_manga_in_app\">Enlace al manga en Kotatsu</string>\n    <string name=\"clear_browser_data\">Borrar datos del navegador</string>\n    <string name=\"clear_browser_data_summary\">Borra los datos del navegador, como la caché y las cookies. Advertencia: la autorización en las fuentes de manga puede dejar de ser válida</string>\n    <string name=\"no_write_permission_to_file\">No tiene permiso para escribir el archivo</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">El manga para adultos no aparecerá en las sugerencias. Esta opción puede funcionar de forma inexacta con algunas fuentes</string>\n    <string name=\"include_disabled_sources\">Incluir fuentes deshabilitadas</string>\n    <string name=\"suggestions_disabled_sources_summary\">Mostrar sugerencias de todas las fuentes de manga, incluidas las deshabilitadas</string>\n    <string name=\"tags_warnings\">Destacar géneros peligrosos</string>\n    <string name=\"tags_warnings_summary\">Resaltar los géneros que pueden ser inapropiados para la mayoría de los usuarios</string>\n    <string name=\"error_non_file_uri\">La ruta seleccionada no se puede utilizar porque no indica un archivo o directorio</string>\n    <string name=\"manga_override_hint\">Estos cambios afectarán a la forma en que se muestra el manga en la aplicación</string>\n    <string name=\"pick_manga_page\">Seleccionar página</string>\n    <string name=\"incognito_for_nsfw\">Modo incógnito para manga NSFW</string>\n    <string name=\"theme_name_expressive\">Expresivo (Prueba)</string>\n    <string name=\"additional_action_required\">Se requieren medidas adicionales</string>\n    <string name=\"hide_from_main_screen\">Ocultar de la pantalla principal</string>\n    <string name=\"changelog\">Registro de cambios</string>\n    <string name=\"changelog_summary\">Historial de cambios de las versiones publicadas recientemente</string>\n    <string name=\"collapse\">Plegar</string>\n    <string name=\"adblock\">Bloquear anuncios en el navegador</string>\n    <string name=\"adblock_summary\">Bloquear anuncios en el navegador integrado (beta)</string>\n    <string name=\"collapse_long_description\">Plegar descripción larga</string>\n    <string name=\"creating_backup\">Creando copia de seguridad</string>\n    <string name=\"share_backup\">Compartir copia de seguridad</string>\n    <string name=\"reader_multitask\">Abrir lector en una tarea separada</string>\n    <string name=\"reader_multitask_summary\">Te permite mantener abiertos varios lectores con diferentes mangas al mismo tiempo</string>\n    <string name=\"reader_navigation_inverted\">Invertir controles de navegación</string>\n    <string name=\"reader_navigation_inverted_summary\">Cambiar la dirección del botón de volumen y la navegación con las teclas de dirección (izquierda/arriba/abajo/derecha)</string>\n    <string name=\"book_effect\">Filtro de luz azul</string>\n    <string name=\"local_storage_cleanup\">Limpieza del almacenamiento local</string>\n    <string name=\"packup_creation_failed\">No se pudo crear la copia de seguridad</string>\n    <string name=\"main_screen\">Pantalla principal</string>\n    <string name=\"main_screen_fab\">Mostrar botón Continuar, flotante</string>\n    <string name=\"main_screen_fab_summary\">Permite continuar leyendo con un solo clic. Este botón no aparecerá en modo incógnito ni cuando el historial esté vacío</string>\n    <string name=\"error_corrupted_zip\">Archivo ZIP dañado (%s)</string>\n    <string name=\"discord_rpc\">Presencia enriquecida con Discord</string>\n    <string name=\"discord_token\">Token de Discord</string>\n    <string name=\"discord_token_summary\">Introduce tu token de Discord para habilitar la presencia enriquecida</string>\n    <string name=\"discord_token_description\">Introduce tu token de Discord o haz clic en %s para obtenerlo mediante el navegador</string>\n    <string name=\"discord_token_hint\">Pega aquí tu token de Discord</string>\n    <string name=\"discord_rpc_summary\">Muestra tu estado de lectura en Discord</string>\n    <string name=\"obtain\">Obtener</string>\n    <string name=\"discord_rpc_description\">Leer manga en Kotatsu: una aplicación para leer manga</string>\n    <string name=\"reading_s\">Leyendo %s</string>\n    <string name=\"read_on_s\">Sigue leyendo en %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">No utilice RPC para contenido de adultos</string>\n    <string name=\"invalid_token\">Token no válido: %s</string>\n    <string name=\"show_floating_control_button\">Mostrar botón de control, flotante</string>\n    <string name=\"unavailable\">No disponible</string>\n    <string name=\"manga_restricted_description\">Este manga no está disponible para leer en esta fuente. Intenta buscarlo en otras fuentes o ábrelo en un navegador para obtener más información</string>\n    <string name=\"no_chapters_in_manga\">Este manga no contiene ningún capítulo</string>\n    <string name=\"chapters_load_failed\">No se pudo cargar la lista de capítulos</string>\n    <string name=\"telegram_integration\">Integración con Telegram</string>\n    <string name=\"test_parser\">Probar la fuente de manga</string>\n    <string name=\"pull_to_prev_chapter\">Abrir el capítulo anterior</string>\n    <string name=\"pull_to_next_chapter\">Abrir el siguiente capítulo</string>\n    <string name=\"pull_top_no_prev\">No existe capítulo anterior</string>\n    <string name=\"pull_bottom_no_next\">No existe capítulo siguiente</string>\n    <string name=\"enable_pull_gesture_title\">Habilitar gesto de arrastrar</string>\n    <string name=\"enable_pull_gesture_summary\">Usa el gesto de arrastrar para cambiar de capítulo en webtoon</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-et/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minut tagasi</item>\n        <item quantity=\"other\">%1$d minutit tagasi</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d asi</item>\n        <item quantity=\"other\">%1$d asja</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d peatükk</item>\n        <item quantity=\"other\">%1$d peatükki</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d uus peatükk</item>\n        <item quantity=\"other\">%1$d uut peatükki</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d kuu tagasi</item>\n        <item quantity=\"other\">%1$d kuud tagasi</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d päev tagasi</item>\n        <item quantity=\"other\">%1$d päeva tagasi</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d tund tagasi</item>\n        <item quantity=\"other\">%1$d tundi tagasi</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minut</item>\n        <item quantity=\"other\">%1$d minutit</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d tund</item>\n        <item quantity=\"other\">%1$d tundi</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-et/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"updates\">Uuendused</string>\n    <string name=\"languages\">Keel</string>\n    <string name=\"status_re_reading\">Uuesti loen</string>\n    <string name=\"add_to_favourites\">Lisa lemmikuks</string>\n    <string name=\"detect_reader_mode\">Automaatne lugeja mood</string>\n    <string name=\"manga_shelf\">Riiul</string>\n    <string name=\"download_started\">Allalaadimine alustatud</string>\n    <string name=\"tracking\">Jälgimine</string>\n    <string name=\"close\">Sulge</string>\n    <string name=\"progress\">Progress</string>\n    <string name=\"text_history_holder_secondary\">Leia mida lugeda «Seiklemis» osakonnas</string>\n    <string name=\"cancel_all\">Tühista kõik</string>\n    <string name=\"sync_host_description\">Saate kasutada ise korraldatud sünkroonimisserverit või tava sünkroonimisserverit. Ärge muutke seda, kui te ei ole kindel, mida teete.</string>\n    <string name=\"all_favourites\">Kõik lemmikud</string>\n    <string name=\"email_enter_hint\">Sisesta oma emaili aadress et jätkata</string>\n    <string name=\"save\">Salvesta</string>\n    <string name=\"light_indicator\">LED indikaator</string>\n    <string name=\"no_chapters\">Pole peatükke</string>\n    <string name=\"related_manga_summary\">Näita seotud mangade nimekirja. Mõnel juhul võib see olla ebatäpne või puududa</string>\n    <string name=\"theme_name_dynamic\">Dünaamiline</string>\n    <string name=\"text_clear_cookies_prompt\">Teid logitakse välja kõikidest allikatest</string>\n    <string name=\"clear_cookies\">Tühjenda küpsisesd</string>\n    <string name=\"favourites_categories\">Lemmikud kategooriad</string>\n    <string name=\"pages_cache\">Lehekülgede vahemälu</string>\n    <string name=\"local_storage\">Kohalik salvestamine</string>\n    <string name=\"filter\">Filterid</string>\n    <string name=\"reset\">Nulli</string>\n    <string name=\"disable_all\">Lülita kõik välja</string>\n    <string name=\"error_occurred\">Tekkis viga</string>\n    <string name=\"order_added\">Lisatud</string>\n    <string name=\"enable_logging\">Lülita sisse loggig</string>\n    <string name=\"clear_thumbs_cache\">Tühjenda pisipildi vahemälu</string>\n    <string name=\"switch_pages\">Vaheta lehekülge</string>\n    <string name=\"settings_apply_restart_required\">Muudatuste rakendamiseks käivitage rakendus uuesti</string>\n    <string name=\"rotate_screen\">Keera ekraani</string>\n    <string name=\"text_clear_updates_feed_prompt\">Tühjenda kõik uuenduste ajalugu?</string>\n    <string name=\"source_disabled\">Allikas välja lülitatud</string>\n    <string name=\"clear_cookies_summary\">Võib aidata mõndade probleemidega. Kõik autoriseerimine saab invalideeritud</string>\n    <string name=\"enable_logging_summary\">Salvesta mõned tegevused silumiseks. Ärge lülitage seda sisse, kui te ei ole kindel, mida teete</string>\n    <string name=\"suggestions_enable\">Lülita sisse soovitused</string>\n    <string name=\"clear_feed\">Tühjenda voog</string>\n    <string name=\"welcome\">Tere tulemast</string>\n    <string name=\"no_description\">Kirjeldus puudub</string>\n    <string name=\"about_app_translation_summary\">Tõlgi see äppi</string>\n    <string name=\"chapters_empty\">Selles mangas ei ole peatükke</string>\n    <string name=\"remove\">Eemalda</string>\n    <string name=\"vibration\">Värisemine</string>\n    <string name=\"no_update_available\">Uuendusi pole saadaval</string>\n    <string name=\"remove_category\">Eemalda</string>\n    <string name=\"clear_all_history\">Tühjenda kõik ajalugu</string>\n    <string name=\"preload_pages\">Lehekülgede eellaadimine</string>\n    <string name=\"data_deletion\">Andmete kustutamine</string>\n    <string name=\"favourites\">Lemmikud</string>\n    <string name=\"history_shortcuts\">Näita hiljutise manga lühitee</string>\n    <string name=\"downloads_wifi_only_summary\">Peata allalaadimised kui vahetad mobiilsele ühendusele</string>\n    <string name=\"read_mode\">Lugemis mood</string>\n    <string name=\"internal_storage\">Sisemine mälu</string>\n    <string name=\"import_completed\">Imporditud</string>\n    <string name=\"show_reading_indicators\">Näita lugemis progressi näitaja</string>\n    <string name=\"read_later\">Loe hiljem</string>\n    <string name=\"backup_saved\">Tagavara salvestatud</string>\n    <string name=\"local_manga_processing\">Töötlen salvestatud manga</string>\n    <string name=\"user_agent\">UserAgent pealkiri</string>\n    <string name=\"cannot_find_available_storage\">Puudub olemasolevat salvestamisruumi</string>\n    <string name=\"create_backup\">Tee andmete tagavara</string>\n    <string name=\"detailed_list\">Detailitud loetelu</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">sisse lülitatud %1$d / %2$d</string>\n    <string name=\"tap_to_try_again\">Vajuta, et uuesti proovida</string>\n    <string name=\"ignore_ssl_errors\">Ignoreeri SSL vigasi</string>\n    <string name=\"auth_required\">Selle sisu vaatamiseks logi sisse</string>\n    <string name=\"search_manga\">Otsi mangat</string>\n    <string name=\"next\">Järgmine</string>\n    <string name=\"restore_backup\">Taasta andmete tagavarast</string>\n    <string name=\"reader_info_bar\">Näita inforiba lugejas</string>\n    <string name=\"password_length_hint\">Parool peab olema 4 tähemärki või veel</string>\n    <string name=\"server_address\">Sereri aadress</string>\n    <string name=\"text_feed_holder\">Uued loetavad mangast peatükid näidatakse siin</string>\n    <string name=\"text_suggestion_holder\">Hakka lugema mangat ja sa saad isikupärastatud soovitusi</string>\n    <string name=\"explore\">Seikle</string>\n    <string name=\"find_similar\">Leija samasuguseid</string>\n    <string name=\"show_notification_new_chapters_off\">Sa ei saa teateid uuenduste kohta, aga uued peatükkid on esiletoodud nimekirjas</string>\n    <string name=\"storage_usage\">Ruumi kasutatud</string>\n    <string name=\"data_restored\">Taastatud</string>\n    <string name=\"favourites_category_empty\">Tühi kategooria</string>\n    <string name=\"protect_application_subtitle\">Sisesta parool millega startida äppi</string>\n    <string name=\"remote_sources\">Manga allikad</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"clear\">Tühjenda</string>\n    <string name=\"by_rating\">Hinnang</string>\n    <string name=\"unknown\">Mitte teatud</string>\n    <string name=\"newest\">Uusimad</string>\n    <string name=\"suggestions\">Soovitused</string>\n    <string name=\"sort_order\">Sorteerimisjärjekord</string>\n    <string name=\"clear_history\">Kustuta ajalugu</string>\n    <string name=\"show_notification_new_chapters_on\">Sa saad manga kohta mis sa loed teateid uuenduste kohta</string>\n    <string name=\"importing_manga\">Impordi manga</string>\n    <string name=\"enabled\">Sisse lülitatud</string>\n    <string name=\"pause\">Paus</string>\n    <string name=\"clear_new_chapters_counters\">Ja tühjenda informatsioon uute peatükkide kohta</string>\n    <string name=\"text_clear_search_history_prompt\">Eemaldada kõik hiljutised otsingupäringud?</string>\n    <string name=\"nothing_here\">Midagi pole siin</string>\n    <string name=\"remove_completed\">Eemalda loetud mangad</string>\n    <string name=\"manga_save_location\">Allalaadimiste kaust</string>\n    <string name=\"status_reading\">Loen</string>\n    <string name=\"auth_complete\">Lubatud</string>\n    <string name=\"loading_\">Laadimine…</string>\n    <string name=\"suggestions_notifications_summary\">Vahepeal näita teateid koos soovitatud mangaga</string>\n    <string name=\"updates_feed_cleared\">Tühjendatud</string>\n    <string name=\"webtoon_zoom\">Webtooni zoom</string>\n    <string name=\"various_languages\">Erinevad keeled</string>\n    <string name=\"list_mode\">Loetelu mood</string>\n    <string name=\"removal_completed\">Eemaldatud</string>\n    <string name=\"download_complete\">Allalaaditud</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"edit\">Muuda</string>\n    <string name=\"captcha_required\">CAPTCHA vajatud</string>\n    <string name=\"popular\">Populaarsed</string>\n    <string name=\"manage_categories\">Halda kategoorjaid</string>\n    <string name=\"update\">Uuenda</string>\n    <string name=\"scrobbling_empty_hint\">Lugemise edenemise jälgimiseks valige manga üksikasjade ekraanil menüü → Jälgimine.</string>\n    <string name=\"removed_from_history\">Eemaldatud ajaloost</string>\n    <string name=\"crash_text\">Midagi läks valesti. Palun esitage arendajatele veateade, et aidata meil seda parandada.</string>\n    <string name=\"theme\">Teema</string>\n    <string name=\"detect_reader_mode_summary\">Automaatselt tuvastada, kas manga on webtoon</string>\n    <string name=\"not_found_404\">Sisu mitte saadaval või eemaldatud</string>\n    <string name=\"feed_will_update_soon\">Voogu uuendused algavad varsti</string>\n    <string name=\"appwidget_recent_description\">Sinu hiljuti loetud manga</string>\n    <string name=\"got_it\">Saadud</string>\n    <string name=\"chapters\">Peatükid</string>\n    <string name=\"appearance\">Välimus</string>\n    <string name=\"bookmark_remove\">Eemalda järjehoidja</string>\n    <string name=\"search_hint\">Sisestage manga pealkiri, žanr või allika nimi</string>\n    <string name=\"disable_battery_optimization_summary\">Aitab tausta uuenduste kontrollimisega</string>\n    <string name=\"downloads\">Alla laadimised</string>\n    <string name=\"auth_not_supported_by\">Sisselogimine %s ei ole toetatud</string>\n    <string name=\"app_update_available\">Uus äppi versioon on olemas</string>\n    <string name=\"status_on_hold\">Ootel</string>\n    <string name=\"last_2_hours\">Viimased 2 tundi</string>\n    <string name=\"name\">Nimi</string>\n    <string name=\"sources_reorder_tip\">Vajuta ja hoija asjal, et uuesti reastada neid</string>\n    <string name=\"edit_category\">Muuda kategooriat</string>\n    <string name=\"resume\">Jätka</string>\n    <string name=\"server_error\">Serveri poolene viga (%1$d). Palun proovi uuessti hiljem</string>\n    <string name=\"bookmark_removed\">Järjehoidja eemaldatud</string>\n    <string name=\"network_unavailable_hint\">Lülita sisse Wi-Fi või mobile data, et lugeda mangat online</string>\n    <string name=\"you_have_not_favourites_yet\">Pole lemmikuid veel</string>\n    <string name=\"select_range\">Valige vahemik</string>\n    <string name=\"tracker_warning\">Mõnedel seadmetel on erinev süsteemikäitumine, mis võib katkestada taustatööd.</string>\n    <string name=\"network_error\">Võrguviga</string>\n    <string name=\"new_version_s\">Uus versioon: %s</string>\n    <string name=\"no_manga_sources\">Pole manga allikaid</string>\n    <string name=\"light\">Valge</string>\n    <string name=\"suggestions_excluded_genres_summary\">Täpsusta žanrid, mida te ei soovi ettepanekutes näha</string>\n    <string name=\"text_delete_local_manga\">Kas kustutame \\\"%s\\\" nutiseadmest jäädavalt?</string>\n    <string name=\"prefetch_content\">Sisu eellaeb</string>\n    <string name=\"confirm_exit\">Vajuta Tagasi, et lahkuda</string>\n    <string name=\"scale_mode\">skaala mood</string>\n    <string name=\"advanced\">Täiustatud</string>\n    <string name=\"only_using_wifi\">Ainult Wi-Fi-l</string>\n    <string name=\"follow_system\">Jälgne süsteemile</string>\n    <string name=\"sync_settings\">Sünkroniseeri seadeid</string>\n    <string name=\"black_dark_theme\">Must</string>\n    <string name=\"text_history_holder_primary\">Mis sa loed näidatakse siin</string>\n    <string name=\"back\">Must</string>\n    <string name=\"delete_manga\">Kustuta manga</string>\n    <string name=\"create_category\">Uus kategooria</string>\n    <string name=\"delete\">Kustuta</string>\n    <string name=\"notification_sound\">Teate hääl</string>\n    <string name=\"screenshots_allow\">Luba</string>\n    <string name=\"backup_restore\">Varundamine ja taastamine</string>\n    <string name=\"other_cache\">Teised vahemälud</string>\n    <string name=\"dns_over_https\">DNS üle HTTPS</string>\n    <string name=\"show_suspicious_content\">Näita kahtlast sisu</string>\n    <string name=\"show_pages_numbers\">nummerda leheküljed</string>\n    <string name=\"allow_unstable_updates_summary\">Saa teateid ebastabiilsete uuenduste kohta</string>\n    <string name=\"sync_title\">Sünkroniseeri oma andmed</string>\n    <string name=\"appwidget_shelf_description\">Manga sinu lemmikutest</string>\n    <string name=\"comics_archive_import_description\">Sa võid valida üks või veel .cbz või .zip faile, iga fail võetakse kui erinev manga.</string>\n    <string name=\"search_history_cleared\">Tühjendatud</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" eemaldatud lokaasest salvestusruumist</string>\n    <string name=\"too_many_requests_message\">Liiga palju taotlusi. Proovige hiljem uuesti</string>\n    <string name=\"downloads_wifi_only\">Laadi alla ainult WiFi võrgus</string>\n    <string name=\"send\">Saada</string>\n    <string name=\"bookmark_add\">Lisa järjehoidja</string>\n    <string name=\"discard\">Unusta</string>\n    <string name=\"saved_manga\">Salvestatud manga</string>\n    <string name=\"manga_downloading_\">Laadin alla…</string>\n    <string name=\"screenshots_block_all\">Alati blokeeri</string>\n    <string name=\"new_sources_text\">Uued manga allikad on saadaval</string>\n    <string name=\"manga_error_description_pattern\">Vea detailid:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Proovi &lt;a href=%2$s&gt;avada mangat oma browseris&lt;/a&gt; et olla kindel et see manga on saadaval allikast &lt;br&gt;2. tee kindlaks et sa kasutad &lt;a href=kotatsu://about&gt;kõige uuemat versiooni Kotatsut&lt;/a&gt;&lt;br&gt;3. Kui see on saadaval, saada vea raport arendajatele.</string>\n    <string name=\"open_in_browser\">Ava veebibrauser</string>\n    <string name=\"about_app_translation\">Tõlge</string>\n    <string name=\"zoom_mode_fit_height\">Sobita kõrgusele</string>\n    <string name=\"notifications\">Teated</string>\n    <string name=\"not_available\">Mitte saadaval</string>\n    <string name=\"check_new_chapters_title\">Otsi uusi peatükke ja tevita sellest</string>\n    <string name=\"history_shortcuts_summary\">Tee hiljutine manga saadaval hoides pikkalt applikatsiooni ikoonil</string>\n    <string name=\"automatic_scroll\">Automaatiline scroll</string>\n    <string name=\"reverse\">Tagurpidi</string>\n    <string name=\"track_sources\">Otsi uusi uuendusi</string>\n    <string name=\"paused\">Pausis</string>\n    <string name=\"clear_search_history\">Tühjenda otsingute ajalugu</string>\n    <string name=\"wrong_password\">Vale parool</string>\n    <string name=\"group\">Grupp</string>\n    <string name=\"_import\">Impordi</string>\n    <string name=\"color_correction\">Värvi korektsioon</string>\n    <string name=\"just_now\">Just nüüd</string>\n    <string name=\"text_file_not_supported\">Vali kas ZIP või CBZ fail.</string>\n    <string name=\"download\">Lae alla</string>\n    <string name=\"chapter_is_missing\">See peatükk puudub</string>\n    <string name=\"categories_delete_confirm\">Kas sa oled kindel, et tahad kustutada valitud lemmikud kategooriad? \\nKõik manga nende sees kaob ja seda tagasi ei saa.</string>\n    <string name=\"logged_in_as\">Logitud sisse kui %s</string>\n    <string name=\"suggestions_info\">Kõik andmed analüüsitakse ainult lokaalselt selles seadmes ja neid ei saadeta kunagi kuhugi.</string>\n    <string name=\"reader_info_pattern\">Pt. %1$d/%2$d Lk. %3$d/%4$d</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"history_cleared\">Ajalugu tühjendatud</string>\n    <string name=\"size_s\">Suurus: %s</string>\n    <string name=\"reader_slider\">Näita lehekülje vahetamise liugurit</string>\n    <string name=\"no_manga_sources_text\">Lülita sisse manga allikaid, et lugeda mangat online</string>\n    <string name=\"about\">Rakenduse teave</string>\n    <string name=\"undo\">Tühista</string>\n    <string name=\"zoom_mode_fit_center\">Sobita keskele</string>\n    <string name=\"options\">Valikud</string>\n    <string name=\"services\">Teenused</string>\n    <string name=\"exclude_nsfw_from_history\">Jäta NSFW manga ajaloost välja</string>\n    <string name=\"clear_pages_cache\">Tühjenda lehekülje vahemälu</string>\n    <string name=\"download_slowdown_summary\">Aitab vältida teie IP-aadressi blokeerimist</string>\n    <string name=\"check_for_new_chapters\">Otsi uusi peatükke</string>\n    <string name=\"read\">Loe</string>\n    <string name=\"text_delete_local_manga_batch\">Kustuta valitud asjad seadmelt?</string>\n    <string name=\"queued\">Järjekorras</string>\n    <string name=\"report\">Raporteeri</string>\n    <string name=\"captcha_solve\">Lahenda</string>\n    <string name=\"download_slowdown\">Aeglusta alla laadimised</string>\n    <string name=\"bookmark_added\">Järjehoidja lisatud</string>\n    <string name=\"data_restored_with_errors\">Andmed on taastatud, aga on vigasi</string>\n    <string name=\"details\">Detailid</string>\n    <string name=\"sync\">Sünkronisatsioon</string>\n    <string name=\"new_chapters\">Uued peatükid</string>\n    <string name=\"search_chapters\">Leia peatükk</string>\n    <string name=\"exit_confirmation\">Lahkumise kinnitus</string>\n    <string name=\"comics_archive\">Koomikute arhiiv</string>\n    <string name=\"more\">Rohkem</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"history_is_empty\">Pole mingit ajalugu veel</string>\n    <string name=\"always\">Alati</string>\n    <string name=\"import_will_start_soon\">Import algab varsti</string>\n    <string name=\"try_again\">Proovi uuesti</string>\n    <string name=\"compact\">Kompaktne</string>\n    <string name=\"protect_application\">Kaitse seda äppi</string>\n    <string name=\"folder_with_images_import_description\">Sa võid valida kausta arhiivi või piltidega. Iga arhiiv (või alamkaust) võetakse kui erinev peatükk.</string>\n    <string name=\"reorder\">ümberjärjestamine</string>\n    <string name=\"share_s\">Jaga %s</string>\n    <string name=\"suggestions_excluded_genres\">Välista žanrid</string>\n    <string name=\"chapter_d_of_d\">Peatükid %1$d %2$d-st</string>\n    <string name=\"canceled\">Peatatud</string>\n    <string name=\"feed\">Voog</string>\n    <string name=\"account_already_exists\">Kasutaja juba eksisteerib</string>\n    <string name=\"dark\">Must</string>\n    <string name=\"hide\">Peida</string>\n    <string name=\"passwords_mismatch\">Ebavõrdsed paroolid</string>\n    <string name=\"by_name\">Nimi</string>\n    <string name=\"yesterday\">Eile</string>\n    <string name=\"check_for_updates\">Otsi uuendusi</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga märkitud kui NSFW kungai ei lisata sinu ajalukku ja progressi ei salvestata</string>\n    <string name=\"mark_as_current\">Margi kui präegune</string>\n    <string name=\"protect_application_summary\">Kusi parooli avades Kotatsut</string>\n    <string name=\"right_to_left\">Paremalt-vasakule</string>\n    <string name=\"show_reading_indicators_summary\">Näita mitu protsenti on loetud ajaloos ja lemmikutes</string>\n    <string name=\"random\">Suvakas</string>\n    <string name=\"mirror_switching\">Vali peegeldus automaatselt</string>\n    <string name=\"use_fingerprint\">Kasuta biomeetriat, kui see on olemas</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"history\">Ajalugu</string>\n    <string name=\"nothing_found\">Mitte midagi leitud</string>\n    <string name=\"onboard_text\">Väli keeled milles sa tahad mangat lugeda. Seda võib hiljem seadetes muuta.</string>\n    <string name=\"show_in_grid_view\">Näita ruudustiku vaates</string>\n    <string name=\"reader_mode_hint\">Valitud konfiguratsioon jäetakse meelde selle manga jaoks</string>\n    <string name=\"default_s\">Default: %s</string>\n    <string name=\"confirm\">Kinnita</string>\n    <string name=\"suggestions_updating\">Soovitused uuendavad</string>\n    <string name=\"add_new_category\">Uus kategooria</string>\n    <string name=\"clear_updates_feed\">Tühjenda uuenduste voog</string>\n    <string name=\"enable\">Lülita sisse</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"chapters_will_removed_background\">Peatükid eemaldatakse taustal</string>\n    <string name=\"import_completed_hint\">Ruumi säästmiseks saad originaalfaili salvestusruumist kustutada</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"share_image\">Jaga pilt</string>\n    <string name=\"disabled\">Välja lülitatud</string>\n    <string name=\"long_ago\">Kaua aega tagasi</string>\n    <string name=\"reader_control_ltr_summary\">Äre kohenda lugemisvaates lehtede vahetamise suunad, näoteks vajutades paremal asuvat nuppu alati suundud järgmisele leheküljele. See eelistus mõjutab vaid eraldi raudvaral põhinevaid sisendseadmeid</string>\n    <string name=\"list\">Loetelu</string>\n    <string name=\"notifications_settings\">Teate seaded</string>\n    <string name=\"language\">Keel</string>\n    <string name=\"domain\">Domeen</string>\n    <string name=\"incognito_mode\">Inkognito mood</string>\n    <string name=\"no_bookmarks_summary\">Sa võid teha järjehoidjaid mangat lugedes</string>\n    <string name=\"search\">Otsi</string>\n    <string name=\"default_mode\">Tava mood</string>\n    <string name=\"reader_settings\">Lugeja seaded</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"text_search_holder_secondary\">Proovi otsingut teiste sõnadega.</string>\n    <string name=\"error\">Viga</string>\n    <string name=\"grid_size\">Ruudustiku suurus</string>\n    <string name=\"manage\">Halda</string>\n    <string name=\"logout\">Logi välja</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"status_completed\">Loetud</string>\n    <string name=\"_continue\">Jätka</string>\n    <string name=\"grid\">Ruudustik</string>\n    <string name=\"recent_manga\">Hiljutised</string>\n    <string name=\"operation_not_supported\">See operatsioon ei ole toetatud</string>\n    <string name=\"reader_control_ltr\">Ergonoomiline lügeja kontroll</string>\n    <string name=\"dont_check\">Ära kontrolli</string>\n    <string name=\"save_page\">Salvesta lehekülg</string>\n    <string name=\"zoom_mode_fit_width\">Sobita laijusele</string>\n    <string name=\"read_more\">Loe veel</string>\n    <string name=\"reset_filter\">Nulli filter</string>\n    <string name=\"search_on_s\">Otsi %s-st</string>\n    <string name=\"status_dropped\">Lõpetatud</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"disable_nsfw\">Lülita välja NSFW</string>\n    <string name=\"processing_\">Töötlen…</string>\n    <string name=\"black_dark_theme_summary\">Kasuta vähem elektrit AMOLED ekraanidel</string>\n    <string name=\"available\">Saadaval</string>\n    <string name=\"notifications_enable\">Lülita sisse notifikatsioonid</string>\n    <string name=\"network_unavailable\">Võrk ei ole saadaval</string>\n    <string name=\"search_results\">Otsingutulemused</string>\n    <string name=\"empty\">Tühi</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Ära soovita NSFW mangat</string>\n    <string name=\"file_not_found\">Fail mitte leitud</string>\n    <string name=\"never\">Mitte kungai</string>\n    <string name=\"text_unsaved_changes_prompt\">Salvesta või unusta mitte salvestatud muutused?</string>\n    <string name=\"settings\">Seaded</string>\n    <string name=\"folder_with_images\">Kaustad piltitega</string>\n    <string name=\"pages\">Leheküljed</string>\n    <string name=\"app_version\">Versioon %s</string>\n    <string name=\"cookies_cleared\">Kõik küpsised on eemaldatud</string>\n    <string name=\"disable_battery_optimization\">Lülita välja aku optimeerimine</string>\n    <string name=\"status_planned\">Planeeritud</string>\n    <string name=\"pages_animation\">Lehekülje animatsioon</string>\n    <string name=\"state_finished\">Lõppetatud</string>\n    <string name=\"state_ongoing\">Käimasolev</string>\n    <string name=\"suggestions_summary\">Soovita manga vastavalt oma eelistustele</string>\n    <string name=\"show\">Näita</string>\n    <string name=\"genres\">Žanrid</string>\n    <string name=\"show_on_shelf\">Näita riiulil</string>\n    <string name=\"allow_unstable_updates\">Luba ebastabiilseid uuendusi</string>\n    <string name=\"sync_auth_hint\">Sa võid sisse logida olemasolevasse kasutajasse või teha uue</string>\n    <string name=\"create_shortcut\">Tee otsetee…</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"other_storage\">Teine salvestamisruum</string>\n    <string name=\"updated\">Uuendatud</string>\n    <string name=\"color_theme\">Värvi skeema</string>\n    <string name=\"brightness\">Helendus</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"preparing_\">Valmistan…</string>\n    <string name=\"share\">Jaga</string>\n    <string name=\"computing_\">Arvutan…</string>\n    <string name=\"add\">Lisa</string>\n    <string name=\"exit_confirmation_summary\">Vajuta Tagasi kaks korda, et lahkuda äppist</string>\n    <string name=\"bookmarks_removed\">Järjehoidjad eemaldatud</string>\n    <string name=\"screenshots_block_nsfw\">Blokeeri kui NSFW</string>\n    <string name=\"enter_password\">Sisesta parool</string>\n    <string name=\"repeat_password\">Korda parooli</string>\n    <string name=\"data_restored_success\">Kõik andmed on taastatud</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"mirror_switching_summary\">Kui peegelserverid on saadaval, siis vea puhul vaheta mangade laadimiseks vajalikke domeene automaatselt</string>\n    <string name=\"no_bookmarks_yet\">Pole järjehoidjaid veel</string>\n    <string name=\"no_thanks\">Tänan ei</string>\n    <string name=\"backup_information\">Sa saad teha tagavara enda ajaloost, lemmikutest ja siis taastada need</string>\n    <string name=\"share_logs\">Jaga looge</string>\n    <string name=\"zoom_mode_keep_start\">Hoija stardis</string>\n    <string name=\"external_storage\">Väline mälu</string>\n    <string name=\"text_local_holder_secondary\">Salvesta midagi võrgupõhisest kataloogist või impordi failist.</string>\n    <string name=\"speed\">Kiirus</string>\n    <string name=\"silent\">Vaikne</string>\n    <string name=\"text_local_holder_primary\">Salvesta midagi esimesena</string>\n    <string name=\"suggestion_manga\">Soovitused: %s</string>\n    <string name=\"removed_from_favourites\">Eemaldatud lemmikutest</string>\n    <string name=\"bookmarks\">Järjehoidja</string>\n    <string name=\"show_all\">Näita kõik</string>\n    <string name=\"page_saved\">Leht on salvestatud</string>\n    <string name=\"today\">Täna</string>\n    <string name=\"empty_favourite_categories\">Pole lemmikkategooriaid</string>\n    <string name=\"invalid_domain_message\">Kehtetu domeen</string>\n    <string name=\"system_default\">Tava</string>\n    <string name=\"text_empty_holder_primary\">On üsna tühi siin…</string>\n    <string name=\"screenshots_policy\">Ekraanipiltide poliitika</string>\n    <string name=\"done\">Tehtud</string>\n    <string name=\"error_no_space_left\">Ruumi pole ülejäänud seadmel</string>\n    <string name=\"sign_in\">Logi sisse</string>\n    <string name=\"remove_completed_downloads_confirm\">Sinu allalaadimise ajalugu kustutatakse jäädavalt. Allalaaditud failid jäävad alles</string>\n    <string name=\"password\">Parool</string>\n    <string name=\"data_and_privacy\">Andmed ja privaatsus</string>\n    <string name=\"invalid_value_message\">Kehtetu väärtus</string>\n    <string name=\"downloads_cancelled\">Allalaadimised on katkestatud</string>\n    <string name=\"web_view_unavailable\">WebView ei ole saadaval: kontrollige kas WebView pakkuja on installitud</string>\n    <string name=\"port\">Port</string>\n    <string name=\"type\">Tüüp</string>\n    <string name=\"images_proxy_title\">Piltide optimeerimise puhverserver</string>\n    <string name=\"username\">Kasutaja nimi</string>\n    <string name=\"authorization_optional\">Autoriseerimine (valikuline)</string>\n    <string name=\"downloads_paused\">Allalaadimised on pausil</string>\n    <string name=\"cancel_all_downloads_confirm\">Kõik pooleliolevad allalaadimised katkestatakse ja poolenisti allalaaditud sisu kustutatakse</string>\n    <string name=\"text_downloads_list_holder\">Sul pole midagi allalaetud</string>\n    <string name=\"invalid_port_number\">Vigane pordi number</string>\n    <string name=\"webtoon_zoom_summary\">Luba sisse suumimis liigutusi webtooni moodis</string>\n    <string name=\"network\">Võrk</string>\n    <string name=\"downloaded\">Allalaetud</string>\n    <string name=\"suggestions_enable_prompt\">Kas te soovite saada personaliseerituid manga soovitusi?</string>\n    <string name=\"address\">Aadress</string>\n    <string name=\"downloads_removed\">Allalaadimised on eemaldatud</string>\n    <string name=\"restore_summary\">Taasta varem loodud varukoopia</string>\n    <string name=\"clear_network_cache\">Tühjendage võrgu vahemälu</string>\n    <string name=\"images_procy_description\">Kasutage wsrv.nl teenust liikluskasutuse vähendamiseks ja võimalusel piltide laadimise kiirendamiseks</string>\n    <string name=\"downloads_resumed\">Allalaadimine jätkub</string>\n    <string name=\"invert_colors\">Pööra värvid tagurpidi</string>\n    <string name=\"proxy\">Puhverserver</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Sinu otsingutingimustele ei vasta ühtegi mangat</string>\n    <string name=\"show_pages_numbers_summary\">Näita alanurgas lehekülje numbreid</string>\n    <string name=\"download_option_all_unread\">Kõik lugemata peatükid</string>\n    <string name=\"download_option_next_unread_n_chapters\">Järgmised lugemata %s</string>\n    <string name=\"local_manga_directories\">Kohalikud kaustad manga jaoks</string>\n    <string name=\"description\">Kirjeldus</string>\n    <string name=\"this_month\">Sel kuul</string>\n    <string name=\"background\">Taust</string>\n    <string name=\"reader_zoom_buttons_summary\">Selle eelistusega lülitad suumimise nupud vaate all paremas nurgas sisse või välja</string>\n    <string name=\"list_options\">Loendi valikud</string>\n    <string name=\"last_read\">Viimatiloetud</string>\n    <string name=\"items_limit_exceeded\">Ühtegi objekti ei saa enam lisada</string>\n    <string name=\"email_password_enter_hint\">Jätkamaks sisesta oma e-posti aadress ja salasõna</string>\n    <string name=\"color_light\">Hele</string>\n    <string name=\"color_black\">Must</string>\n    <string name=\"color_dark\">Tume</string>\n    <string name=\"zoom_out\">Suumi välja</string>\n    <string name=\"mark_as_completed\">Märgi loetuks</string>\n    <string name=\"chapters_grid_view\">Vaade ruudustikuna</string>\n    <string name=\"reader_info_bar_summary\">Näita ekraani ülaosas praegust aega ja lugemise edenemist</string>\n    <string name=\"color_white\">Valge</string>\n    <string name=\"keep_screen_on_summary\">Manga lugemise ajal ära lülita ekraani välja</string>\n    <string name=\"keep_screen_on\">Hoia ekraan sisselülitatuna</string>\n    <string name=\"mark_as_completed_prompt\">Kas soovid märkida valitud manga läbiloetuks?\n\\n\n\\nHoiatus: sellega kustub ka praeguseks salvestatud lugemise olek.</string>\n    <string name=\"category_hidden_done\">See kategooria on põhivaatest peidetud ja on leitav siit: Menüü → Halda kategooriaid</string>\n    <string name=\"clear_source_cookies_summary\">Kustuta vaid valitud domeenide küpsised. Enamusel juhtudel tähendab see ka autentimise katkemist vastavas saidis</string>\n    <string name=\"download_option_all_chapters\">Kõik peatükid %s tõlkega</string>\n    <string name=\"download_option_manual_selection\">Vali peatükid käsitsi</string>\n    <string name=\"no_access_to_file\">Sul puudub ligipääs sellele failile või kaustale</string>\n    <string name=\"voice_search\">Häälotsing</string>\n    <string name=\"related_manga\">Sarnane manga</string>\n    <string name=\"pick_custom_directory\">Vali enda eelistatud kaust</string>\n    <string name=\"in_progress\">Töös</string>\n    <string name=\"suggestions_wifi_only_summary\">Ära uuenda soovitusi mahupõhiste võrguühenduste puhul</string>\n    <string name=\"tracker_wifi_only_summary\">Ära kontrolli uute peatükkide olemasolu mahupõhiste võrguühenduste puhul</string>\n    <string name=\"captcha_required_summary\">%s eeldab korralikuks toimimiseks robotilõksu lahendamist</string>\n    <string name=\"manga_list\">Mangade loend</string>\n    <string name=\"error_corrupted_file\">Päringuvastuses on vigased andmed või fail on vigane</string>\n    <string name=\"on_device\">Seadmes</string>\n    <string name=\"directories\">Kaustad</string>\n    <string name=\"main_screen_sections\">Põhivaate valikud</string>\n    <string name=\"to_top\">Üles</string>\n    <string name=\"moved_to_top\">Nihutasime üles</string>\n    <string name=\"categories\">Kategooriad</string>\n    <string name=\"by_relevance\">Olulisus</string>\n    <string name=\"invalid_server_address_message\">Vigane serveri aadress</string>\n    <string name=\"download_option_whole_manga\">Terve manga</string>\n    <string name=\"download_option_first_n_chapters\">Esimesed %s</string>\n    <string name=\"download_option_all_unread_b\">Kõik lugemata peatükid (%s)</string>\n    <string name=\"data_not_restored\">Andmed jäid taastamata</string>\n    <string name=\"data_not_restored_text\">Palun kontrolli, et oled valinud õige varundusfaili</string>\n    <string name=\"too_many_requests_message_retry\">Liiga palju päringuid. Proovi uuesti %s</string>\n    <string name=\"zoom_in\">Suumi sisse</string>\n    <string name=\"reader_zoom_buttons\">Näita suuminuppe</string>\n    <string name=\"state_abandoned\">Lõpetatud</string>\n    <string name=\"enhanced_colors_summary\">Sellega läheb graafika ilusamaks, aga võib väheneda nutiseadme jõudlus</string>\n    <string name=\"enhanced_colors\">32-bitine värvirežiim</string>\n    <string name=\"suggest_new_sources\">Peale rakenduse uuendamist soovita uusi andmeallikaid</string>\n    <string name=\"suggest_new_sources_summary\">Peale rakenduse uuendamist küsi, kas soovid kasutusele võtta uusi mangade allikaid</string>\n    <string name=\"automatic\">Automaatne</string>\n    <string name=\"migration_completed\">Andmete kolimne on lõppenud</string>\n    <string name=\"retry\">Proovi uuesti</string>\n    <string name=\"lock_screen_rotation\">Lukusta ekraani paigutus</string>\n    <string name=\"frequency_every_2_days\">Iga 2 päeva järel</string>\n    <string name=\"frequency_once_per_week\">Kord nädalas</string>\n    <string name=\"frequency_twice_per_month\">Kaks korda kuus</string>\n    <string name=\"frequency_once_per_month\">Kord kuus</string>\n    <string name=\"periodic_backups_enable\">Tee valitud ajavahemiku järel varukoopiaid</string>\n    <string name=\"backups_output_directory\">Varukoopiate kaust</string>\n    <string name=\"last_successful_backup\">Viimane õnnestunud varukoopia: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_other\">Muu</string>\n    <string name=\"skip\">Jäta vahele</string>\n    <string name=\"grayscale\">Halltoonides</string>\n    <string name=\"online_variant\">Veebis leiduv variant</string>\n    <string name=\"periodic_backups\">Regulaarsed varukoopiad</string>\n    <string name=\"backup_frequency\">Varukoopiate tegemise sagedus</string>\n    <string name=\"frequency_every_day\">Iga päev</string>\n    <string name=\"show_menu\">Näita menüüd</string>\n    <string name=\"toggle_ui\">Näita/peida kasutajaliides</string>\n    <string name=\"prev_chapter\">Eelmine peatükk</string>\n    <string name=\"next_chapter\">Järgmine peatükk</string>\n    <string name=\"prev_page\">Eelmine leht</string>\n    <string name=\"next_page\">Järgmine leht</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Koomiks</string>\n    <string name=\"restore\">Taasta</string>\n    <string name=\"source_enabled\">Andmeallikas on kasutusel</string>\n    <string name=\"this_manga\">See manga</string>\n    <string name=\"sources_catalog\">Andmeallikate kataloog</string>\n    <string name=\"backup_date_\">Varukoopia kuupäev: %s</string>\n    <string name=\"content_rating\">Hinnang sisule</string>\n    <string name=\"pages_saved\">Lehed on salvestatud</string>\n    <string name=\"state\">Olek</string>\n    <string name=\"remove_from_history\">Eemalda ajaloost</string>\n    <string name=\"reading_stats\">Lugemise statistika</string>\n    <string name=\"state_paused\">Peatatud</string>\n    <string name=\"clear_stats\">Kustuta statistika</string>\n    <string name=\"stats_cleared\">Statistika on kustutatud</string>\n    <string name=\"statistics\">Statistika</string>\n    <string name=\"reader_optimize\">Vähenda mälukasutust (beeta)</string>\n    <string name=\"fullscreen_mode\">Täisekraanivaade</string>\n    <string name=\"reader_fullscreen_summary\">Peida süsteemi oleku- ja liikumisribad</string>\n    <string name=\"available_d\">Saadaval: %1$d</string>\n    <string name=\"reader_optimize_summary\">Mälukasutuse vähendamiseks alanda lehtede kuvamise kvaliteeti</string>\n    <string name=\"genres_exclude\">Välista žanrid</string>\n    <string name=\"reading_time_estimation\">Näita hinnangulist lugemiseks kuluvat aega</string>\n    <string name=\"clear_stats_confirm\">Kas sa kindlasti soovid statistikat kustutada? Seda tegevust ei saa tagasi pöörata.</string>\n    <string name=\"chapters_and_pages\">Peatükid ja lehed</string>\n    <string name=\"pages_slider\">Lehevahetuse liugurlüliti</string>\n    <string name=\"screen_rotation_locked\">Ekraani pööramise võimalus on lukustatud</string>\n    <string name=\"screen_rotation_unlocked\">Ekraani pööramise võimaluse lukustatus on eemaldatud</string>\n    <string name=\"default_tab\">Vaikimisi vahekaart</string>\n    <string name=\"reading_time_estimation_summary\">Ajahinnang ei pruugi olla täpne</string>\n    <string name=\"catalog\">Kataloog</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-eu/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-fa/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d چپتر جدید</item>\n        <item quantity=\"other\">%1$d چپتر جدید</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d مورد</item>\n        <item quantity=\"other\">%1$d مورد</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d چپتر</item>\n        <item quantity=\"other\">%1$d چپتر</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d دقیقه قبل</item>\n        <item quantity=\"other\">%1$d دقیقه قبل</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d ساعت قبل</item>\n        <item quantity=\"other\">%1$d ساعت قبل</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d روز قبل</item>\n        <item quantity=\"other\">%1$d روز قبل</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d ماه قبل</item>\n        <item quantity=\"other\">%1$d ماه قبل</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d دقیقه</item>\n        <item quantity=\"other\">%1$d دقیقه</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d ساعت</item>\n        <item quantity=\"other\">%1$d ساعت</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fa/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"local_storage\">محل ذخیره سازی</string>\n    <string name=\"favourites\">پسندیده‌ها</string>\n    <string name=\"history\">تاریخچه</string>\n    <string name=\"error_occurred\">خطایی رخ داده است</string>\n    <string name=\"network_error\">خطای اتصال به شبکه</string>\n    <string name=\"text_delete_local_manga\">آیا \\\"%s\\\" بطور دائم از دستگاه حذف شود؟</string>\n    <string name=\"about_app_translation_summary\">این برنامه را ترجمه کنید</string>\n    <string name=\"history_is_empty\">هنوز تاریخچه ای وجود ندارد</string>\n    <string name=\"list_mode\">نوع لیستی</string>\n    <string name=\"internal_storage\">حافظه ی درونی</string>\n    <string name=\"right_to_left\">راست به چپ</string>\n    <string name=\"reader_mode_hint\">پیکربندی انتخاب شده برای این مانگا بخاطر خواهد ماند</string>\n    <string name=\"follow_system\">تم سیستم</string>\n    <string name=\"pages\">صفحات</string>\n    <string name=\"clear\">پاکسازی</string>\n    <string name=\"domain\">دامنه</string>\n    <string name=\"preparing_\">در حال آماده سازی…</string>\n    <string name=\"repeat_password\">گذرواژه را تکرار کنید</string>\n    <string name=\"read_more\">خواندن بیشتر</string>\n    <string name=\"grid\">دریچه</string>\n    <string name=\"settings\">تنظیمات</string>\n    <string name=\"chapter_d_of_d\">چپتر %1$d از %2$d</string>\n    <string name=\"close\">بستن</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" از حافظه ی داخلی پاک شد</string>\n    <string name=\"search_results\">نتایج جستجو</string>\n    <string name=\"add\">اضافه کردن</string>\n    <string name=\"share\">اشتراک گذاری</string>\n    <string name=\"app_update_available\">یک نسخه ی جدید از اپلیکیشن در دسترس می باشد</string>\n    <string name=\"about\">درباره</string>\n    <string name=\"chapters\">چپترها</string>\n    <string name=\"add_to_favourites\">افزودن به مورد علاقه ها</string>\n    <string name=\"page_saved\">ذخیره شد</string>\n    <string name=\"manga_save_location\">پوشه ی دانلودها</string>\n    <string name=\"list\">فهرست</string>\n    <string name=\"detailed_list\">لیست با جزئیات</string>\n    <string name=\"try_again\">مجدد امتحان کنید</string>\n    <string name=\"add_new_category\">دسته بندی جدید</string>\n    <string name=\"manga_downloading_\">درحال دانلود…</string>\n    <string name=\"processing_\">درحال پردازش…</string>\n    <string name=\"download_complete\">دانلود شد</string>\n    <string name=\"save\">ذخیره</string>\n    <string name=\"computing_\">درحال محاسبه کردن…</string>\n    <string name=\"clear_history\">پاکسازی تاریخچه</string>\n    <string name=\"read\">خواندن</string>\n    <string name=\"create_shortcut\">ایجاد میانبر…</string>\n    <string name=\"share_s\">اشتراک گذاری %s</string>\n    <string name=\"search_manga\">جستجو مانگا</string>\n    <string name=\"save_page\">ذخیره ی صفحه</string>\n    <string name=\"_import\">وارد کردن</string>\n    <string name=\"remove_category\">حذف دسته بندی</string>\n    <string name=\"share_image\">اشتراک گذاری عکس</string>\n    <string name=\"switch_pages\">جابه جایی صفحات</string>\n    <string name=\"notification_sound\">صدای اعلان</string>\n    <string name=\"vibration\">لرزاندن</string>\n    <string name=\"silent\">سکوت</string>\n    <string name=\"protect_application_subtitle\">یک گذرواژه برای شروع برنامه وارد کنید</string>\n    <string name=\"confirm\">تایید</string>\n    <string name=\"password_length_hint\">گذرواژه باید 4 کاراکتر یا بیشتر باشد</string>\n    <string name=\"auth_not_supported_by\">ورود به اکانت در %s پشتیبانی نمی شود</string>\n    <string name=\"preload_pages\">از قبل صفحات را بارگذاری کنید</string>\n    <string name=\"favourites_categories\">دسته بندی های مورد علاقه</string>\n    <string name=\"all_favourites\">همه ی علاقه مندی ها</string>\n    <string name=\"favourites_category_empty\">دسته بندی خالی</string>\n    <string name=\"screenshots_policy\">محدودیت های اسکرین شات</string>\n    <string name=\"screenshots_allow\">اجازه دادن</string>\n    <string name=\"screenshots_block_nsfw\">اجازه ندادن درصورت صحنه دار بودن</string>\n    <string name=\"various_languages\">زبان های مختلف</string>\n    <string name=\"read_later\">بعدا خواندن</string>\n    <string name=\"text_feed_holder\">چپترهای جدیدی که شما درحال خواندن هستید در اینجا نمایش داده می شوند</string>\n    <string name=\"updates_feed_cleared\">پاکسازی شد</string>\n    <string name=\"protect_application\">محافظت از برنامه</string>\n    <string name=\"passwords_mismatch\">گذرواژه ها تطابق ندارند</string>\n    <string name=\"yesterday\">دیروز</string>\n    <string name=\"long_ago\">خیلی وقت پیش</string>\n    <string name=\"group\">گروه</string>\n    <string name=\"tap_to_try_again\">برای تلاش مجدد کلیک کنید</string>\n    <string name=\"captcha_required\">به حل و تاییدیه کپچا نیاز است</string>\n    <string name=\"captcha_solve\">حل و تایید کردن</string>\n    <string name=\"text_clear_updates_feed_prompt\">همه ی به روزرسانی ها بطور دائم پاکسازی شوند؟</string>\n    <string name=\"you_have_not_favourites_yet\">هنوز مورد علاقه ای وجود ندارد</string>\n    <string name=\"external_storage\">حافظه ی بیرونی</string>\n    <string name=\"queued\">در صف قرار گرفت</string>\n    <string name=\"track_sources\">جست و جو برای به روز رسانی ها</string>\n    <string name=\"dont_check\">بررسی نکردن</string>\n    <string name=\"enter_password\">گذرواژه را وارد کنید</string>\n    <string name=\"wrong_password\">گذرواژه اشتباه است</string>\n    <string name=\"protect_application_summary\">وقتی از برنامه استفاده می شود، گذرواژه پرسیده شود</string>\n    <string name=\"only_using_wifi\">فقط با وایفای</string>\n    <string name=\"create_backup\">پشتیبان گیری از داده ها</string>\n    <string name=\"details\">جزئیات</string>\n    <string name=\"loading_\">درحال بارگذاری…</string>\n    <string name=\"downloads\">دانلودها</string>\n    <string name=\"by_name\">اسم</string>\n    <string name=\"popular\">محبوبیت</string>\n    <string name=\"updated\">به روز رسانی</string>\n    <string name=\"newest\">جدیدترین</string>\n    <string name=\"by_rating\">رتبه بندی</string>\n    <string name=\"sort_order\">مرتب سازی بر اساس</string>\n    <string name=\"filter\">فیلتر</string>\n    <string name=\"theme\">تم</string>\n    <string name=\"light\">تم روشن</string>\n    <string name=\"dark\">تم تیره</string>\n    <string name=\"remove\">حذف</string>\n    <string name=\"delete\">حذف کردن</string>\n    <string name=\"clear_pages_cache\">پاکسازی کش صفحه</string>\n    <string name=\"text_file_sizes\">بایت|کیلوبایت|مگابایت|گیگابایت|ترابایت</string>\n    <string name=\"no_description\">بدون توضیحات</string>\n    <string name=\"standard\">استاندارد</string>\n    <string name=\"zoom_mode_fit_width\">متناسب به عرض</string>\n    <string name=\"delete_manga\">حذف کردن مانگا</string>\n    <string name=\"open_in_browser\">بازکردن در مرورگر</string>\n    <string name=\"notifications_settings\">تنظیمات اعلان ها</string>\n    <string name=\"webtoon\">وبتون</string>\n    <string name=\"reader_settings\">تنظیمات خوانایی</string>\n    <string name=\"_continue\">ادامه دادن</string>\n    <string name=\"error\">خطا</string>\n    <string name=\"search_history_cleared\">پاکسازی شد</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d از %2$d فعال است</string>\n    <string name=\"new_chapters\">چپتر های جدید</string>\n    <string name=\"text_history_holder_primary\">چیزهایی که خوانده اید اینجا نمایش داده میشود</string>\n    <string name=\"manga_shelf\">تاقچه</string>\n    <string name=\"not_available\">در دسترس نیست</string>\n    <string name=\"cannot_find_available_storage\">محل ذخیره سازی قابل دسترسی ئی یافت نشد</string>\n    <string name=\"scale_mode\">مقیاس</string>\n    <string name=\"zoom_mode_fit_center\">متناسب به مرکز</string>\n    <string name=\"zoom_mode_keep_start\">نگه داشتن در شروع</string>\n    <string name=\"reverse\">معکوس</string>\n    <string name=\"auth_required\">برای دیدن این مطلب، به اکانت خود وارد شوید</string>\n    <string name=\"default_s\">پیش فرض: %s</string>\n    <string name=\"auth_complete\">به اکانت وارد شدید</string>\n    <string name=\"nothing_found\">هیچ نتیجه ای پیدا نشد</string>\n    <string name=\"notifications\">اعلان ها</string>\n    <string name=\"recent_manga\">مانگاهای اخیر</string>\n    <string name=\"chapter_is_missing\">این چپتر پیدا نشد</string>\n    <string name=\"about_app_translation\">ترجمه</string>\n    <string name=\"suggestions_info\">همه ی داده ها صرفا برروی دستگاه شما آنالیز می شود و جای دیگری فرستاده نخواهد شد.</string>\n    <string name=\"backup_information\">شما می توانید از تاریخچه و علاقه مندی های خود پشتیبان گیری کرده و آن ها را بعدا بازیابی کنید</string>\n    <string name=\"just_now\">همین الان</string>\n    <string name=\"today\">امروز</string>\n    <string name=\"cookies_cleared\">همه ی کوکی ها پاکسازی شدند</string>\n    <string name=\"sign_in\">ورود</string>\n    <string name=\"state_ongoing\">درحال پخش</string>\n    <string name=\"system_default\">پیش فرض</string>\n    <string name=\"exclude_nsfw_from_history\">مانگاهای صحنه دار را از تاریخچه ی جسنجو پاک کنید</string>\n    <string name=\"clear_updates_feed\">پاکسازی فیدهای بروزرسانی</string>\n    <string name=\"clear_feed\">پاکسازی فید</string>\n    <string name=\"search\">جستجو</string>\n    <string name=\"search_on_s\">جستجو برای %s</string>\n    <string name=\"black_dark_theme_summary\">استفاده کردن باتری کمتربرروی صفحه های AMOLED</string>\n    <string name=\"genres\">ژانرها</string>\n    <string name=\"done\">انجام شد</string>\n    <string name=\"remote_sources\">منابع مانگا</string>\n    <string name=\"text_history_holder_secondary\">برای پیدا کردن چیزهای جدید برای خواندن، به بخش «Explore» مراجعه کنید</string>\n    <string name=\"download\">دانلود</string>\n    <string name=\"text_empty_holder_primary\">اینجا یجورایی خالیه…</string>\n    <string name=\"text_search_holder_secondary\">سعی کنید کلمات جستجو را تغییر دهید.</string>\n    <string name=\"suggestions_summary\">پیشنهاد دادن مانگا بر اساس اولویت ها</string>\n    <string name=\"text_local_holder_primary\">در ابتدا چیزی را ذخیره کنید</string>\n    <string name=\"text_local_holder_secondary\">ابتدا چیزی را از یک دسته بندی آنلاین ذخیره کنید و یا از یک فایل وارد کنید.</string>\n    <string name=\"text_clear_cookies_prompt\">شما از اکانت های همه ی منابع خارج خواهید شد</string>\n    <string name=\"state_finished\">پایان یافته</string>\n    <string name=\"show_pages_numbers\">صفحات شماره دار</string>\n    <string name=\"screenshots_block_all\">هیچوقت اجازه ندادن</string>\n    <string name=\"suggestions\">پیشنهادها</string>\n    <string name=\"suggestions_enable\">فعال سازی پیشنهادها</string>\n    <string name=\"text_suggestion_holder\">شروع به خواندن مانگا کرده و پیشنهادهایی بر اساس سلیقه ی شخصی خود دریافت کنید</string>\n    <string name=\"enabled\">فعال شد</string>\n    <string name=\"read_mode\">حالت خواندن</string>\n    <string name=\"grid_size\">اندازه ی دریچه</string>\n    <string name=\"clear_thumbs_cache\">پاکسازی حافظه ی thumbnail ها</string>\n    <string name=\"clear_search_history\">پاکسازی تاریخچه ی جستجو</string>\n    <string name=\"light_indicator\">نشانگر ال ای دی</string>\n    <string name=\"pages_animation\">انیمیشن صفحه</string>\n    <string name=\"updates\">به روز شده ها</string>\n    <string name=\"new_version_s\">نسخه ی جدید: %s</string>\n    <string name=\"other_storage\">محل های ذخیره سازی دیگر</string>\n    <string name=\"size_s\">حجم: %s</string>\n    <string name=\"rotate_screen\">چرخاندن صفحه</string>\n    <string name=\"update\">به روزرسانی</string>\n    <string name=\"feed_will_update_soon\">آپدیت فید به زودی شروع می شود</string>\n    <string name=\"app_version\">نسخه %s</string>\n    <string name=\"check_for_updates\">بررسی بروزرسانی</string>\n    <string name=\"no_update_available\">هیچ بروزرسانی موجود نیست</string>\n    <string name=\"create_category\">دسته بندی جدید</string>\n    <string name=\"zoom_mode_fit_height\">متناسب به طول</string>\n    <string name=\"black_dark_theme\">سیاه</string>\n    <string name=\"backup_restore\">پشتیبان گیری و بازیابی</string>\n    <string name=\"restore_backup\">بازیابی داده ها</string>\n    <string name=\"data_restored\">بازیابی شد</string>\n    <string name=\"file_not_found\">فایل پیدا نشد</string>\n    <string name=\"data_restored_success\">همه ی داده ها بازیابی شدند</string>\n    <string name=\"data_restored_with_errors\">داده ها بازیابی شدند اما خطاهایی رخ داده اند</string>\n    <string name=\"clear_cookies\">پاکسازی کوکی ها</string>\n    <string name=\"check_for_new_chapters\">بررسی برای چپتر جدید</string>\n    <string name=\"next\">بعدی</string>\n    <string name=\"text_clear_search_history_prompt\">همه ی کلمات جستجو شده بصورت دائم پاک شوند؟</string>\n    <string name=\"welcome\">خوش آمدید</string>\n    <string name=\"backup_saved\">پشتیبان گیری انجام شد</string>\n    <string name=\"exclude_nsfw_from_suggestions\">پیشنهاد ندادن مانگاهای صحنه دار</string>\n    <string name=\"disabled\">غیرفعال شد</string>\n    <string name=\"reset_filter\">ریست کردن فیلترها</string>\n    <string name=\"never\">هیچوقت</string>\n    <string name=\"always\">همیشه</string>\n    <string name=\"logged_in_as\">به عنوان %s وارد اکانت شدید</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"operation_not_supported\">این عملیات پشتیبانی نمی شود</string>\n    <string name=\"text_file_not_supported\">لطفا یک فایل با فرمت ZIP یا CBZ انتخاب کنید.</string>\n    <string name=\"tracker_warning\">بعضی از دستگاه ها دارای رفتار سیستمی متفاوت می باشند، که ممکن است برخی از پروسه های پس زمینه را مختل کنند.</string>\n    <string name=\"onboard_text\">زبان های مانگاهایی که میخواهید بخوانید را وارد کنید. میتوانید در بخش تنظیمات، آنها را بعدا تغییر دهید.</string>\n    <string name=\"chapters_empty\">این مانگا هیچ چپتری ندارد</string>\n    <string name=\"removal_completed\">حذف انجام شد</string>\n    <string name=\"download_slowdown_summary\">از مسدود شدن آدرس آیپی شما جلوگیری می کند</string>\n    <string name=\"appearance\">تنظیمات ظاهری</string>\n    <string name=\"suggestions_updating\">به روزرسانی های پیشنهادات</string>\n    <string name=\"suggestions_excluded_genres\">ژانرهای آورده نشده</string>\n    <string name=\"suggestions_excluded_genres_summary\">ژانرهایی که نمیخواهید در پیشنهادات ببینید را مشخص کنید</string>\n    <string name=\"download_slowdown\">کاهش سرعت دانلود</string>\n    <string name=\"search_chapters\">پیدا کردن چپتر</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"text_delete_local_manga_batch\">موارد انتخاب شده از دستگاه بطور دائم پاک شوند؟</string>\n    <string name=\"local_manga_processing\">پردازش مانگا ذخیره شد</string>\n    <string name=\"hide\">مخفی کردن</string>\n    <string name=\"canceled\">لغو شد</string>\n    <string name=\"account_already_exists\">اکانتی با این نام کاربری وجود دارد</string>\n    <string name=\"email_enter_hint\">برای ادامه دادن، ایمیل خود را وارد کنید</string>\n    <string name=\"check_new_chapters_title\">چک کردن و اطلاع دادن برای چپتر جدید</string>\n    <string name=\"show_notification_new_chapters_on\">برای آپدیت مانگاهایی که درحال خواندن هستید، اعلان دریافت خواهید کرد</string>\n    <string name=\"show_notification_new_chapters_off\">برای چپترهای جدید مانگاهایی که نشانه گذاری کرده اید، اعلان دریافت نخواهید کرد</string>\n    <string name=\"edit\">ویرایش</string>\n    <string name=\"edit_category\">ویرایش دسته بندی</string>\n    <string name=\"tracking\">ردیابی</string>\n    <string name=\"empty_favourite_categories\">هیچ دسته بندی مورد علاقه ای وجود ندارد</string>\n    <string name=\"logout\">خروج از اکانت</string>\n    <string name=\"bookmark_add\">نشان کردن</string>\n    <string name=\"new_sources_text\">منابع جدید مانگا در دسترس هستند</string>\n    <string name=\"chapters_will_removed_background\">چپترها در پس زمینه حذف خواهند شد</string>\n    <string name=\"bookmark_removed\">نشانه گذاری حذف شد</string>\n    <string name=\"back\">قبل</string>\n    <string name=\"sync\">هماهنگ سازی</string>\n    <string name=\"sync_title\">داده های خود را همگام سازی کنید</string>\n    <string name=\"name\">اسم</string>\n    <string name=\"notifications_enable\">فعال کردن اعلان ها</string>\n    <string name=\"bookmark_remove\">حذف نشانه</string>\n    <string name=\"bookmarks\">نشانه ها</string>\n    <string name=\"text_empty_holder_secondary_filtered\">مانگایی یافت نشد که با دسته‌بندی های انتخاب شده تطابق داشته باشد</string>\n    <string name=\"bookmark_added\">نشانه اضافه شد</string>\n    <string name=\"chapters_grid_view\">نمای مربعی</string>\n    <string name=\"removed_from_history\">از تاریخچه حذف شد</string>\n    <string name=\"default_mode\">حالت پیش‌فرض</string>\n    <string name=\"disable_battery_optimization\">غیرفعال کردن بهینه سازی باتری</string>\n    <string name=\"send\">فرستادن</string>\n    <string name=\"retry\">تلاش مجدد</string>\n    <string name=\"scrobbler_auth_required\">برای ادامه دادن به %s وارد شوید</string>\n    <string name=\"unstable_feature\">ویژگی ناپایدار</string>\n    <string name=\"disable_all\">غیرفعال کردن کل</string>\n    <string name=\"use_fingerprint\">استفاده از اثر انگشت در صورت امکان</string>\n    <string name=\"detect_reader_mode_summary\">تشخیص خودکار وب‌تون بودن مانگا</string>\n    <string name=\"status_reading\">خواندن</string>\n    <string name=\"status_completed\">تکمیل شده</string>\n    <string name=\"status_re_reading\">خواندن مجدد</string>\n    <string name=\"manga_with_downloaded_chapters\">مانگاهای دارای چپتر دانلود شده</string>\n    <string name=\"download_new_chapters\">دانلود چپتر های جدید</string>\n    <string name=\"downloads_background\">دانلودهای پس‌زمینه‌ای</string>\n    <string name=\"unstable_feature_summary\">این ویژگی در حالت آزمایشی می‌باشد، لطفاً از داشتن بکاپ مطمئن شوید تا داده های شما از بین نروند</string>\n    <string name=\"undo\">بازگرداندن</string>\n    <string name=\"automatic\">خودکار</string>\n    <string name=\"crash_text\">مشکلی پیش آمد. لطفاً به منظور کمک کردن به رفع مشکل، گزارشی به توسعه‌دهندگان ارسال کنید.</string>\n    <string name=\"dns_over_https\">DNS بر روی HTTPS</string>\n    <string name=\"detect_reader_mode\">تشخیص خودکار حالت خواندن</string>\n    <string name=\"show_reading_indicators\">نشان دادن نشانگر پیشروی خوانش</string>\n    <string name=\"data_deletion\">پاک کردن داده ها</string>\n    <string name=\"popularity\">محبوبیت</string>\n    <string name=\"status_on_hold\">در انتظار</string>\n    <string name=\"status_dropped\">رها شده</string>\n    <string name=\"exclude_nsfw_from_history_summary\">مانگای نشان شده به عنوان NSFWهرگز به تاریخچه افزوده نخواهد شد و پیشرفت شما ذخیره نخواهد شد.</string>\n    <string name=\"disable_battery_optimization_summary\">به بررسی‌های به‌روز رسانی‌های پس‌زمینه کمک می‌کند</string>\n    <string name=\"appwidget_shelf_description\">مانگا از پسندیده‌های شما</string>\n    <string name=\"report\">گزارش</string>\n    <string name=\"status_planned\">برنامه‌ریزی شده</string>\n    <string name=\"show_reading_indicators_summary\">نشان دادن درصد خوانده شده در تاریخچه و پسندیده‌ها</string>\n    <string name=\"appwidget_recent_description\">مانگا های اخیراً خوانده شده</string>\n    <string name=\"clear_cookies_summary\">می‌تواند در صورت بروز برخی مشکلات کمک کند. همه مجوزها باطل خواهند شد</string>\n    <string name=\"show_all\">نمایش همه</string>\n    <string name=\"invalid_domain_message\">دامنه نامعتبر</string>\n    <string name=\"invalid_server_address_message\">آدرس سرور نامعتیر</string>\n    <string name=\"select_range\">انتخاب بازه</string>\n    <string name=\"clear_all_history\">پاک کردن همه تاریخچه</string>\n    <string name=\"last_2_hours\">۲ ساعت گذشته</string>\n    <string name=\"history_cleared\">تاریخچه پاک شد</string>\n    <string name=\"manage\">مدیریت</string>\n    <string name=\"no_bookmarks_yet\">هنوز هیچ نشانکی وجود ندارد</string>\n    <string name=\"no_bookmarks_summary\">می‌توانید هنگام خواندن مانگا نشانک ایجاد کنید</string>\n    <string name=\"bookmarks_removed\">نشانک حذف شد</string>\n    <string name=\"no_manga_sources\">هیچ منبعی برای مانگا وجود ندارد</string>\n    <string name=\"no_manga_sources_text\">برای خواندن مانگا به صورت برخط، منابع مانگا را فعال کنید</string>\n    <string name=\"random\">تصادفی</string>\n    <string name=\"categories_delete_confirm\">آیا مطمئن هستید که می‌خواهید دسته‌های مورد علاقه انتخاب‌شده را حذف کنید؟\\nتمام مانگاهای موجود در آن از بین خواهند رفت و این قابل بازگشت نیست.</string>\n    <string name=\"reorder\">مرتب‌سازی مجدد</string>\n    <string name=\"empty\">تهی</string>\n    <string name=\"explore\">کاوش</string>\n    <string name=\"confirm_exit\">برای خروج دکمه بازگشت را دوباره فشار دهید</string>\n    <string name=\"exit_confirmation_summary\">دکمه باز گشت را دوبار بفشارید تا از برنامه خارج شوید</string>\n    <string name=\"exit_confirmation\">تایید خروج</string>\n    <string name=\"saved_manga\">مانگا های ذخیره شده</string>\n    <string name=\"pages_cache\">حافظه نهان صفحات</string>\n    <string name=\"other_cache\">دیگر حافظه های نهان</string>\n    <string name=\"storage_usage\">فضای ذخیره‌سازی استفاده شده</string>\n    <string name=\"available\">در دسترس</string>\n    <string name=\"removed_from_favourites\">از مورد علاقه‌ها حذف شد</string>\n    <string name=\"options\">گزینه ها</string>\n    <string name=\"not_found_404\">محتوا حذف شده یا پیدا نشد</string>\n    <string name=\"incognito_mode\">حالت ناشناس</string>\n    <string name=\"no_chapters\">بدون فصل</string>\n    <string name=\"automatic_scroll\">پیمایش خودکار</string>\n    <string name=\"reader_info_pattern\">چپتر.%2$d%1$dصفحه.%4$d%3$d</string>\n    <string name=\"reader_info_bar\">نمایش نوار اطلاعات در خوانشگر</string>\n    <string name=\"comics_archive\">آرشیو کامیک ها</string>\n    <string name=\"folder_with_images\">پوشه با تصویر</string>\n    <string name=\"importing_manga\">وارد کردن مانگا</string>\n    <string name=\"import_completed\">وارد کردن کامل شد</string>\n    <string name=\"import_completed_hint\">شما میتوانید پرونده اصلی را از حافظه برای آزاد سازی فضای ذخیره سازی پاک کنید</string>\n    <string name=\"import_will_start_soon\">وارد کردن به زودی آغاز خواهد شد</string>\n    <string name=\"feed\">خوراک</string>\n    <string name=\"history_shortcuts\">نمایش میانبرهای اخیر مانگا</string>\n    <string name=\"history_shortcuts_summary\">دسترسی به مانگاهای اخیر با فشار طولانی روی آیکون برنامه</string>\n    <string name=\"reader_control_ltr_summary\">جهت تغییر صفحه را به حالت خوانشگر تنظیم نکنید، به عنوان مثال، فشار دادن کلید سمت راست همیشه به صفحه بعدی می‌رود. این گزینه فقط بر دستگاه‌های ورودی سخت‌افزاری تأثیر می‌گذارد</string>\n    <string name=\"reader_control_ltr\">کنترل خوانشگر ارگونومیک</string>\n    <string name=\"color_correction\">تصحیح رنگ</string>\n    <string name=\"brightness\">روشنایی</string>\n    <string name=\"contrast\">کنتراست</string>\n    <string name=\"reset\">بازنشانی</string>\n    <string name=\"text_unsaved_changes_prompt\">ذخیره یا لغو تغییرات ذخیره نشده؟</string>\n    <string name=\"discard\">لغو</string>\n    <string name=\"error_no_space_left\">هیچ فضایی روی دستگاه باقی نمانده است</string>\n    <string name=\"reader_slider\">نمایش اسلایدر تعویض صفحه</string>\n    <string name=\"webtoon_zoom\">بزرگ نمایی وبتوون</string>\n    <string name=\"network_unavailable\">شبکه در دسترس نیست</string>\n    <string name=\"network_unavailable_hint\">برای خواندن مانگا به صورت برخط، وایفای یا دیتای تلفن همراه را روشن کنید</string>\n    <string name=\"server_error\">خطای سمت سرویس دهنده (%1$d). لطفا بعداً دوباره تلاش کنید</string>\n    <string name=\"clear_new_chapters_counters\">اطلاعات مربوط به فصل های جدید رو هم پاک کن</string>\n    <string name=\"compact\">فشرده</string>\n    <string name=\"source_disabled\">منبع غیر فعال شده است</string>\n    <string name=\"prefetch_content\">پیش بازگذاری محتوا</string>\n    <string name=\"mark_as_current\">نشانه گذاری به عنوان فعلی</string>\n    <string name=\"language\">زبان</string>\n    <string name=\"share_logs\">اشتراک گذاری گزارش</string>\n    <string name=\"enable_logging\">فعال کردن گزارشات</string>\n    <string name=\"enable_logging_summary\">ثبت برخی عمیات برای اهداف اشکال زدایی. اگه نمیدونی داری چیکار میکنی این گزینه رو روشن نکن</string>\n    <string name=\"show_suspicious_content\">نمایش محتوای مشکوک</string>\n    <string name=\"theme_name_dynamic\">پویا</string>\n    <string name=\"theme_name_expressive\">صریح (تست)</string>\n    <string name=\"color_theme\">طرح رنگ</string>\n    <string name=\"show_in_grid_view\">نمایش به حالت شبکه ای</string>\n    <string name=\"theme_name_miku\">میکو</string>\n    <string name=\"theme_name_asuka\">آسوکا</string>\n    <string name=\"theme_name_mion\">میون</string>\n    <string name=\"theme_name_rikka\">ریکا</string>\n    <string name=\"theme_name_sakura\">ساکورا</string>\n    <string name=\"theme_name_mamimi\">مامیمی</string>\n    <string name=\"theme_name_kanade\">کاناده</string>\n    <string name=\"nothing_here\">چیزی اینجا نیست</string>\n    <string name=\"scrobbling_empty_hint\">برای فعال کردن پیگیری پیشرفت خواندن، منو را انتخاب کنید ← پیگیری در صفحه جزئیات مانگا.</string>\n    <string name=\"services\">خدمات</string>\n    <string name=\"allow_unstable_updates\">اجازه بروزرسانی های ناپایدار</string>\n    <string name=\"allow_unstable_updates_summary\">دریافت اعلان درباره ورژن های ناپایدار</string>\n    <string name=\"download_started\">بارگیری آغاز شد</string>\n    <string name=\"got_it\">متوجه شدم</string>\n    <string name=\"sources_reorder_tip\">برای مرتب‌سازی مجدد آیتم‌ها، روی آنها ضربه بزنید و نگه دارید</string>\n    <string name=\"user_agent\">هدر UserAgent</string>\n    <string name=\"settings_apply_restart_required\">لطفا برای اعمال این تغییرات، برنامه را مجددا راه اندازی کنید</string>\n    <string name=\"comics_archive_import_description\">میتونی یک یا چند فایل .cbz یا .zip رو انتخاب کنی، هر فایل به صورت یک مانگای مجزا شناخته میشه.</string>\n    <string name=\"folder_with_images_import_description\">میتونی یک پوشه حاوی آرشیو ها یا تصاویر انتخاب کنی. هر آرشیو (یا زیرشاخه) به عنوان یک فصل شناخته میشن.</string>\n    <string name=\"speed\">سرعت</string>\n    <string name=\"show_on_shelf\">نمایش در ققسه</string>\n    <string name=\"sync_auth_hint\">میتونی وارد یک اکانت بشی یا یک اکانت جدید بسازی</string>\n    <string name=\"find_similar\">پیدا کردن مشابه</string>\n    <string name=\"sync_settings\">تنظیمات همگام سازی</string>\n    <string name=\"server_address\">آدرس سرور</string>\n    <string name=\"sync_host_description\">می‌توانید از یک سرور همگام‌سازی خود-میزبان یا یک سرور پیش‌فرض استفاده کنید. اگر مطمئن نیستید که چه کاری انجام می‌دهید، این را تغییر ندهید.</string>\n    <string name=\"ignore_ssl_errors\">نادیده گرفتن خطا های SSL</string>\n    <string name=\"mirror_switching\">انتخاب خودکار آینه</string>\n    <string name=\"mirror_switching_summary\">در صورت وجود آینه، دامنه‌ها را برای منابع مانگا به صورت خودکار تغییر دهید</string>\n    <string name=\"pause\">توقف</string>\n    <string name=\"resume\">ازسرگیری</string>\n    <string name=\"paused\">متوقف شده</string>\n    <string name=\"remove_completed\">منوی آیتم؛ اقدام برای حذف آیتم های کامل شده</string>\n    <string name=\"cancel_all\">لغو همه</string>\n    <string name=\"downloads_wifi_only\">بارگیری فقط از طریق وایفای</string>\n    <string name=\"downloads_wifi_only_summary\">توقف دانلود در زمان تغییرشبکه به داده تلفن همراه</string>\n    <string name=\"suggestion_manga\">پیشنهاد:‌%s</string>\n    <string name=\"suggestions_notifications_summary\">گاهی اوقات اعلان‌هایی با مانگا پیشنهادی نمایش داده می‌شود</string>\n    <string name=\"more\">بیشتر</string>\n    <string name=\"enable\">فعال</string>\n    <string name=\"no_thanks\">نه ممنون</string>\n    <string name=\"cancel_all_downloads_confirm\">تمام دانلودهای فعال لغو می‌شوند، داده‌هایی که به‌طور ناقص دانلود شده‌اند از بین می‌روند</string>\n    <string name=\"remove_completed_downloads_confirm\">تاریخچه بارگیری شما برای همیشه حذف خواهد شد. شامل فایل های بارگیری شده نمیشود</string>\n    <string name=\"text_downloads_list_holder\">شما هیچ بارگیری ندارید</string>\n    <string name=\"downloads_paused\">بارگیری ها متوقف شده اند</string>\n    <string name=\"downloads_removed\">بارگیری ها حذف شده اند</string>\n    <string name=\"web_view_unavailable\">وب ویو در دسترس نیست: بررسی کنید ارائه دهنده وب ویو نصب شده باشد</string>\n    <string name=\"clear_network_cache\">پاک کردن حافظه نهان شبکه</string>\n    <string name=\"type\">نویسه</string>\n    <string name=\"address\">آدرس</string>\n    <string name=\"port\">درگاه</string>\n    <string name=\"proxy\">پروکسی</string>\n    <string name=\"invalid_value_message\">مقدار نامعتبر</string>\n    <string name=\"email_password_enter_hint\">برای ادامه رایانامه و گذرواژه را وارد کنید</string>\n    <string name=\"downloaded\">بارگیری شده</string>\n    <string name=\"images_proxy_title\">پروکسی بهینه سازی تصاویر</string>\n    <string name=\"images_procy_description\">استفاده از سرویس wsrv.nl (در صورت امکان)‌ برای کاهش مصرف ترافیک و افزایش سرعت بارگیری تصویر</string>\n    <string name=\"invert_colors\">معکوس کردن رنگ ها</string>\n    <string name=\"username\">نام کاربری</string>\n    <string name=\"password\">گذرواژه</string>\n    <string name=\"authorization_optional\">اعتبار سنجی (اختیاری)</string>\n    <string name=\"invalid_port_number\">شماره درگاه نامعتبر</string>\n    <string name=\"network\">شبکه</string>\n    <string name=\"data_and_privacy\">داده ها و حریم خصوصی</string>\n    <string name=\"restore_summary\">بازیابی نسخه پشتیبان تهیه شده قبلی</string>\n    <string name=\"webtoon_zoom_summary\">امکان زوم در حالت وبتون</string>\n    <string name=\"reader_info_bar_summary\">نمایش زمان و پیشرفت کنونی در بالای صفحه نمایش</string>\n    <string name=\"show_pages_numbers_summary\">نمایش شماره صفحه در گوشیه پایین صفحه</string>\n    <string name=\"clear_source_cookies_summary\">پاک کردن کوکی ها برای یک دامنه خاص. در بیشتر موارد، مجوز را باطل می‌کند</string>\n    <string name=\"download_option_all_chapters\">تمام فصل ها ترجمه شده %s</string>\n    <string name=\"download_option_whole_manga\">تمام مانگا</string>\n    <string name=\"download_option_first_n_chapters\">ابتدا %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">خوانده نشده بعدی %s</string>\n    <string name=\"download_option_all_unread\">تمام فصل های خوانده نشده</string>\n    <string name=\"download_option_all_unread_b\">تمام فصل های خوانده نشده (%s)</string>\n    <string name=\"download_option_manual_selection\">انتخاب فصل به صورت دستی</string>\n    <string name=\"pick_custom_directory\">انتخاب پوشه سفارشی</string>\n    <string name=\"no_access_to_file\">شما دسترسی به این پرونده یا پوشه را ندارید</string>\n    <string name=\"local_manga_directories\">پوشه های مانگای محلی</string>\n    <string name=\"description\">توضیحات</string>\n    <string name=\"this_month\">این ماه</string>\n    <string name=\"voice_search\">جستجوی صوتی</string>\n    <string name=\"related_manga\">مانگا های مرتبط</string>\n    <string name=\"color_light\">حالت روز</string>\n    <string name=\"color_dark\">حالت شب</string>\n    <string name=\"color_white\">سفید</string>\n    <string name=\"color_black\">مشکی</string>\n    <string name=\"background\">پسزمینه</string>\n    <string name=\"data_not_restored\">داده ها بازیابی نشدند</string>\n    <string name=\"data_not_restored_text\">از انتخاب درست پرونده پشتیبان اطمینان حاصل کنید</string>\n    <string name=\"manage_categories\">مدیریت دسته بندی ها</string>\n    <string name=\"suggestions_wifi_only_summary\">به‌روزرسانی نکردن پیشنهادها با استفاده از اتصالات شبکه محدود</string>\n    <string name=\"tracker_wifi_only_summary\">عدم بررسی بروزرسانی فصل ها با اتصال شبکه محدود</string>\n    <string name=\"search_hint\">عنوان مانگا را وارد کنید، ژانر یا نام منبع</string>\n    <string name=\"progress\">پیشرفت</string>\n    <string name=\"order_added\">اضافه شده</string>\n    <string name=\"show\">نمایش</string>\n    <string name=\"captcha_required_summary\">%s نیازمند حل کردن کپچا برای کار کردن است</string>\n    <string name=\"languages\">زبان ها</string>\n    <string name=\"unknown\">ناشناخته</string>\n    <string name=\"in_progress\">درحال انجام</string>\n    <string name=\"disable_nsfw\">غیر فعال سازی NSFW</string>\n    <string name=\"too_many_requests_message\">درخواست‌ها خیلی زیاد است. بعداً دوباره امتحان کنید</string>\n    <string name=\"too_many_requests_message_retry\">درخواست‌ها خیلی زیاد است. بعدا از %s دوباره تلاش کنید</string>\n    <string name=\"related_manga_summary\">نمایش یک فهرست از مانگا های مرتبط. در برخی موارد ممکن است اشتباه یا ناقص باشد</string>\n    <string name=\"advanced\">پیشرفته</string>\n    <string name=\"manga_list\">مدیریت فهرست</string>\n    <string name=\"error_corrupted_file\">داده نامعتبر است یا پرونده خراب است</string>\n    <string name=\"on_device\">روی دستگاه</string>\n    <string name=\"directories\">پوشه ها</string>\n    <string name=\"main_screen_sections\">بخش‌های صفحه اصلی</string>\n    <string name=\"items_limit_exceeded\">نمیتوان آیتم های بیشتری اضافه کرد</string>\n    <string name=\"to_top\">به بالا</string>\n    <string name=\"moved_to_top\">به بالا منتقل شد</string>\n    <string name=\"zoom_out\">کوچک نمایی</string>\n    <string name=\"zoom_in\">بزرگ نمایی</string>\n    <string name=\"reader_zoom_buttons\">نمایش دکمه های بزرگنمایی</string>\n    <string name=\"reader_zoom_buttons_summary\">اینکه آیا دکمه‌های کنترل بزرگنمایی در گوشه پایین سمت راست نمایش داده شوند یا خیر</string>\n    <string name=\"keep_screen_on\">روشن نگه داشتن صفحه نمایش</string>\n    <string name=\"keep_screen_on_summary\">جلوگیری از خاموش شدن صفحه نمایش در زمان خواندن مانگاه</string>\n    <string name=\"state_abandoned\">رها شده</string>\n    <string name=\"enhanced_colors_summary\">باندینگ را کاهش می‌دهد، اما ممکن است بر عملکرد تأثیر بگذارد</string>\n    <string name=\"enhanced_colors\">حالت رنگ ۳۲ بیتی</string>\n    <string name=\"suggest_new_sources\">پیشنهاد منابع جدید بعد از بروزرسانی برنامه</string>\n    <string name=\"suggest_new_sources_summary\">درخواست فعال کردن منابع تازه اضافه شده پس از به‌روزرسانی برنامه</string>\n    <string name=\"list_options\">گزینه های فهرست</string>\n    <string name=\"by_relevance\">ارتباط</string>\n    <string name=\"categories\">دسته بندی ها</string>\n    <string name=\"online_variant\">نوع برخط</string>\n    <string name=\"periodic_backups\">پشتیبان‌گیری‌های دوره‌ای</string>\n    <string name=\"backup_frequency\">فرکانس ایجاد نسخه پشتیبان</string>\n    <string name=\"frequency_every_day\">هر روز</string>\n    <string name=\"frequency_every_2_days\">هر ۲ دوز</string>\n    <string name=\"frequency_once_per_week\">هر هفته</string>\n    <string name=\"frequency_twice_per_month\">دوبار در ماه</string>\n    <string name=\"frequency_once_per_month\">یک بار درماه</string>\n    <string name=\"periodic_backups_enable\">فعال کردن پشتیبان گیری دوره ای</string>\n    <string name=\"backups_output_directory\">پوشه خروجی پشتیبان گیری</string>\n    <string name=\"last_successful_backup\">آخرین پشتیبان گیری موفق:‌%s</string>\n    <string name=\"lock_screen_rotation\">قفل چرخش صفحه</string>\n    <string name=\"content_type_manga\">مانگا</string>\n    <string name=\"content_type_hentai\">هنتای</string>\n    <string name=\"content_type_comics\">کامیک بوک</string>\n    <string name=\"content_type_other\">دیگر</string>\n    <string name=\"sources_catalog\">کاتالوگ منابع</string>\n    <string name=\"source_enabled\">منبع فعال شد</string>\n    <string name=\"no_manga_sources_catalog_text\">هیچ منبع فعالی در این بخش وجود ندارد، یا همه آنها قبلا اضافه شده اند.\\nبا ما همراه باشید</string>\n    <string name=\"no_manga_sources_found\">هیچ منبع فعال بر اساس جستجوی شما یافت نشد</string>\n    <string name=\"catalog\">کاتالوگ</string>\n    <string name=\"manage_sources\">مدیریت منابع</string>\n    <string name=\"manual\">دستی</string>\n    <string name=\"available_d\">دردسترس: %1$d</string>\n    <string name=\"disable_nsfw_summary\">در صورت امکان مانگای بزرگسالان و منابع NSFW را غیر فعال کنید</string>\n    <string name=\"state_paused\">متوقف شده</string>\n    <string name=\"reader_optimize\">کاهش مصرف حافظه (آزمایشی)</string>\n    <string name=\"reader_optimize_summary\">کاهش کیفیت صفحات خارج از صفحه برای استفاده کمتر از حافظه</string>\n    <string name=\"state\">حالت</string>\n    <string name=\"downloads_resumed\">بارگیری‌ها ازسر گرفته شده‌اند</string>\n    <string name=\"downloads_cancelled\">بارگیری‌ها لغو شده‌اند</string>\n    <string name=\"suggestions_enable_prompt\">آیا می‌خواهید پیشنهادهای مانگای شخصی شده دریافت کنید؟</string>\n    <string name=\"error_multiple_genres_not_supported\">فیلتر کردن با چندین ژانر توسط این منبع مانگا پشتیبانی نمیشود</string>\n    <string name=\"error_multiple_states_not_supported\">فیلتر کردن با چندین وضعیت توسط این منبع مانگا پشتیبانی نمیشود</string>\n    <string name=\"error_search_not_supported\">جستجو کردن توسط این منبع مانگا پشتیبانی نمیشود</string>\n    <string name=\"downloads_settings_info\">اگر با مسدود شدن از طرف سرویس دهنده میشکل دارید، میتوانید کند کننده بارگیری را برای هر منبع مانگاه به صورت جداگانه در تنظیمات منبع فعال کنید</string>\n    <string name=\"skip\">رد شدن</string>\n    <string name=\"grayscale\">حالت خاکستری</string>\n    <string name=\"globally\">سراسری</string>\n    <string name=\"this_manga\">همین مانگا</string>\n    <string name=\"color_correction_apply_text\">این تنظیم‌ها می‌توانند برای همه‌ی مانگاها یا فقط همین مانگا اعمال شوند. اگر برای همه اعمال شوند، تنظیم‌های تک‌به‌تک دست‌نخورده می‌مانند.</string>\n    <string name=\"apply\">اعمال</string>\n    <string name=\"error_filter_locale_genre_not_supported\">این منبع از فیلتر هم‌زمان بر پایه‌ی ژانر و زبان پشتیبانی نمی‌کند</string>\n    <string name=\"error_filter_states_genre_not_supported\">این منبع از فیلتر هم‌زمان بر پایه‌ی ژانر و وضعیت پشتیبانی نمی‌کند</string>\n    <string name=\"genres_search_hint\">برای شروع ژانر مورد نظر خود را بنویسید</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">اگر مشکلی با بارگیری دارید،‌ممکن است به شروع شدن آن کمک کند</string>\n    <string name=\"welcome_text\">لطفا منابع محتوا مورد نظر خود را انتخاب کنید. این بخش را میتوان بعدا از طریق تنظیمات تغییر داد</string>\n    <string name=\"sync_auth\">برای همگام سازی حساب وارد شوید</string>\n    <string name=\"restore\">بازیابی</string>\n    <string name=\"backup_date_\">تاریخ پشتیبان:‌ %s</string>\n    <string name=\"state_upcoming\">پیشرو</string>\n    <string name=\"by_name_reverse\">نام برعکس</string>\n    <string name=\"content_rating\">امتیاز دهی به محتوا</string>\n    <string name=\"genres_exclude\">نادیده گرفتن ژانر ها</string>\n    <string name=\"rating_safe\">امن</string>\n    <string name=\"rating_suggestive\">تحریک‌آمیز</string>\n    <string name=\"rating_adult\">بزرگسال</string>\n    <string name=\"default_tab\">صفحه پیشفرض</string>\n    <string name=\"mark_as_completed\">علامت به عنوان تمام شده</string>\n    <string name=\"mark_as_completed_prompt\">علامت زدن مانگا به عنوان کاملا خوانده شده؟\\n\\nاخطار:‌ پیشرفت فعلی از دست خواهد رفت.</string>\n    <string name=\"category_hidden_done\">دسته بندی از صفحه اصلی پنهان شده و میتوان از طریق منو ← دسته بندی مانگا ها به ان دسترسی داشت</string>\n    <string name=\"volume_\">جلد %d</string>\n    <string name=\"volume_unknown\">جلد نامشخص</string>\n    <string name=\"incognito_mode_hint\">پیشرفت خواندن شما ذخیره نخواهد شد</string>\n    <string name=\"vertical\">عمودی</string>\n    <string name=\"last_read\">اخرین خوانده شده</string>\n    <string name=\"show_menu\">نمایش فهرست</string>\n    <string name=\"toggle_ui\">نمایش/پنهان سازی رابط کاربری</string>\n    <string name=\"prev_chapter\">فصل قبل</string>\n    <string name=\"next_chapter\">فصل بعد</string>\n    <string name=\"prev_page\">صفحه قبل</string>\n    <string name=\"next_page\">صفحه بعد</string>\n    <string name=\"reader_actions\">کنش‌های خواننده</string>\n    <string name=\"reader_actions_summary\">پیکربندی کنش ها برای قسمت های قابل لمس صفحه</string>\n    <string name=\"switch_pages_volume_buttons\">به‌کار انداختن دکمه‌های صدا</string>\n    <string name=\"switch_pages_volume_buttons_summary\">استفاده از دکمه های صدا برای عوض کردن صفحات</string>\n    <string name=\"reader_navigation_inverted\">معکوس کردن کنترل های ناوبری</string>\n    <string name=\"reader_navigation_inverted_summary\">عوض کردن جهت دکمه‌های صدا و کلیدهای سخت‌افزاری (چپ/راست/بالا/پایین)</string>\n    <string name=\"tap_action\">کنش های لمسی</string>\n    <string name=\"long_tap_action\">کنش های لمس طولانی</string>\n    <string name=\"none\">هیچکدام</string>\n    <string name=\"config_reset_confirm\">تنظیمات با مقادیر پیشفرض بازنویسی شود؟ این عمل قابل بازگشت نیست.</string>\n    <string name=\"use_two_pages_landscape\">نمایش دو صفحه در حالت افقی (آزمایشی)</string>\n    <string name=\"default_webtoon_zoom_out\">کوچک‌نمایی پیش‌فرض وبتون</string>\n    <string name=\"fullscreen_mode\">حالت تمام صفحه</string>\n    <string name=\"reader_fullscreen_summary\">پنهان کردن نوار وضعیت و راهبری</string>\n    <string name=\"reading_time_estimation\">نمایش زمان خواندن تخمینی</string>\n    <string name=\"reading_time_estimation_summary\">مقدار زمان تخمینی ممکن است نادرست باشد</string>\n    <string name=\"suggestions_unavailable_text\">پیشنهادات غیر فعال است</string>\n    <string name=\"check_for_new_chapters_disabled\">بررسی برای فصول جدید غیر فعال است</string>\n    <string name=\"show_labels_in_navbar\">نمایش برچسب‌ها در نوار راهبری</string>\n    <string name=\"pages_saving\">ذخیره‌سازی صفحه‌ها</string>\n    <string name=\"ask_for_dest_dir_every_time\">هر بار پوشه‌ی مقصد را بپرس</string>\n    <string name=\"default_page_save_dir\">پوشه پیشفرض ذخیره سازی صفحات</string>\n    <string name=\"remove_from_history\">پاک کردن از تاریخچه</string>\n    <string name=\"location\">مکان</string>\n    <string name=\"preferred_download_format\">قالب بارگیری ترجیحی</string>\n    <string name=\"single_cbz_file\">پروندهٔ CBZ تکی</string>\n    <string name=\"multiple_cbz_files\">پروندهٔ CBZ چندتایی</string>\n    <string name=\"reading_stats\">آمار خواندن</string>\n    <string name=\"other_manga\">مانگا های دیگر</string>\n    <string name=\"less_than_minute\">کمتر از یک دقیقه</string>\n    <string name=\"statistics\">آمار</string>\n    <string name=\"clear_stats\">پاک کردن آمار ها</string>\n    <string name=\"stats_cleared\">آمار ها پاک شدند</string>\n    <string name=\"clear_stats_confirm\">آیا قصد پاک کردن تمام آمار ها را دارید؟ این عمل قابل بازگشت نیست.</string>\n    <string name=\"week\">هفته</string>\n    <string name=\"month\">ماه</string>\n    <string name=\"all_time\">همه</string>\n    <string name=\"day\">روز</string>\n    <string name=\"three_months\">سه ماه</string>\n    <string name=\"empty_stats_text\">هیچ آماری برای دوره انتخاب شده وجود ندارد</string>\n    <string name=\"pages_read_s\">صفحات خوانده شده:‌ %s</string>\n    <string name=\"alternatives\">جایگزین‌ها</string>\n    <string name=\"migrate\">انتقال</string>\n    <string name=\"migrate_confirmation\">مانگا «%1$s» از «%2$s» با «%3$s» از «%4$s» (در صورت وجود داشتن) در تاریخچه و مورد علاقه ها جایگزین خواهد شد</string>\n    <string name=\"manga_migration\">انتقال مانگا</string>\n    <string name=\"migration_completed\">انتقال انجام شد</string>\n    <string name=\"delete_read_chapters\">پاک کردن فصل های خوانده شده</string>\n    <string name=\"no_chapters_deleted\">هیچ فصلی پاک نشد</string>\n    <string name=\"chapters_deleted_pattern\">%1$s حذف شده، %2$s پاک شده</string>\n    <string name=\"delete_read_chapters_summary\">فصل های خوانده شده برای آزاد سازی فضا ذخیره سازی حذف کنید</string>\n    <string name=\"delete_read_chapters_prompt\">این کار تمام فصل هایی که به عنوان خوانده شده علامت گزاری شده اند را برای همیشه پاک خواهد کرد. شما میتونید بعدا آن هارا دانلود کنید اما فصل های افزوده‌شده ممکن است برای همیشه پاک شوند</string>\n    <string name=\"delete_read_chapters_auto\">حذف فصل های خوانده شده به صورت خودکار</string>\n    <string name=\"runs_on_app_start\">با آغاز برنامه اجرا می‌شود</string>\n    <string name=\"split_by_translations\">بخش‌بندی بر اساس ترجمه‌ه</string>\n    <string name=\"split_by_translations_summary\">نمایش فصل ها با ترجمه های مختلف به صورت جداگانه بجای نمایش در یک لیست</string>\n    <string name=\"order_oldest\">قدیمی ترین</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fi/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d tunti sitten</item>\n        <item quantity=\"other\">%1$d tuntia sitten</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d luku</item>\n        <item quantity=\"other\">%1$d lukua</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minuutti sitten</item>\n        <item quantity=\"other\">%1$d minuuttia sitten</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d kohde</item>\n        <item quantity=\"other\">%1$d kohdetta</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d uusi luku</item>\n        <item quantity=\"other\">%1$d uutta lukua</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d päivä sitten</item>\n        <item quantity=\"other\">%1$d päivää sitten</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d kuukausi sitten</item>\n        <item quantity=\"other\">%1$d kuukausia sitten</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d tunti</item>\n        <item quantity=\"other\">%1$d tuntia</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuutti</item>\n        <item quantity=\"other\">%1$d minuuttia</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"text_clear_cookies_prompt\">Sinut kirjaudutaan ulos kaikista lähteistä, joihin sinulla on valtuutus</string>\n    <string name=\"genres\">Tyylilajit</string>\n    <string name=\"auth_not_supported_by\">%s -valtuutusta ei tueta</string>\n    <string name=\"auth_complete\">Valtuutus valmis</string>\n    <string name=\"about_app_translation\">Käännös</string>\n    <string name=\"about_app_translation_summary\">Käännä tämä sovellus</string>\n    <string name=\"chapter_is_missing\">Luku puuttuu</string>\n    <string name=\"queued\">Jonossa</string>\n    <string name=\"read_more\">Lue lisää</string>\n    <string name=\"tracker_warning\">Jotkin valmistajat voivat muuttaa järjestelmän käyttäytymistä, mikä voi keskeyttää taustatehtäviä.</string>\n    <string name=\"backup_saved\">Varmuuskopiointi tallennettu onnistuneesti</string>\n    <string name=\"welcome\">Tervetuloa</string>\n    <string name=\"text_clear_search_history_prompt\">Haluatko todella poistaa kaikki viimeaikaiset hakukyselyt\\?</string>\n    <string name=\"password_length_hint\">Salasanan on oltava vähintään 4 merkkiä</string>\n    <string name=\"confirm\">Vahvista</string>\n    <string name=\"protect_application_subtitle\">Syötä salasana, joka vaaditaan, kun sovellus käynnistyy</string>\n    <string name=\"next\">Seuraava</string>\n    <string name=\"default_s\">Oletusarvo: %s</string>\n    <string name=\"auth_required\">Sinun pitäisi valtuuttaa nähdäksesi tämän sisällön</string>\n    <string name=\"sign_in\">Kirjaudu sisään</string>\n    <string name=\"reverse\">Takaperin</string>\n    <string name=\"check_for_new_chapters\">Etsitään uusia lukuja</string>\n    <string name=\"text_clear_updates_feed_prompt\">Kaikki päivityshistoria tyhjennetään, eikä tätä toimenpidettä voi peruuttaa. Oletko varma\\?</string>\n    <string name=\"clear_feed\">Tyhjennä syöttö</string>\n    <string name=\"cookies_cleared\">Kaikki evästeet poistettiin</string>\n    <string name=\"clear_cookies\">Tyhjennä evästeet</string>\n    <string name=\"captcha_solve\">Ratkaise</string>\n    <string name=\"captcha_required\">CAPTCHA vaaditaan</string>\n    <string name=\"silent\">Äänetön</string>\n    <string name=\"reader_mode_hint\">Valittu kokoonpano muistetaan tästä mangasta</string>\n    <string name=\"tap_to_try_again\">Yritä uudelleen napauttamalla</string>\n    <string name=\"today\">Tänään</string>\n    <string name=\"group\">Ryhmä</string>\n    <string name=\"long_ago\">Kauan sitten</string>\n    <string name=\"yesterday\">Eilen</string>\n    <string name=\"just_now\">Juuri nyt</string>\n    <string name=\"backup_information\">Voit luoda varmuuskopion historiastasi ja suosikeistasi ja palauttaa sen</string>\n    <string name=\"data_restored_with_errors\">Tiedot palautettu, mutta niissä on virheitä</string>\n    <string name=\"data_restored_success\">Kaikki tiedot palautettu onnistuneesti</string>\n    <string name=\"file_not_found\">Tiedostoa ei löytynyt</string>\n    <string name=\"preparing_\">Valmistellaan…</string>\n    <string name=\"data_restored\">Tiedot palautettu</string>\n    <string name=\"restore_backup\">Palauta varmuuskopiosta</string>\n    <string name=\"create_backup\">Luo tietojen varmuuskopio</string>\n    <string name=\"backup_restore\">Varmuuskopiointi ja palautus</string>\n    <string name=\"black_dark_theme_summary\">Hyödyllinen AMOLED-näytöille</string>\n    <string name=\"black_dark_theme\">Musta tumma teema</string>\n    <string name=\"zoom_mode_keep_start\">Pidä alussa</string>\n    <string name=\"zoom_mode_fit_width\">Sovita leveyteen</string>\n    <string name=\"zoom_mode_fit_height\">Sovita korkeuteen</string>\n    <string name=\"zoom_mode_fit_center\">Sovita keskelle</string>\n    <string name=\"scale_mode\">Skaalaustila</string>\n    <string name=\"create_category\">Uusi luokka</string>\n    <string name=\"right_to_left\">Oikealta vasemmalle</string>\n    <string name=\"no_update_available\">Ei päivityksiä saatavilla</string>\n    <string name=\"check_for_updates\">Tarkista päivitykset</string>\n    <string name=\"app_version\">Versio %s</string>\n    <string name=\"about\">Tietoja</string>\n    <string name=\"passwords_mismatch\">Salasanat eivät täsmää</string>\n    <string name=\"dont_check\">Älä tarkista</string>\n    <string name=\"track_sources\">Tarkista mangan päivitykset</string>\n    <string name=\"feed_will_update_soon\">Syötteen päivitys alkaa pian</string>\n    <string name=\"update\">Päivitä</string>\n    <string name=\"rotate_screen\">Käännä näyttöä</string>\n    <string name=\"clear_updates_feed\">Tyhjennä päivityssyöte</string>\n    <string name=\"updates_feed_cleared\">Päivitykset syötteen tyhjennetty</string>\n    <string name=\"size_s\">Koko: %s</string>\n    <string name=\"new_version_s\">Uusi versio: %s</string>\n    <string name=\"search_results\">Hakutulokset</string>\n    <string name=\"text_feed_holder\">Täällä näet lukemasi mangan uudet luvut</string>\n    <string name=\"updates\">Päivitykset</string>\n    <string name=\"read_later\">Lue myöhemmin</string>\n    <string name=\"favourites_category_empty\">Tämä luokka on tyhjä</string>\n    <string name=\"all_favourites\">Kaikki suosikit</string>\n    <string name=\"done\">Valmis</string>\n    <string name=\"other_storage\">Muu tallennustila</string>\n    <string name=\"cannot_find_available_storage\">Ei löydy vapaata tallennustilaa</string>\n    <string name=\"not_available\">Ei saatavilla</string>\n    <string name=\"manga_save_location\">Mangan latauspaikka</string>\n    <string name=\"pages_animation\">Sivujen animaatio</string>\n    <string name=\"recent_manga\">Viimeaikaiset mangat</string>\n    <string name=\"manga_shelf\">Mangahylly</string>\n    <string name=\"text_local_holder_secondary\">Voit tallentaa sen verkkolähteistä tai tuoda sen tiedostosta.</string>\n    <string name=\"text_local_holder_primary\">Sinulla ei ole vielä yhtään tallennettua mangaa</string>\n    <string name=\"text_history_holder_secondary\">Löydät luettavaa sivuvalikosta.</string>\n    <string name=\"text_history_holder_primary\">Manga, jota luet, näytetään täällä</string>\n    <string name=\"text_search_holder_secondary\">Yritä muotoilla kysely uudelleen.</string>\n    <string name=\"text_empty_holder_primary\">Täällä on aika tyhjää…</string>\n    <string name=\"remove_category\">Poista luokka</string>\n    <string name=\"favourites_categories\">Suosikkiluokat</string>\n    <string name=\"vibration\">Tärinä</string>\n    <string name=\"light_indicator\">Merkkivalo</string>\n    <string name=\"notification_sound\">Ilmoitusääni</string>\n    <string name=\"notifications_settings\">Ilmoitusten asetukset</string>\n    <string name=\"download\">Lataa</string>\n    <string name=\"new_chapters\">Uusia lukuja</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Käytössä %1$d / %2$d</string>\n    <string name=\"notifications\">Ilmoitukset</string>\n    <string name=\"open_in_browser\">Avaa selaimessa</string>\n    <string name=\"app_update_available\">Sovelluksen päivitys on saatavilla</string>\n    <string name=\"domain\">Verkkotunnus</string>\n    <string name=\"external_storage\">Ulkoinen tallennustila</string>\n    <string name=\"internal_storage\">Sisäinen tallennustila</string>\n    <string name=\"search_history_cleared\">Hakuhistoria tyhjennetty</string>\n    <string name=\"clear_search_history\">Tyhjennä hakuhistoria</string>\n    <string name=\"clear_thumbs_cache\">Tyhjennä pikkukuvien välimuisti</string>\n    <string name=\"error\">Virhe</string>\n    <string name=\"_continue\">Jatka</string>\n    <string name=\"switch_pages\">Vaihda sivuja</string>\n    <string name=\"reader_settings\">Lukijan asetukset</string>\n    <string name=\"text_delete_local_manga\">Haluatko todella poistaa ”%s” puhelimen paikallisesta tallennustilasta?</string>\n    <string name=\"delete_manga\">Poista manga</string>\n    <string name=\"search_on_s\">Hae kohteesta %s</string>\n    <string name=\"grid_size\">Ruudukon koko</string>\n    <string name=\"read_mode\">Lukutila</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"standard\">Vakio</string>\n    <string name=\"text_file_sizes\">t|kt|Mt|Gt|Tt</string>\n    <string name=\"clear_pages_cache\">Tyhjennä sivujen välimuisti</string>\n    <string name=\"no_description\">Ei kuvausta</string>\n    <string name=\"text_file_not_supported\">Virheellinen tiedosto. Vain ZIP ja CBZ ovat tuettuja.</string>\n    <string name=\"operation_not_supported\">Tätä toimintoa ei tueta</string>\n    <string name=\"delete\">Poista</string>\n    <string name=\"_import\">Tuo</string>\n    <string name=\"share_image\">Jaa kuva</string>\n    <string name=\"page_saved\">Sivu tallennettu onnistuneesti</string>\n    <string name=\"save_page\">Tallenna sivu</string>\n    <string name=\"_s_deleted_from_local_storage\">”%s” poistettu paikallisesta tallennustilasta</string>\n    <string name=\"remove\">Poista</string>\n    <string name=\"clear\">Tyhjennä</string>\n    <string name=\"pages\">Sivut</string>\n    <string name=\"follow_system\">Automaattinen</string>\n    <string name=\"dark\">Tumma</string>\n    <string name=\"light\">Vaalea</string>\n    <string name=\"theme\">Teema</string>\n    <string name=\"filter\">Suodatin</string>\n    <string name=\"sort_order\">Lajittelujärjestys</string>\n    <string name=\"by_rating\">Luokituksen mukaan</string>\n    <string name=\"newest\">Uusimmat</string>\n    <string name=\"updated\">Päivitetty</string>\n    <string name=\"popular\">Suosittu</string>\n    <string name=\"by_name\">Nimen mukaan</string>\n    <string name=\"downloads\">Lataukset</string>\n    <string name=\"download_complete\">Lataus valmis</string>\n    <string name=\"processing_\">Käsitellään…</string>\n    <string name=\"manga_downloading_\">Manga ladataan…</string>\n    <string name=\"search_manga\">Etsi manga</string>\n    <string name=\"search\">Etsi</string>\n    <string name=\"share_s\">Jaa %s</string>\n    <string name=\"create_shortcut\">Luo pikakuvake…</string>\n    <string name=\"share\">Jaa</string>\n    <string name=\"save\">Tallenna</string>\n    <string name=\"add\">Lisää</string>\n    <string name=\"add_new_category\">Lisää uusi luokka</string>\n    <string name=\"add_to_favourites\">Lisää suosikkeihin</string>\n    <string name=\"you_have_not_favourites_yet\">Sinulla ei ole vielä suosikkeja</string>\n    <string name=\"read\">Lue</string>\n    <string name=\"history_is_empty\">Historia on tyhjä</string>\n    <string name=\"nothing_found\">Mitään ei löytynyt</string>\n    <string name=\"clear_history\">Tyhjennä historia</string>\n    <string name=\"try_again\">Yritä uudelleen</string>\n    <string name=\"close\">Sulje</string>\n    <string name=\"chapter_d_of_d\">Luku %1$d / %2$d</string>\n    <string name=\"loading_\">Ladataan…</string>\n    <string name=\"remote_sources\">Mangalähteet</string>\n    <string name=\"settings\">Asetukset</string>\n    <string name=\"list_mode\">Luettelotila</string>\n    <string name=\"grid\">Ruudukko</string>\n    <string name=\"list\">Luettelo</string>\n    <string name=\"detailed_list\">Yksityiskohtainen luettelo</string>\n    <string name=\"chapters\">Luvut</string>\n    <string name=\"details\">Yksityiskohdat</string>\n    <string name=\"network_error\">Verkkoyhteysvirhe</string>\n    <string name=\"error_occurred\">On tapahtunut virhe</string>\n    <string name=\"history\">Historia</string>\n    <string name=\"favourites\">Suosikit</string>\n    <string name=\"local_storage\">Paikallinen tallennustila</string>\n    <string name=\"repeat_password\">Toista salasana</string>\n    <string name=\"protect_application_summary\">Kysy salasanaa sovelluksen alkaessa</string>\n    <string name=\"protect_application\">Suojaa sovellus</string>\n    <string name=\"wrong_password\">Väärä salasana</string>\n    <string name=\"enter_password\">Syötä salasana</string>\n    <string name=\"state_finished\">Päättynyt</string>\n    <string name=\"state_ongoing\">Jatkuva</string>\n    <string name=\"system_default\">Oletus</string>\n    <string name=\"exclude_nsfw_from_history\">Sulje NSFW-mangat pois historiasta</string>\n    <string name=\"show_pages_numbers\">Numeroidut sivut</string>\n    <string name=\"screenshots_policy\">Kuvakaappausten politiikka</string>\n    <string name=\"screenshots_allow\">Salli</string>\n    <string name=\"screenshots_block_nsfw\">Estä NSFW:lle</string>\n    <string name=\"screenshots_block_all\">Estä aina</string>\n    <string name=\"computing_\">Lasketaan…</string>\n    <string name=\"suggestions\">Ehdotukset</string>\n    <string name=\"suggestions_summary\">Ehdota mangaa mieltymystesi perusteella</string>\n    <string name=\"suggestions_info\">Kaikki tiedot analysoidaan paikallisesti tässä laitteessa. Henkilötietojasi ei siirretä mihinkään palveluihin</string>\n    <string name=\"suggestions_enable\">Ota ehdotukset käyttöön</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Älä ehdota NSFW-mangaa</string>\n    <string name=\"text_suggestion_holder\">Aloita mangan lukeminen ja saat henkilökohtaisia ehdotuksia</string>\n    <string name=\"enabled\">Käytössä</string>\n    <string name=\"disabled\">Pois päältä</string>\n    <string name=\"only_using_wifi\">Vain Wi-Fi:n käyttö</string>\n    <string name=\"preload_pages\">Esilataa sivut</string>\n    <string name=\"always\">Aina</string>\n    <string name=\"never\">Ei koskaan</string>\n    <string name=\"reset_filter\">Nollaa suodatin</string>\n    <string name=\"onboard_text\">Valitse kielet, joilla haluat lukea mangaa. Voit muuttaa sitä myöhemmin asetuksissa.</string>\n    <string name=\"logged_in_as\">Kirjautunut sisään nimellä %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"search_chapters\">Etsi luku</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"various_languages\">Eri kieliä</string>\n    <string name=\"chapters_empty\">Ei lukuja tässä mangassa</string>\n    <string name=\"appearance\">Ulkonäkö</string>\n    <string name=\"suggestions_updating\">Ehdotukset päivitetään</string>\n    <string name=\"suggestions_excluded_genres_summary\">Määritä genret, joita et halua nähdä ehdotuksissa</string>\n    <string name=\"suggestions_excluded_genres\">Sulje pois genrejä</string>\n    <string name=\"text_delete_local_manga_batch\">Poista valitut kohteet laitteesta pysyvästi\\?</string>\n    <string name=\"removal_completed\">Poisto valmis</string>\n    <string name=\"download_slowdown\">Latauksen hidastuminen</string>\n    <string name=\"download_slowdown_summary\">Auttaa välttämään IP-osoitteesi estämisen</string>\n    <string name=\"chapters_will_removed_background\">Luvut poistetaan taustalla. Se voi kestää jonkin aikaa</string>\n    <string name=\"local_manga_processing\">Tallennettujen mangojen käsittely</string>\n    <string name=\"hide\">Piilota</string>\n    <string name=\"new_sources_text\">Uusia mangalähteitä on saatavilla</string>\n    <string name=\"check_new_chapters_title\">Tarkista uudet luvut ja ilmoita siitä</string>\n    <string name=\"notifications_enable\">Ota ilmoitukset käyttöön</string>\n    <string name=\"empty_favourite_categories\">Ei suosikkiluokkia</string>\n    <string name=\"name\">Nimi</string>\n    <string name=\"edit\">Muokkaa</string>\n    <string name=\"edit_category\">Muokkaa luokkaa</string>\n    <string name=\"show_notification_new_chapters_off\">Et saa ilmoituksia, mutta uudet luvut korostetaan luetteloissa</string>\n    <string name=\"show_notification_new_chapters_on\">Saat ilmoituksia lukemasi mangan päivityksistä</string>\n    <string name=\"bookmark_add\">Lisää kirjanmerkki</string>\n    <string name=\"bookmark_remove\">Poista kirjanmerkki</string>\n    <string name=\"bookmarks\">Kirjanmerkit</string>\n    <string name=\"bookmark_removed\">Kirjanmerkki poistettu</string>\n    <string name=\"bookmark_added\">Kirjanmerkki lisätty</string>\n    <string name=\"undo\">Kumoa</string>\n    <string name=\"removed_from_history\">Poistettu historiasta</string>\n    <string name=\"dns_over_https\">DNS HTTPS:n kautta</string>\n    <string name=\"detect_reader_mode\">Lukijan automaattinen tunnistaminen</string>\n    <string name=\"detect_reader_mode_summary\">Tunnista automaattisesti, onko manga webtoon</string>\n    <string name=\"default_mode\">Oletustila</string>\n    <string name=\"disable_battery_optimization\">Poista akun optimoinnin käytöstä</string>\n    <string name=\"disable_battery_optimization_summary\">Auttaa taustapäivitystarkastuksissa</string>\n    <string name=\"crash_text\">Jokin meni pieleen. Lähetä vikailmoitus kehittäjille, jotta voimme korjata sen.</string>\n    <string name=\"disable_all\">Poista kaikki käytöstä</string>\n    <string name=\"send\">Lähetä</string>\n    <string name=\"use_fingerprint\">Käytä sormenjälkeä, jos käytettävissä</string>\n    <string name=\"appwidget_shelf_description\">Manga suosikeistasi</string>\n    <string name=\"appwidget_recent_description\">Äskettäin lukemasi manga</string>\n    <string name=\"tracking\">Seuranta</string>\n    <string name=\"logout\">Kirjaudu ulos</string>\n    <string name=\"status_reading\">Lukemassa</string>\n    <string name=\"status_re_reading\">Lukemassa uudelleen</string>\n    <string name=\"data_deletion\">Tietojen poistaminen</string>\n    <string name=\"show_all\">Näytä kaikki</string>\n    <string name=\"select_range\">Valitse alue</string>\n    <string name=\"not_found_404\">Sisältöä ei löydy tai se on poistettu</string>\n    <string name=\"retry\">Yritä uudelleen</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fil/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d aytem</item>\n        <item quantity=\"other\">%1$d (na) aytem</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minutong nakakalipas</item>\n        <item quantity=\"other\">%1$d (na) minutong nakakalipas</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d bagong kabanata</item>\n        <item quantity=\"other\">%1$d mga bagong kabanata</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">\"%1$d  kabanata\"</item>\n        <item quantity=\"other\">%1$d (na) kabanata</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d oras ang nakalipas</item>\n        <item quantity=\"other\">%1$d (na) oras ang nakalipas</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d araw ang nakalipas</item>\n        <item quantity=\"other\">%1$d (na) araw ang nakalipas</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d buwan nakakalipas</item>\n        <item quantity=\"other\">%1$d (na) buwan nakakalipas</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d oras</item>\n        <item quantity=\"other\">%1$d (na) oras</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuto</item>\n        <item quantity=\"other\">%1$d (na) minuto</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-fil/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"updated\">Na-update</string>\n    <string name=\"newest\">Pinakabago</string>\n    <string name=\"light\">Maliwanag</string>\n    <string name=\"by_rating\">Marka</string>\n    <string name=\"filter\">Pansala</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"dark\">Madilim</string>\n    <string name=\"follow_system\">Sundan ang sistema</string>\n    <string name=\"error_occurred\">May nangyaring error</string>\n    <string name=\"network_error\">Error sa network</string>\n    <string name=\"details\">Mga detalye</string>\n    <string name=\"chapters\">Mga kabanata</string>\n    <string name=\"list\">Listahan</string>\n    <string name=\"page_saved\">Na-save ang pahina</string>\n    <string name=\"share_image\">I-share ang larawan</string>\n    <string name=\"_import\">Mag-import</string>\n    <string name=\"delete\">Tanggalin</string>\n    <string name=\"operation_not_supported\">Hindi suportado ang operasyong ito</string>\n    <string name=\"text_file_not_supported\">Pumili ng ZIP o CBZ file.</string>\n    <string name=\"no_description\">Walang paglalarawan</string>\n    <string name=\"grid_size\">Laki ng grid</string>\n    <string name=\"search_on_s\">Hanapin sa %s</string>\n    <string name=\"delete_manga\">Tanggalin ang manga</string>\n    <string name=\"text_delete_local_manga\">Permanenteng tanggalin ang \\\"%s\\\" mula sa device?</string>\n    <string name=\"reader_settings\">Mga setting sa reader</string>\n    <string name=\"switch_pages\">Magpalit ng (mga) pahina</string>\n    <string name=\"_continue\">Magpatuloy</string>\n    <string name=\"clear_thumbs_cache\">Linisin ang cache ng mga thumbnail</string>\n    <string name=\"search_history_cleared\">Nalinisan na</string>\n    <string name=\"app_update_available\">Available ang isang bagong bersyon ng app</string>\n    <string name=\"open_in_browser\">Buksan sa web browser</string>\n    <string name=\"notifications\">Mga abiso</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d ng %2$d ay napagana</string>\n    <string name=\"new_chapters\">Mga bagong kabanata</string>\n    <string name=\"text_search_holder_secondary\">Subukang i-reformulate ang query.</string>\n    <string name=\"text_history_holder_primary\">Ang iyong nabasa ay ipapakita dito</string>\n    <string name=\"text_local_holder_primary\">Mag-save muna ng isa</string>\n    <string name=\"text_local_holder_secondary\">Mag-save ng isang bagay mula sa isang online na catalog o i-import ito mula sa isang file.</string>\n    <string name=\"manga_shelf\">Istante</string>\n    <string name=\"pages_animation\">Animasyon ng pahina</string>\n    <string name=\"not_available\">Hindi magagamit</string>\n    <string name=\"cannot_find_available_storage\">Walang available na storage</string>\n    <string name=\"other_storage\">Iba pang storage</string>\n    <string name=\"done\">Tapos na</string>\n    <string name=\"all_favourites\">Lahat ng paborito</string>\n    <string name=\"favourites_category_empty\">Walang laman ang kategorya</string>\n    <string name=\"read_later\">Basahin mamaya</string>\n    <string name=\"updates\">Mga update</string>\n    <string name=\"search_results\">Mga resulta ng paghahanap</string>\n    <string name=\"size_s\">Laki: %s</string>\n    <string name=\"clear_updates_feed\">Linisin ang feed ng mga update</string>\n    <string name=\"updates_feed_cleared\">Nalinisan na</string>\n    <string name=\"update\">Update</string>\n    <string name=\"feed_will_update_soon\">Ang pag update ng feed ay magsisimula sa lalong madaling panahon</string>\n    <string name=\"track_sources\">Maghanap ng mga update</string>\n    <string name=\"enter_password\">Ilagay ang password</string>\n    <string name=\"protect_application_summary\">Humingi ng password kapag sinimulan ang Kotatsu</string>\n    <string name=\"about\">Tungkol rito</string>\n    <string name=\"check_for_updates\">Maghanap ng update</string>\n    <string name=\"right_to_left\">Kanan sa kaliwa</string>\n    <string name=\"create_category\">Bagong Kategorya</string>\n    <string name=\"zoom_mode_fit_center\">Pagkasyahin sa gitna</string>\n    <string name=\"zoom_mode_keep_start\">Panatilihin sa simula</string>\n    <string name=\"black_dark_theme_summary\">Gumagamit ng mas kaunting power sa mga AMOLED na screen</string>\n    <string name=\"backup_restore\">I-backup at I-restore</string>\n    <string name=\"data_restored\">Naibalik na</string>\n    <string name=\"preparing_\">Naghahanda…</string>\n    <string name=\"file_not_found\">Hindi nahanap ang file</string>\n    <string name=\"backup_information\">Maaari kang lumikha ng backup ng iyong kasaysayan at mga paborito at i-restore ito</string>\n    <string name=\"just_now\">Ngayon lang</string>\n    <string name=\"yesterday\">Kahapon</string>\n    <string name=\"long_ago\">Matagal na ang nakalipas</string>\n    <string name=\"today\">Ngayong araw</string>\n    <string name=\"tap_to_try_again\">I-tap para subukang muli</string>\n    <string name=\"captcha_solve\">Lutasin</string>\n    <string name=\"cookies_cleared\">Natanggal ang lahat ng mga cookie</string>\n    <string name=\"clear_feed\">Linisin ang feed</string>\n    <string name=\"check_for_new_chapters\">Suriin ang mga bagong kabanata</string>\n    <string name=\"sign_in\">Mag-sign in</string>\n    <string name=\"auth_required\">Mag-sign in upang tingnan ang nilalamang ito</string>\n    <string name=\"default_s\">Default: %s</string>\n    <string name=\"next\">Susunod</string>\n    <string name=\"confirm\">Kumpirmahin</string>\n    <string name=\"password_length_hint\">Ang password ay dapat na 4 na character o higit pa</string>\n    <string name=\"welcome\">Maligayang pagdating</string>\n    <string name=\"backup_saved\">Na-save ang backup</string>\n    <string name=\"read_more\">Magbasa pa</string>\n    <string name=\"chapter_is_missing\">Kulang ang kabanata</string>\n    <string name=\"auth_not_supported_by\">Ang pag-log in sa %s ay hindi suportado</string>\n    <string name=\"genres\">Mga genre</string>\n    <string name=\"state_ongoing\">Patuloy</string>\n    <string name=\"system_default\">Default</string>\n    <string name=\"exclude_nsfw_from_history\">Hindi isali ang NSFW manga mula sa kasaysayan</string>\n    <string name=\"show_pages_numbers\">Mga pahinang may bilang</string>\n    <string name=\"screenshots_policy\">Patakaran sa screenshot</string>\n    <string name=\"screenshots_allow\">Payagan</string>\n    <string name=\"screenshots_block_all\">Palaging i-block</string>\n    <string name=\"suggestions\">Mga mungkahi</string>\n    <string name=\"suggestions_enable\">Paganahin ang mga mungkahi</string>\n    <string name=\"text_suggestion_holder\">Simulan ang pagbabasa ng manga at makakakuha ka ng mga personalized na mungkahi</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Huwag magmungkahi ng NSFW manga</string>\n    <string name=\"enabled\">Pinagana</string>\n    <string name=\"onboard_text\">Pumili ng mga wika na gusto mong basahin ang manga. Maaari mo itong baguhin sa ibang pagkakataon sa mga setting.</string>\n    <string name=\"local_storage\">Lokal na storage</string>\n    <string name=\"favourites\">Mga paborito</string>\n    <string name=\"history\">Kasaysayan</string>\n    <string name=\"list_mode\">Mode na listahan</string>\n    <string name=\"detailed_list\">Detalyadong listahan</string>\n    <string name=\"grid\">Grid</string>\n    <string name=\"settings\">Mga setting</string>\n    <string name=\"remote_sources\">Mga source ng Manga</string>\n    <string name=\"loading_\">Naglo-load…</string>\n    <string name=\"close\">Isara</string>\n    <string name=\"nothing_found\">Walang nahanap</string>\n    <string name=\"remove\">Tanggalin</string>\n    <string name=\"add_new_category\">Bagong Kategorya</string>\n    <string name=\"read\">Magbasa</string>\n    <string name=\"you_have_not_favourites_yet\">Wala pang paborito</string>\n    <string name=\"add_to_favourites\">I-paborito ito</string>\n    <string name=\"add\">Idagdag</string>\n    <string name=\"save\">I-save</string>\n    <string name=\"share\">Ibahagi</string>\n    <string name=\"create_shortcut\">Lumikha ng shortcut</string>\n    <string name=\"share_s\">Ibahagi sa %s</string>\n    <string name=\"search\">Maghanap</string>\n    <string name=\"search_manga\">Maghanap ng manga</string>\n    <string name=\"manga_downloading_\">Nagda-download…</string>\n    <string name=\"processing_\">Nagpoproseso…</string>\n    <string name=\"download_complete\">Na-download</string>\n    <string name=\"downloads\">Mga download</string>\n    <string name=\"by_name\">Pangalan</string>\n    <string name=\"popular\">Sikat</string>\n    <string name=\"pages\">Mga pahina</string>\n    <string name=\"clear_history\">Linisin ang kasaysayan</string>\n    <string name=\"clear_search_history\">Linisin ang kasaysayan ng paghahanap</string>\n    <string name=\"new_version_s\">Bagong bersyon: %s</string>\n    <string name=\"passwords_mismatch\">Hindi tumutugma sa mga password</string>\n    <string name=\"clear_cookies\">Linisin ang mga cookie</string>\n    <string name=\"clear_pages_cache\">Linisin ang page cache</string>\n    <string name=\"download\">I-download</string>\n    <string name=\"notifications_settings\">Mga setting ng abiso</string>\n    <string name=\"notification_sound\">Tunog ng abiso</string>\n    <string name=\"favourites_categories\">Mga paboritong kategorya</string>\n    <string name=\"remove_category\">Tanggalin</string>\n    <string name=\"text_empty_holder_primary\">Parang walang laman dito…</string>\n    <string name=\"text_history_holder_secondary\">Hanapin kung anong pwedeng basahin sa «Mag-Explore» na seksyon</string>\n    <string name=\"recent_manga\">Kamakailan</string>\n    <string name=\"manga_save_location\">Mga folder ng pag-download</string>\n    <string name=\"save_page\">I-save ang pahina</string>\n    <string name=\"_s_deleted_from_local_storage\">Natanggal ang \\\"%s\\\" sa lokal na storage</string>\n    <string name=\"history_is_empty\">Wala pang kasaysayan</string>\n    <string name=\"dont_check\">Huwag suriin</string>\n    <string name=\"repeat_password\">Ulitin ang password</string>\n    <string name=\"protect_application\">Protektahan ang app</string>\n    <string name=\"wrong_password\">Maling password</string>\n    <string name=\"app_version\">Bersyon %s</string>\n    <string name=\"scale_mode\">Mode ng scale</string>\n    <string name=\"no_update_available\">Walang available na update</string>\n    <string name=\"reverse\">Baliktarin</string>\n    <string name=\"group\">Grupo</string>\n    <string name=\"silent\">Tahimik</string>\n    <string name=\"zoom_mode_fit_width\">Pagkasyahin sa lapad</string>\n    <string name=\"black_dark_theme\">Itim</string>\n    <string name=\"create_backup\">Lumikha ng data backup</string>\n    <string name=\"restore_backup\">I-restore mula sa backup</string>\n    <string name=\"data_restored_success\">Na-restore ang lahat ng data</string>\n    <string name=\"data_restored_with_errors\">Ang data ay na-restore, ngunit may mga error</string>\n    <string name=\"reader_mode_hint\">Ang napiling konpigurasyon ay maaalala para sa manga na ito</string>\n    <string name=\"about_app_translation_summary\">Isalin ang app na ito</string>\n    <string name=\"auth_complete\">Awtorisado na</string>\n    <string name=\"captcha_required\">Kinakailangan ang CAPTCHA</string>\n    <string name=\"text_clear_updates_feed_prompt\">Linisin nang permanente ang lahat ng update history?</string>\n    <string name=\"protect_application_subtitle\">Maglagay ng password para simulan ang app</string>\n    <string name=\"tracker_warning\">Ang ilang device ay may iba\\'t ibang gawi ng system, na maaaring masira ang mga gawain sa background.</string>\n    <string name=\"queued\">Nakapila na</string>\n    <string name=\"text_clear_cookies_prompt\">Mala-log out ka mula sa lahat ng source</string>\n    <string name=\"state_finished\">Tapos na</string>\n    <string name=\"text_clear_search_history_prompt\">Alisin ang lahat ng kamakailang query sa paghahanap nang permanente\\?</string>\n    <string name=\"about_app_translation\">Pagsasalin</string>\n    <string name=\"screenshots_block_nsfw\">I-block sa NSFW</string>\n    <string name=\"suggestions_summary\">Magmungkahi ng manga batay sa iyong mga kagustuhan</string>\n    <string name=\"suggestions_info\">Ang lahat ng data ay lokal lamang na sinusuri sa device na ito at hindi kailanman ipinadala kahit saan.</string>\n    <string name=\"disabled\">Di pinagana</string>\n    <string name=\"reset_filter\">I-reset ang filter</string>\n    <string name=\"text_feed_holder\">Ang mga bagong kabanata ng iyong binabasa ay ipinapakita dito</string>\n    <string name=\"rotate_screen\">I-rotate ang screen</string>\n    <string name=\"zoom_mode_fit_height\">Pagkasyahin sa tangkad</string>\n    <string name=\"never\">Hindi kailanman</string>\n    <string name=\"only_using_wifi\">Sa Wi-Fi lang</string>\n    <string name=\"computing_\">Nagco-compute…</string>\n    <string name=\"chapter_d_of_d\">Kabanata %1$d ng %2$d</string>\n    <string name=\"try_again\">Subukan ulit</string>\n    <string name=\"sort_order\">Pag-aayos ng order</string>\n    <string name=\"clear\">Linisin</string>\n    <string name=\"always\">Lagi na lang</string>\n    <string name=\"preload_pages\">I-preload ang mga pahina</string>\n    <string name=\"logged_in_as\">Naka-log in bilang %s</string>\n    <string name=\"various_languages\">Iba\\'t ibang wika</string>\n    <string name=\"search_chapters\">Maghanap ng kabanata</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Hitsura</string>\n    <string name=\"suggestions_excluded_genres\">Hindi isali ang mga genre</string>\n    <string name=\"suggestions_excluded_genres_summary\">Tukuyin ang mga genre na hindi mo nais na makita sa mga mungkahi</string>\n    <string name=\"removal_completed\">Nakumpleto na ang pagtanggal</string>\n    <string name=\"download_slowdown_summary\">Tumutulong na maiwasan ang pag-block ng iyong IP address</string>\n    <string name=\"local_manga_processing\">Naka-save na pagproseso ng manga</string>\n    <string name=\"account_already_exists\">Mayroon nang account</string>\n    <string name=\"back\">Bumalik</string>\n    <string name=\"sync\">Pag-synchronize</string>\n    <string name=\"email_enter_hint\">Ilagay ang iyong email upang magpatuloy</string>\n    <string name=\"hide\">Itago</string>\n    <string name=\"new_sources_text\">May mga bagong source ng manga ay available</string>\n    <string name=\"show_notification_new_chapters_off\">Hindi ka makakatanggap ng mga abiso ngunit ang mga bagong kabanata ay iha-highlight sa mga listahan</string>\n    <string name=\"notifications_enable\">Paganahin ang mga abiso</string>\n    <string name=\"edit_category\">Ayusin ang kategorya</string>\n    <string name=\"tracking\">Tina-track</string>\n    <string name=\"empty_favourite_categories\">Walang mga paboritong kategorya</string>\n    <string name=\"logout\">Mag-log out</string>\n    <string name=\"bookmark_add\">Magdagdag ng bookmark</string>\n    <string name=\"bookmark_removed\">Tinanggal ang bookmark</string>\n    <string name=\"removed_from_history\">Inalis sa kasaysayan</string>\n    <string name=\"dns_over_https\">DNS sa HTTPS</string>\n    <string name=\"default_mode\">Default na mode</string>\n    <string name=\"detect_reader_mode\">Automatikong matukoy ang reader mode</string>\n    <string name=\"crash_text\">May nangyaring mali. Mangyaring magsumite ng isang bug report sa mga developer upang matulungan kaming ayusin ito.</string>\n    <string name=\"send\">Ipadala</string>\n    <string name=\"status_re_reading\">Muling pagbabasa</string>\n    <string name=\"status_dropped\">Itinigil</string>\n    <string name=\"appwidget_shelf_description\">Manga mula sa iyong mga paborito</string>\n    <string name=\"appwidget_recent_description\">Ang iyong kamakailang nabasa na manga</string>\n    <string name=\"data_deletion\">Pagtanggal ng data</string>\n    <string name=\"show_reading_indicators_summary\">Ipakita ang porsyento na nabasa sa kasaysayan at mga paborito</string>\n    <string name=\"show_all\">Ipakita lahat</string>\n    <string name=\"select_range\">Pumili ng saklaw</string>\n    <string name=\"clear_all_history\">Linisin lahat ng kasaysayan</string>\n    <string name=\"no_bookmarks_summary\">Maaari kang lumikha ng bookmark habang nagbabasa ng manga</string>\n    <string name=\"bookmarks_removed\">Tinanggal ang mga bookmark</string>\n    <string name=\"random\">Random</string>\n    <string name=\"no_manga_sources\">Walang mga source ng manga</string>\n    <string name=\"no_manga_sources_text\">Paganahin ang mga source ng manga upang basahin ang manga online</string>\n    <string name=\"reorder\">Ayusin muli</string>\n    <string name=\"empty\">Walang laman</string>\n    <string name=\"confirm_exit\">Pindutin muli ang Bumalik upang lumabas</string>\n    <string name=\"exit_confirmation_summary\">Pindutin ang Bumalik nang dalawang beses upang lumabas sa app</string>\n    <string name=\"exit_confirmation\">Pagkumpirma ng paglabas</string>\n    <string name=\"saved_manga\">Na-save na manga</string>\n    <string name=\"explore\">Mag-Explore</string>\n    <string name=\"other_cache\">Iba pang cache</string>\n    <string name=\"storage_usage\">Paggamit ng storage</string>\n    <string name=\"available\">Magagamit na</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"removed_from_favourites\">Inalis sa mga paborito</string>\n    <string name=\"options\">Mga pagpipilian</string>\n    <string name=\"incognito_mode\">Incognito na mode</string>\n    <string name=\"no_chapters\">Walang mga kabanata</string>\n    <string name=\"automatic_scroll\">Awtomatikong pag-scroll</string>\n    <string name=\"reader_info_pattern\">Kab. %1$d/%2$d Pah. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Ipakita ang information bar sa reader</string>\n    <string name=\"comics_archive\">Archive ng mga comics</string>\n    <string name=\"folder_with_images\">Folder na may mga larawan</string>\n    <string name=\"import_completed\">Nakumpleto na ang pag-import</string>\n    <string name=\"import_will_start_soon\">Magsisimula na ang pag-import</string>\n    <string name=\"feed\">Feed</string>\n    <string name=\"history_shortcuts_summary\">Gawing magagamit ang kamakailang manga sa pamamagitan ng mahabang pagpindot sa icon ng application</string>\n    <string name=\"history_shortcuts\">Ipakita ang mga kamakailang manga shortcut</string>\n    <string name=\"reader_control_ltr\">Ergonomic na kontrol sa reader</string>\n    <string name=\"color_correction\">Pagwawasto ng kulay</string>\n    <string name=\"brightness\">Liwanag</string>\n    <string name=\"contrast\">Kaibahan</string>\n    <string name=\"text_unsaved_changes_prompt\">I-save o kalimutan ang mga hindi na-save na pagbabago\\?</string>\n    <string name=\"discard\">Kalimutan</string>\n    <string name=\"error_no_space_left\">Walang natitirang espasyo sa device</string>\n    <string name=\"webtoon_zoom\">Pag-zoom sa webtoon</string>\n    <string name=\"server_error\">Server side error (%1$d). Subukang muli mamaya</string>\n    <string name=\"clear_new_chapters_counters\">Linisan rin ang impormasyon tungkol sa mga bagong kabanata</string>\n    <string name=\"prefetch_content\">Preloading ng nilalaman</string>\n    <string name=\"mark_as_current\">Markahan bilang kasalukuyan</string>\n    <string name=\"language\">Wika</string>\n    <string name=\"share_logs\">Ibahagi ang mga log</string>\n    <string name=\"show_suspicious_content\">Magpakita ng kahina-hinalang nilalaman</string>\n    <string name=\"theme_name_dynamic\">Dynamic</string>\n    <string name=\"show_in_grid_view\">Ipakita sa grid view</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"nothing_here\">Wala naman dito</string>\n    <string name=\"services\">Mga serbisyo</string>\n    <string name=\"allow_unstable_updates\">Payagan ang mga hindi stable na update</string>\n    <string name=\"show_reading_indicators\">Ipakita ang pang-hiwatig ng progreso</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Ang mga Manga na naka-mark na NSFW ay hindi masasali sa kasaysayan at ang progress mo ay hindi ma-sesave</string>\n    <string name=\"clear_cookies_summary\">Maaaring makatulong sa kaso ng ilang mga isyu. Ang lahat ng pahintulot ay mawawalan ng bisa</string>\n    <string name=\"invalid_domain_message\">Imbalidong domain</string>\n    <string name=\"last_2_hours\">Huling 2 oras</string>\n    <string name=\"history_cleared\">Nalinisan na ang kasaysayan</string>\n    <string name=\"manage\">Pamahalaan</string>\n    <string name=\"no_bookmarks_yet\">Wala pang bookmark</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"not_found_404\">Hindi natagpuan o inalis ang nilalaman</string>\n    <string name=\"enable_logging_summary\">Magtala ng ilang pagkilos para sa mga layunin ng pag-debug. Huwag buksan kung hindi mo alam kung ano ang ginagawa mo</string>\n    <string name=\"text_delete_local_manga_batch\">Permanenteng tanggalin ang mga napiling item sa device\\?</string>\n    <string name=\"chapters_empty\">Walang mga kabanata sa manga na ito</string>\n    <string name=\"suggestions_updating\">Nag-a-update ang mga mungkahi</string>\n    <string name=\"download_slowdown\">Pagbagal ng pag-download</string>\n    <string name=\"chapters_will_removed_background\">Tatanggalin ang mga kabanata sa background</string>\n    <string name=\"canceled\">Kinansela</string>\n    <string name=\"sync_title\">I-sync ang iyong data</string>\n    <string name=\"check_new_chapters_title\">Tingnan ang mga bagong kabanata at ipaalam ang tungkol dito</string>\n    <string name=\"name\">Pangalan</string>\n    <string name=\"edit\">I-edit</string>\n    <string name=\"bookmark_remove\">Tanggalin ang bookmark</string>\n    <string name=\"show_notification_new_chapters_on\">Makakatanggap ka ng mga abiso tungkol sa mga update ng manga na iyong binabasa</string>\n    <string name=\"undo\">I-undo</string>\n    <string name=\"status_reading\">Nagbabasa</string>\n    <string name=\"pages_cache\">Cache ng mga pahina</string>\n    <string name=\"bookmarks\">Mga bookmark</string>\n    <string name=\"categories_delete_confirm\">Sigurado ka bang gusto mong tanggalin ang mga napiling paboritong kategorya? \\nAng lahat ng manga sa loob nito ay mawawala at hindi na ito mababawi.</string>\n    <string name=\"bookmark_added\">Idinagdag ang bookmark</string>\n    <string name=\"detect_reader_mode_summary\">Awtomatikong matukoy kung ang manga ay webtoon</string>\n    <string name=\"disable_battery_optimization\">Di paganahin ang pag-optimize ng baterya</string>\n    <string name=\"disable_battery_optimization_summary\">Tumutulong sa mga pagsusuri sa mga update sa background</string>\n    <string name=\"status_planned\">Nakaplano</string>\n    <string name=\"status_completed\">Nakumpleto na</string>\n    <string name=\"status_on_hold\">Na-hold</string>\n    <string name=\"disable_all\">Di paganahin lahat</string>\n    <string name=\"use_fingerprint\">Gumamit ng biometric kung magagamit</string>\n    <string name=\"report\">Ulat</string>\n    <string name=\"reset\">I-reset</string>\n    <string name=\"allow_unstable_updates_summary\">Makakuha ng paunawa tungkol sa mga unstable build</string>\n    <string name=\"network_unavailable\">Hindi magagamit ang network</string>\n    <string name=\"network_unavailable_hint\">I-on ang Wi-Fi o mobile network para magbasa ng manga online</string>\n    <string name=\"reader_control_ltr_summary\">Huwag i-adjust ang direksyon ng pagpalit ng pahina sa reader mode, hal. kapag pindutin ang kanan (right) na key ay palaging lumilipat sa susunod na pahina. Ang opsyong ito ay nakakaapekto lamang sa mga hardware input device</string>\n    <string name=\"reader_slider\">Ipakita ang slider ng paglipat ng pahina</string>\n    <string name=\"manga_error_description_pattern\">Mga detalye ng error:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Subukang &lt;a href=%2$s&gt;magbukas ng manga sa isang web browser&lt;/a&gt; upang matiyak na available ito sa souce&lt;br&gt;2. Tiyaking ginagamit mo ang &lt;a href=kotatsu://about&gt;pinakabagong bersyon ng Kotatsu&lt;/a&gt;&lt;br&gt;3. Kung available ito, magpadala ng ulat ng error sa mga developer.</string>\n    <string name=\"enable_logging\">Paganahin ang pag-log</string>\n    <string name=\"source_disabled\">Di pinagana ang source</string>\n    <string name=\"importing_manga\">Pag-import ng manga</string>\n    <string name=\"import_completed_hint\">Maaari mong tanggalin ang orihinal na file mula sa storage upang makatipid ng espasyo</string>\n    <string name=\"compact\">Compact</string>\n    <string name=\"scrobbling_empty_hint\">Upang subaybayan ang pag unlad ng pagbabasa, piliin ang Menu → Track sa screen ng mga detalye ng manga.</string>\n    <string name=\"download_started\">Nagsimula na ang pag-download</string>\n    <string name=\"color_theme\">Scheme ng kulay</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"user_agent\">Header ng UserAgent</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Pamantayan</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Mode na pang-basa</string>\n    <string name=\"error\">Mayroong Error</string>\n    <string name=\"internal_storage\">Internal na storage</string>\n    <string name=\"external_storage\">External na storage</string>\n    <string name=\"vibration\">Vibration</string>\n    <string name=\"domain\">Domain</string>\n    <string name=\"light_indicator\">LED indicator</string>\n    <string name=\"settings_apply_restart_required\">I-restart ang application para ma-ilapat ang mga pagbabagong ito</string>\n    <string name=\"got_it\">Nakuha ko</string>\n    <string name=\"sources_reorder_tip\">I-tap at hawakan ang isang aytem upang muling ayusin ang mga ito</string>\n    <string name=\"show_on_shelf\">Ipakita sa Istante</string>\n    <string name=\"speed\">Bilis</string>\n    <string name=\"comics_archive_import_description\">Maaari kang pumili ng isa o higit pang .cbz o .zip file, ang bawat file ay makikilala bilang isang hiwalay na manga.</string>\n    <string name=\"folder_with_images_import_description\">Maaari kang pumili ng isang directory na may mga archive o mga larawan. Ang bawat archive (o subdirectory) ay makikilala bilang isang kabanata.</string>\n    <string name=\"find_similar\">Maghanap ng katulad</string>\n    <string name=\"sync_auth_hint\">Maaari kang mag-sign in sa isang umiiral na account o lumikha ng bago</string>\n    <string name=\"web_view_unavailable\">Hindi available ang WebView: tingnan kung naka-install ang WebView provider</string>\n    <string name=\"enable\">Paganahin</string>\n    <string name=\"downloads_paused\">Na-pause ang mga pag-download</string>\n    <string name=\"sync_settings\">Mga setting ng pag-synchronize</string>\n    <string name=\"server_address\">Address ng server</string>\n    <string name=\"sync_host_description\">Maaari kang gumamit ng self-hosted synchronization server o isang default. Huwag baguhin ito kung hindi ka sigurado sa iyong ginagawa.</string>\n    <string name=\"ignore_ssl_errors\">Huwag pansinin ang mga error sa SSL</string>\n    <string name=\"mirror_switching\">Awtomatikong pumili ng mirror</string>\n    <string name=\"mirror_switching_summary\">Awtomatikong lumipat ng domain para sa mga manga source kapag may error kung may available na mirror</string>\n    <string name=\"cancel_all\">Kanselahin lahat</string>\n    <string name=\"downloads_wifi_only\">Mag-download lamang sa pamamagitan ng Wi-Fi</string>\n    <string name=\"downloads_wifi_only_summary\">Itigil ang pag-download kapag lumipat sa isang mobile network</string>\n    <string name=\"paused\">Naka-pause</string>\n    <string name=\"remove_completed\">Tanggalin ang nakumpleto na</string>\n    <string name=\"suggestion_manga\">Mga mungkahi: %s</string>\n    <string name=\"pause\">I-pause</string>\n    <string name=\"suggestions_notifications_summary\">Minsang magpakita ng mga notification na may iminungkahing manga</string>\n    <string name=\"more\">Higit pa</string>\n    <string name=\"no_thanks\">Salamat nalang</string>\n    <string name=\"resume\">Ipagpatuloy</string>\n    <string name=\"cancel_all_downloads_confirm\">Ang lahat ng mga aktibong pag download ay kakanselahin, bahagyang na download na data ay mawawala</string>\n    <string name=\"remove_completed_downloads_confirm\">Permanenteng ide-delete ang iyong history ng mga pag-download. Walang mga na-download na file ay maapektuhan</string>\n    <string name=\"text_downloads_list_holder\">Wala kang anumang mga pag-download</string>\n    <string name=\"downloads_resumed\">Ipinagpatuloy ang mga pag-download</string>\n    <string name=\"suggestions_enable_prompt\">Gusto mo bang makatanggap ng personalized na mga mungkahi sa manga\\?</string>\n    <string name=\"downloads_removed\">Inalis na ang mga download</string>\n    <string name=\"downloads_cancelled\">Nakansela ang mga pag-download</string>\n    <string name=\"clear_network_cache\">Linisin ang network cache</string>\n    <string name=\"type\">Uri</string>\n    <string name=\"address\">Address</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"invalid_value_message\">Imbalidong value</string>\n    <string name=\"downloaded\">Na-download</string>\n    <string name=\"images_proxy_title\">Proxy sa pag-optimize ng mga imahe</string>\n    <string name=\"images_procy_description\">Gamitin ang serbisyo ng wsrv.nl upang bawasan ang paggamit ng trapiko at pabilisin ang pag-load ng imahe kung maaari</string>\n    <string name=\"username\">Username</string>\n    <string name=\"authorization_optional\">Awtorisasyon (opsyonal)</string>\n    <string name=\"network\">Network</string>\n    <string name=\"data_and_privacy\">Data at privacy</string>\n    <string name=\"restore_summary\">Ibalik ang dating ginawang backup</string>\n    <string name=\"webtoon_zoom_summary\">Payagan ang pag-zoom in na gesture sa webtoon mode</string>\n    <string name=\"reader_info_bar_summary\">Ipakita ang kasalukuyang oras at progreso ng pagbabasa sa tuktok ng screen</string>\n    <string name=\"show_pages_numbers_summary\">Ipakita ang mga numero ng pahina sa ibabang gilid</string>\n    <string name=\"invalid_port_number\">Imbalidong numero ng port</string>\n    <string name=\"clear_source_cookies_summary\">Linisin ang mga cookie para sa tinukoy na domain lamang. Sa karamihan ng mga kaso, magpapawalang-bisa ang awtorisasyon</string>\n    <string name=\"download_option_all_chapters\">Lahat ng mga kabanata na may pagsasalin na %s</string>\n    <string name=\"download_option_whole_manga\">Ang buong manga</string>\n    <string name=\"download_option_first_n_chapters\">Unang %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Susunod na di-nabasa %s</string>\n    <string name=\"download_option_all_unread\">Lahat ng di-nababasang kabanata</string>\n    <string name=\"download_option_all_unread_b\">Lahat ng di-nababasang kabanata (%s)</string>\n    <string name=\"download_option_manual_selection\">Manu-manong pumili ng mga kabanata</string>\n    <string name=\"invert_colors\">Baliktarin ang mga kulay</string>\n    <string name=\"pick_custom_directory\">Pumili ng custom na directory</string>\n    <string name=\"no_access_to_file\">Wala kang access sa file o directory na ito</string>\n    <string name=\"local_manga_directories\">Mga lokal na direktoryo ng manga</string>\n    <string name=\"password\">Password</string>\n    <string name=\"description\">Paglalarawan</string>\n    <string name=\"this_month\">Sa buwang ito</string>\n    <string name=\"color_light\">Maliwanag</string>\n    <string name=\"color_dark\">Madilim</string>\n    <string name=\"color_white\">Puti</string>\n    <string name=\"color_black\">Itim</string>\n    <string name=\"background\">Background</string>\n    <string name=\"data_not_restored\">Hindi na-restore ang data</string>\n    <string name=\"data_not_restored_text\">Tiyaking napili mo ang tamang backup file</string>\n    <string name=\"tracker_wifi_only_summary\">Huwag suriin ang mga bagong kabanata gamit ang mga naka-metrong network na koneksyon</string>\n    <string name=\"search_hint\">Ilagay ang pamagat ng manga, genre o pangalan ng source</string>\n    <string name=\"progress\">Pag-unlad</string>\n    <string name=\"order_added\">Idinagdag na</string>\n    <string name=\"languages\">Mga wika</string>\n    <string name=\"voice_search\">Paghahanap gamit ang boses</string>\n    <string name=\"related_manga\">Kaugnay na manga</string>\n    <string name=\"captcha_required_summary\">Ang %s ay nangangailangan ng isang captcha upang malutas upang gumana nang maayos</string>\n    <string name=\"manage_categories\">Ayusin ang mga kategorya</string>\n    <string name=\"suggestions_wifi_only_summary\">Huwag i-update ang mga mungkahi gamit ang mga naka-metrong network na koneksyon</string>\n    <string name=\"show\">Ipakita</string>\n    <string name=\"unknown\">Di-kilala</string>\n    <string name=\"in_progress\">Isinasagawa</string>\n    <string name=\"disable_nsfw\">Di paganahin ang NSFW</string>\n    <string name=\"error_corrupted_file\">Nabalik ang di-wastong data o na-corrupt ang file</string>\n    <string name=\"related_manga_summary\">Magpakita ng listahan ng mga kaugnay na manga. Sa ilang mga kaso, ito ay maaaring may mali o nawawala</string>\n    <string name=\"advanced\">Advanced</string>\n    <string name=\"too_many_requests_message\">Masyadong maraming request. Subukang ulit mamaya</string>\n    <string name=\"manga_list\">Listahan ng Manga</string>\n    <string name=\"zoom_in\">Mag-zoom paloob</string>\n    <string name=\"reader_zoom_buttons_summary\">Kung magpapakita ba ng mga button ng pang-zoom sa kanang sulok sa ibaba</string>\n    <string name=\"on_device\">Sa device</string>\n    <string name=\"moved_to_top\">Nailipat sa itaas</string>\n    <string name=\"items_limit_exceeded\">Wala nang mga aytem na pwedeng idagdag</string>\n    <string name=\"directories\">Mga direktoryo</string>\n    <string name=\"reader_zoom_buttons\">Ipakita ang mga button ng pag-zoom</string>\n    <string name=\"main_screen_sections\">Mga pangunahing seksyon ng screen</string>\n    <string name=\"zoom_out\">Mag-zoom palabas</string>\n    <string name=\"to_top\">Sa taas</string>\n    <string name=\"suggest_new_sources\">Magmungkahi ng mga bagong source pagkatapos ng app update</string>\n    <string name=\"enhanced_colors_summary\">Binabawasan ang banding, ngunit maaaring makaapekto ito sa performance</string>\n    <string name=\"state_abandoned\">Na-drop</string>\n    <string name=\"keep_screen_on\">Panatilihing naka-on ang screen</string>\n    <string name=\"enhanced_colors\">32-bit na color mode</string>\n    <string name=\"keep_screen_on_summary\">Huwag I-off ang screen habang nagbabasa ng manga</string>\n    <string name=\"suggest_new_sources_summary\">I-prompt na paganahin ang mga bagong idinagdag na source pagkatapos i-update ang aplikasyon</string>\n    <string name=\"categories\">Mga Kategorya</string>\n    <string name=\"list_options\">Opsyon sa Listahan</string>\n    <string name=\"by_relevance\">Kaugnayan</string>\n    <string name=\"online_variant\">Online na variant</string>\n    <string name=\"frequency_every_day\">Araw araw</string>\n    <string name=\"backup_frequency\">Dalas ng paglikha ng backup</string>\n    <string name=\"periodic_backups_enable\">Paganahin ang periodic na pag-backup</string>\n    <string name=\"frequency_every_2_days\">Kada 2 araw</string>\n    <string name=\"frequency_once_per_week\">Isang beses kada linggo</string>\n    <string name=\"periodic_backups\">Mga periodic na pag-backup</string>\n    <string name=\"frequency_twice_per_month\">Dalawang beses bawat buwan</string>\n    <string name=\"frequency_once_per_month\">Isang beses bawat buwan</string>\n    <string name=\"backups_output_directory\">Output na directory ng mga backup</string>\n    <string name=\"last_successful_backup\">Huling matagumpay na pag-backup: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"sources_catalog\">Katalugo ng mga source</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Mga Comic</string>\n    <string name=\"catalog\">Katalogo</string>\n    <string name=\"manage_sources\">Pamahalaan ang mga source</string>\n    <string name=\"no_manga_sources_found\">Walang available na manga source na nahanap base sa iyong query</string>\n    <string name=\"lock_screen_rotation\">Rotation ng lock screen</string>\n    <string name=\"manual\">Manu-mano</string>\n    <string name=\"source_enabled\">Napaganang source</string>\n    <string name=\"disable_nsfw_summary\">Di paganahin ang mga source na NSFW at itago ang manga na pang-matanda mula sa listahan kung maaari</string>\n    <string name=\"no_manga_sources_catalog_text\">Walang mga sources na maaring gamitin sa seksyong ito, o maaring na-add na lahat.\n\\nManatiling nakatutok para sa iba pang mga source</string>\n    <string name=\"available_d\">Available: %1$d</string>\n    <string name=\"content_type_other\">Iba pa</string>\n    <string name=\"error_multiple_states_not_supported\">Ang pag-filter ayon sa maraming estado ay hindi sinusuportahan ng manga source na ito</string>\n    <string name=\"reader_optimize\">Bawasan ang pagkonsumo ng memory (beta)</string>\n    <string name=\"error_multiple_genres_not_supported\">Ang pag-filter ayon sa maramihang genre ay hindi sinusuportahan ng manga source na ito</string>\n    <string name=\"error_search_not_supported\">Ang paghahanap ay hindi suportado ng manga source na ito</string>\n    <string name=\"reader_optimize_summary\">Bawasan ang kalidad ng mga offscreen na pahina upang gumamit ng mas kaunting memory</string>\n    <string name=\"state\">Estado</string>\n    <string name=\"state_paused\">Na-pause</string>\n    <string name=\"error_filter_states_genre_not_supported\">Ang pag-filter ayon sa magkaparehong genre at estado ay hindi sinusuportahan ng source na ito</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Ang pag-filter ayon sa magkaparehong genre at wika ay hindi sinusuportahan ng source na ito</string>\n    <string name=\"apply\">Gamitin</string>\n    <string name=\"genres_search_hint\">Simulan ang pag-type ng pangalan ng genre</string>\n    <string name=\"globally\">Pangkahalatan</string>\n    <string name=\"downloads_settings_info\">Maaari mong paganahin ang pag-bagal ng pag-download para sa bawat source ng manga nang paisa-isa sa mga setting ng source kung nagkakaroon ka ng mga problema sa pagharang na server-side</string>\n    <string name=\"this_manga\">Itong manga na ito</string>\n    <string name=\"skip\">Laktawan</string>\n    <string name=\"color_correction_apply_text\">Ang mga setting na ito ay maaaring ilapat sa pangkahalatan o sa kasalukuyang manga lamang. Kung inilapat sa pangkahalatan, hindi ma-override ang mga indibidwal na setting.</string>\n    <string name=\"grayscale\">Walang kulay</string>\n    <string name=\"welcome_text\">Piliin kung anong mga content source ang gusto mo i-enable. Pwede rin ito ayusin mamaya sa mga setting</string>\n    <string name=\"restore\">I-balik</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Baka makatulong sa pag-simula ng download kung mayroong isyu</string>\n    <string name=\"backup_date_\">Petsa ng pag-backup: %s</string>\n    <string name=\"sync_auth\">Mag-login para i-sync ang account</string>\n    <string name=\"by_name_reverse\">Binaligtad ang pangalan</string>\n    <string name=\"state_upcoming\">Paparating</string>\n    <string name=\"rating_safe\">Ligtas</string>\n    <string name=\"rating_suggestive\">Imoral</string>\n    <string name=\"genres_exclude\">Ibukod na dyanra</string>\n    <string name=\"rating_adult\">Nasa gulang</string>\n    <string name=\"content_rating\">Content rating</string>\n    <string name=\"default_tab\">Default na tab</string>\n    <string name=\"mark_as_completed\">Markahan bilang kumpleto na</string>\n    <string name=\"mark_as_completed_prompt\">Markahan ang napiling manga bilang tapos na nabasa?\n\\n\n\\nBabala: mawawala ang kasalukuyang progress sa pagbabasa.</string>\n    <string name=\"category_hidden_done\">Nakatago ang kategoryang ito mula sa pangunahing screen at naa-access sa pamamagitan ng Menu → Ayusin ang mga kategorya</string>\n    <string name=\"remove_from_history\">Alisin sa kasaysayan</string>\n    <string name=\"incognito_mode_hint\">Hindi mase-save ang iyong progress sa pagbabasa</string>\n    <string name=\"last_read\">Huling nabasa</string>\n    <string name=\"default_webtoon_zoom_out\">Default zoom out sa webtoon</string>\n    <string name=\"show_labels_in_navbar\">Ipakita ang mga label sa navigation bar</string>\n    <string name=\"pages_saving\">Nagse-save ng mga pahina</string>\n    <string name=\"ask_for_dest_dir_every_time\">Laging magtanong sa direktoryo ng patutunguhan</string>\n    <string name=\"default_page_save_dir\">Default na directory ng pag-save ng pahina</string>\n    <string name=\"email_password_enter_hint\">Ilagay ang iyong email at password upang magpatuloy</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"volume_\">Volume %d</string>\n    <string name=\"volume_unknown\">Hindi kilalang volume</string>\n    <string name=\"vertical\">Patayo</string>\n    <string name=\"show_menu\">Ipakita ang menu</string>\n    <string name=\"tap_action\">Aksyon sa pag-tap</string>\n    <string name=\"long_tap_action\">Aksyon sa matagal na pag-tap</string>\n    <string name=\"none\">Wala</string>\n    <string name=\"config_reset_confirm\">I-reset ang mga setting sa mga default na value? Ang gawaing ito ay hindi pwedeng bawiin.</string>\n    <string name=\"use_two_pages_landscape\">Gumamit ng dalawang page na layout sa landscape na oryentasyon (beta)</string>\n    <string name=\"fullscreen_mode\">Fullscreen mode</string>\n    <string name=\"reader_fullscreen_summary\">Itago ang status ng system at navigation bar</string>\n    <string name=\"toggle_ui\">Ipakita/itago ang UI</string>\n    <string name=\"prev_chapter\">Nakaraang kabanata</string>\n    <string name=\"next_chapter\">Sunod na kabanata</string>\n    <string name=\"prev_page\">Nakaraang pahina</string>\n    <string name=\"next_page\">Susunod na pahina</string>\n    <string name=\"reader_actions\">Mga aksyon sa reader</string>\n    <string name=\"reader_actions_summary\">Ayusin ang mga pagkilos para sa mga nata-tap na lugar ng screen</string>\n    <string name=\"switch_pages_volume_buttons\">Paganahin ang mga volume button</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Gumamit ng mga volume button para sa paglipat ng mga pahina</string>\n    <string name=\"suggestions_unavailable_text\">Di pinagana ang feature na Mga suhestiyon</string>\n    <string name=\"check_for_new_chapters_disabled\">Di pinagana ang pagsusuri para sa mga bagong kabanata</string>\n    <string name=\"reading_time_estimation\">Ipakita ang tinantyang oras ng pagbabasa</string>\n    <string name=\"reading_time_estimation_summary\">Maaaring hindi tumpak ang halaga ng pagtatantya ng oras</string>\n    <string name=\"location\">Lokasyon</string>\n    <string name=\"automatic\">Awtomatiko</string>\n    <string name=\"preferred_download_format\">Ginustong format ng pag-download</string>\n    <string name=\"single_cbz_file\">Isang CBZ na file</string>\n    <string name=\"multiple_cbz_files\">Maramihang CBZ na file</string>\n    <string name=\"reading_stats\">Statistika sa pagbabasa</string>\n    <string name=\"less_than_minute\">Wala pang isang minuto</string>\n    <string name=\"statistics\">Mga statistika</string>\n    <string name=\"clear_stats\">Linisin ang statistika</string>\n    <string name=\"stats_cleared\">Nalinisan na ang statistika</string>\n    <string name=\"pages_read_s\">Mga nabasang pahina: %s</string>\n    <string name=\"other_manga\">Ibang manga</string>\n    <string name=\"day\">Araw</string>\n    <string name=\"clear_stats_confirm\">Gusto mo ba talagang linisin ang lahat ng istatistika ng pagbabasa? Ang gawaing ito ay hindi pwedeng bawiin.</string>\n    <string name=\"week\">Linggo</string>\n    <string name=\"all_time\">Lahat ng oras</string>\n    <string name=\"month\">Buwan</string>\n    <string name=\"three_months\">Tatlong buwan</string>\n    <string name=\"empty_stats_text\">Walang mga istatistika para sa napiling panahon</string>\n    <string name=\"migrate_confirmation\">Ang manga \\\"%1$s\\\" mula sa \\\"%2$s\\\" ay papalitan ng \\\"%3$s\\\" galing sa \\\"%4$s\\\" sa iyong kasaysayan at mga paborito (kung mayroon)</string>\n    <string name=\"delete_read_chapters\">Tanggalin ang mga binasang kabanata</string>\n    <string name=\"no_chapters_deleted\">Walang kabanata ang natanggal</string>\n    <string name=\"chapters_deleted_pattern\">Natanggal %1$s, nalinisan %2$s</string>\n    <string name=\"delete_read_chapters_auto\">Awtomatikong tanggalin ang mga nabasang kabanata</string>\n    <string name=\"long_ago_read\">Matagal nang nabasa</string>\n    <string name=\"unread\">Di pa nababasa</string>\n    <string name=\"chapters_grid_view\">View na grid</string>\n    <string name=\"alternatives\">Alternatibo</string>\n    <string name=\"migrate\">Lumipat</string>\n    <string name=\"manga_migration\">Paglilipat ng manga</string>\n    <string name=\"migration_completed\">Natapos na ang paglilipat</string>\n    <string name=\"delete_read_chapters_summary\">Tanggalin ang mga kabanata na nabasa mo na mula sa lokal na storage upang magbakante ng espasyo</string>\n    <string name=\"delete_read_chapters_prompt\">Permanente nitong tatanggalin ang lahat ng kabanata na minarkahan bilang nabasa mula sa iyong lokal na storage. Maaari mong muling i-download ito sa ibang pagkakataon, ngunit ang mga na-import na kabanata ay maaaring mawala nang tuluyan</string>\n    <string name=\"runs_on_app_start\">Tatakbo kapag nagsimula ang aplikasyon</string>\n    <string name=\"split_by_translations\">Hatiin ayon sa mga pagsasalin</string>\n    <string name=\"split_by_translations_summary\">Magpakita ng mga kabanata na may iba\\'t ibang pagsasalin nang hiwalay, sa halip na sa isang listahan</string>\n    <string name=\"order_oldest\">Pinakaluma</string>\n    <string name=\"enable_source\">Paganahin ang source</string>\n    <string name=\"unsupported_source\">Ang manga source na ito ay hindi suportado</string>\n    <string name=\"show_pages_thumbs\">Ipakita ang mga thumbnail ng pahina</string>\n    <string name=\"show_pages_thumbs_summary\">Paganahin ang tab na \\\"Mga Pahina\\\" sa screen ng mga detalye</string>\n    <string name=\"error_no_data_received\">Walang natanggap na data mula sa server</string>\n    <string name=\"unsupported_backup_message\">Pumili ng wastong backup file ng Kotatsu</string>\n    <string name=\"hours_short\">%d o</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"hours_minutes_short\">%1$d o %2$d m</string>\n    <string name=\"fix\">Ayusin</string>\n    <string name=\"missing_storage_permission\">Walang pahintulot na ma-access ang manga sa external storage</string>\n    <string name=\"last_used\">Huling nagamit</string>\n    <string name=\"show_updated\">Ipakita ang mas bago</string>\n    <string name=\"webtoon_gaps\">Puwang sa webtoon mode</string>\n    <string name=\"webtoon_gaps_summary\">Ipakita ang bertikal na puwang sa pagitan ng pahina sa webtoon mode</string>\n    <string name=\"recent_queries\">Mga kamakailang query</string>\n    <string name=\"authors\">May-akda</string>\n    <string name=\"search_suggestions\">Mga mungkahi sa paghahanap</string>\n    <string name=\"suggested_queries\">Mga iminungkahing query</string>\n    <string name=\"less_frequently\">Minsanan</string>\n    <string name=\"more_frequently\">Madalas</string>\n    <string name=\"frequency_of_check\">Dalas ng pag-tingin</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">I-pin ang UI ng nabigasyon</string>\n    <string name=\"pin_navigation_ui_summary\">Huwag itago ang navigation bar at ang view ng paghahanap sa pag-scroll</string>\n    <string name=\"blocked_by_server_message\">Hinarangan ka ng server. Subukang gumamit ng ibang koneksyon sa network (VPN, Proxy, atbp.)</string>\n    <string name=\"disable\">Di paganahin</string>\n    <string name=\"sources_disabled\">Di pinagana ang mga source</string>\n    <string name=\"disable_connectivity_check\">Di paganahin ang pagtingin sa koneksyon</string>\n    <string name=\"disable_connectivity_check_summary\">Laktawan ang pagsuri sa koneksyon kung sakaling mayroon kang isyu rito (hal. pagpunta sa offline mode kahit na nakakonekta sa network)</string>\n    <string name=\"ignore_ssl_errors_summary\">Maaaring di paganahin ang pag-verify ng mga SSL certificate kung sakaling makaharap ka ng mga isyu na nauugnay sa SSL kapag nag-a-access ng mga network resource. Ito ay makaapekto sa iyong seguridad. Kinakailangang mag-restart ang aplikasyon pagkatapos baguhin ang setting na ito.</string>\n    <string name=\"disable_nsfw_notifications_summary\">Huwag magpakita ng mga abiso tungkol sa mga update ng NSFW manga</string>\n    <string name=\"tracker_debug_info\">Sinusuri ang mga log ng mga bagong kabanata</string>\n    <string name=\"tracker_debug_info_summary\">Debug na impormasyon tungkol sa mga pagsusuri sa background para sa mga bagong kabanata</string>\n    <string name=\"disable_nsfw_notifications\">Di paganahin ang mga abisong NSFW</string>\n    <string name=\"_new\">Mga bago</string>\n    <string name=\"screenshots_block_incognito\">Harangan kapag nasa incognito na mode</string>\n    <string name=\"all_languages\">Lahat ng wika</string>\n    <string name=\"image_server\">Piniling image server</string>\n    <string name=\"crop_pages\">Mag-crop ng pahina</string>\n    <string name=\"source_unpinned\">Na-unpin ang source</string>\n    <string name=\"sources_unpinned\">Mga source na na-unpin</string>\n    <string name=\"unpin\">I-unpin</string>\n    <string name=\"sources_pinned\">Mga source na na-pin</string>\n    <string name=\"pin\">I-pin</string>\n    <string name=\"source_pinned\">Naka-pin ang source</string>\n    <string name=\"recent_sources\">Kamakailang mga source</string>\n    <string name=\"percent_left\">Porsyentong natitira</string>\n    <string name=\"chapters_left\">Kabanatang natitira</string>\n    <string name=\"percent_read\">Porsyentong nabasa</string>\n    <string name=\"chapters_read\">Kabanatang nabasa</string>\n    <string name=\"plugin_incompatible\">Hindi tugmang plugin o internal na error. Tiyaking ginagamit mo ang pinakabagong bersyon ng plugin at Kotatsu</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Walang manga na tumutugma sa mga filter na iyong pinili</string>\n    <string name=\"connection_ok\">Maayos ang koneksyon</string>\n    <string name=\"invalid_proxy_configuration\">Imbalidong configuaration sa proxy</string>\n    <string name=\"show_quick_filters\">Ipakita ang mabilisang filter</string>\n    <string name=\"show_quick_filters_summary\">Nagbibigay ng kakayahang i-filter ang mga listahan ng manga batay sa ilang mga parameter</string>\n    <string name=\"invalid_server_address_message\">Di-balidong address ng server</string>\n    <string name=\"stuck\">Naipit</string>\n    <string name=\"retry\">Subukan muli</string>\n    <string name=\"too_many_requests_message_retry\">Masyado maraming request. Subukan muli pagkatapos ng %s</string>\n    <string name=\"skip_all\">Laktawan lahat</string>\n    <string name=\"external_source\">Ang external o plugin</string>\n    <string name=\"seconds_short\">s %d</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"not_in_favorites\">Wala sa mga paborito</string>\n    <string name=\"unpopular\">Hindi sikat</string>\n    <string name=\"low_rating\">Mababa ang rating</string>\n    <string name=\"sort_order_asc\">Pataas</string>\n    <string name=\"sort_order_desc\">Pababa</string>\n    <string name=\"popularity\">Kasikatan</string>\n    <string name=\"updated_long_ago\">Matagal nang na-update</string>\n    <string name=\"by_date\">Petsa</string>\n    <string name=\"scrobbler_auth_required\">Mag sign in sa %s upang magpatuloy</string>\n    <string name=\"scrobbler_auth_intro\">Mag sign in para mag set up ng integration ng %s. Ito ay magbibigay-daan sa iyo na ma-track ang iyong progress at status sa pagbabasa ng manga</string>\n    <string name=\"unstable_feature\">Unstable na feature</string>\n    <string name=\"unstable_feature_summary\">Ang function na ito ay pang-eksperimento. Pakitiyak na mayroon kang backup upang maiwasan ang pagkawala ng data</string>\n    <string name=\"downloads_background\">Mga download sa background</string>\n    <string name=\"download_new_chapters\">Mag-download ng mga bagong kabanata</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga na may na-download na mga kabanata</string>\n    <string name=\"fixing_manga\">Inaayos ang manga</string>\n    <string name=\"fixed\">Matagumpay na naayos</string>\n    <string name=\"manga_fix_prompt\">Ang function na ito ay makakahanap ng mga alternatibong source para sa napiling manga. Ang gawain ay magtatagal at magpapatuloy sa background</string>\n    <string name=\"manga_replaced\">Ang Manga na \\\"%1$s\\\" (%2$s) ay pinalitan ng \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"no_fix_required\">Di kinakailangan ng paayos sa \\\"%s\\\"</string>\n    <string name=\"no_alternatives_found\">Walang alternatibong nahanap para sa \\\"%s\\\"</string>\n    <string name=\"content_type_novel\">Nobela</string>\n    <string name=\"recently_added\">Kamakailang idinagdag</string>\n    <string name=\"added_long_ago\">Matagal nang naidagdag</string>\n    <string name=\"popular_in_hour\">Sikat sa oras na ito</string>\n    <string name=\"popular_today\">Sikat ngayong araw</string>\n    <string name=\"popular_in_week\">Sikat ngayong linggo</string>\n    <string name=\"popular_in_month\">Sikat ngayong buwan</string>\n    <string name=\"popular_in_year\">Sikat ngayong taon</string>\n    <string name=\"original_language\">Orihinal na wika</string>\n    <string name=\"year\">Taon</string>\n    <string name=\"years\">Mga Taon</string>\n    <string name=\"any\">Kahit ano</string>\n    <string name=\"filter_search_warning\">Ang source na ito ay hindi sinusuportahan ang paghahanap na may mga filter. Ang iyong mga filter ay malilinisan</string>\n    <string name=\"demographics\">Demograpiko</string>\n    <string name=\"content_type_image_set\">Set ng mga imahe</string>\n    <string name=\"debug\">Mag-debug</string>\n    <string name=\"user_manual\">Manwal ng gumagamit</string>\n    <string name=\"telegram_group\">Grupo sa Telegram</string>\n    <string name=\"error_image_format\">Di-suportadong format ng imahe: %s</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"download_over_cellular\">Mag-download sa pamamagitan ng cellular network</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_one_shot\">One shot</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"content_type_artist_cg\">Artist CG</string>\n    <string name=\"content_type_game_cg\">Game CG</string>\n    <string name=\"source_code\">Ang source code</string>\n    <string name=\"start_download\">Simulan ang pag-download</string>\n    <string name=\"save_manga_confirm\">I-save ang napiling manga? Maaari itong kumonsumo ng trapiko at espasyo sa disk</string>\n    <string name=\"save_manga\">I-save ang manga</string>\n    <string name=\"genre\">Mga Genre</string>\n    <string name=\"download_added\">Idinagdag sa pag-download</string>\n    <string name=\"more_options\">Iba pang mga opsyon</string>\n    <string name=\"destination_directory\">Directory ng destinasyon</string>\n    <string name=\"chapter_selection_hint\">Maari kang makapili ng mga kabanata para mai-download sa pamamagitan ng pag-long click sa aytem sa listahan ng kabanata.</string>\n    <string name=\"chapters_all\">Lahat</string>\n    <string name=\"download_cellular_confirm\">Payagan ang pag-download sa pamamagitan ng cellular network?</string>\n    <string name=\"dont_allow\">Di payagan</string>\n    <string name=\"allow_always\">Laging payagan</string>\n    <string name=\"allow_once\">Payagan kaisa</string>\n    <string name=\"ask_every_time\">Palaging payagan</string>\n    <string name=\"screen_orientation\">Oryentasyon ng screen</string>\n    <string name=\"portrait\">Patayo (portrait)</string>\n    <string name=\"landscape\">Pahalang (landscape)</string>\n    <string name=\"plugin_incompatible_with_cause\">Plugin error: %s\\n Tiyaking ginagamit mo ang pinakabagong bersyon ng plugin at Kotatsu</string>\n    <string name=\"error_not_image\">Imbalidong pormat: inaasahang larawan ngunit nakakuha ng %s</string>\n    <string name=\"access_denied_403\">Tinanggihan ang pag-access (403)</string>\n    <string name=\"max_backups_count\">Pinakamataas na bilang ng mga backup</string>\n    <string name=\"pages_saved\">Na-save na mga pahina</string>\n    <string name=\"delete_old_backups\">Tanggalin ang mga lumang backup</string>\n    <string name=\"delete_old_backups_summary\">Awtomatikong tanggalin ang mga lumang backup file upang makatipid ng storage space</string>\n    <string name=\"handle_links\">Pangasiwaan ang mga link</string>\n    <string name=\"handle_links_summary\">Pangasiwaan ang manga link mula sa mga panlabas na application (hal. web browser). Maaaring kailanganin mo rin itong manual na paganahin sa mga setting ng system ng aplikasyon</string>\n    <string name=\"email\">Ang email</string>\n    <string name=\"captcha_required_message\">Ang source na ito ay kinakailang lutasin ang captcha para magpatuloy</string>\n    <string name=\"show_slider\">Ipakita ang slider</string>\n    <string name=\"author\">May-Akda</string>\n    <string name=\"error_connection_reset\">I-reset ang koneksyon sa remote host</string>\n    <string name=\"incognito\">Incognito</string>\n    <string name=\"backup_tg_check\">I-check kung ang API ay gumagana</string>\n    <string name=\"backup_tg_echo\">Mensaheng Pinasusubok</string>\n    <string name=\"backup_tg_id_not_set\">Ang chat ID ay hindi nakatakda</string>\n    <string name=\"telegram_chat_id\">Ang telegram chat ID</string>\n    <string name=\"open_telegram_bot\">Buksan ang bot sa telegram</string>\n    <string name=\"clear_database\">Linisin ang database</string>\n    <string name=\"clear_database_summary\">Tanggalin ang impormasyon tungkol sa manga na hindi ginamit</string>\n    <string name=\"send_backups_telegram\">Magpadala ng mga backup sa Telegram</string>\n    <string name=\"test_connection\">I-test ang koneksyon</string>\n    <string name=\"open_telegram_bot_summary\">Pindutin para buksan ang pinag-usapan gamit ang Kotatsu Backup Bot</string>\n    <string name=\"translation\">Pagsasalin</string>\n    <string name=\"telegram_chat_id_summary\">Ilagay ang chat ID kung saan dapat ipadala ang mga backup</string>\n    <string name=\"rating\">Kaurian</string>\n    <string name=\"source\">Pinagmulan</string>\n    <string name=\"enable_all_sources_summary\">Lahat ng available na manga source ay permanenteng papaganahin</string>\n    <string name=\"all_sources_enabled\">Lahat ng source ay napagana na</string>\n    <string name=\"enable_all_sources\">Paganahin lahat ang mga manga source</string>\n    <string name=\"screen_rotation_unlocked\">Ang pag-ikot ng screen ay na-unlock</string>\n    <string name=\"backup_restored_background\">Ini-restore ang backup sa background</string>\n    <string name=\"restoring_backup\">Niri-restore ang backup</string>\n    <string name=\"reader_controls_in_bottom_bar\">Mga kontrol ng reader sa bottom bar</string>\n    <string name=\"chapters_and_pages\">Mga Kabanata at pahina</string>\n    <string name=\"screen_rotation_locked\">Ang pag-ikot ng screen ay naka-lock</string>\n    <string name=\"pages_slider\">Slider sa pagpalit ng pahina</string>\n    <string name=\"global_search\">Global na paghahanap</string>\n    <string name=\"search_everywhere\">Maghanap kahit saan</string>\n    <string name=\"badges_in_lists\">Mga badge sa listahan</string>\n    <string name=\"disable_captcha_notifications\">I-disable ang mga abiso ng captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Hindi ka makakatanggap ng mga abiso tungkol sa paglutas ng CAPTCHA para sa source na ito ngunit maaari itong humantong sa pagsira sa mga operasyon sa background (pagsusuri ng mga bagong kabanata, pagkuha ng mga rekomendasyon, atbp)</string>\n    <string name=\"error_details\">Detaye ng error</string>\n    <string name=\"error_disclaimer_manga\">Subukang buksan ang manga sa isang web browser upang matiyak na available ito sa source.</string>\n    <string name=\"search_disabled_sources\">Maghanap sa pamamagitan ng mga hindi pinaganang source</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Kabanata %2$s</string>\n    <string name=\"chapter_number\">Kabanata %s</string>\n    <string name=\"unnamed_chapter\">Walang pangalan na kabanata</string>\n    <string name=\"error_disclaimer_app_outdated\">Mukhang luma na ang bersyon mo ng Kotatsu. Mangyaring i-install ang pinakabagong bersyon upang makuha ang lahat ng magagamit na mga pag-aayos.</string>\n    <string name=\"error_disclaimer_report\">Maaari kang magsumite ng ulat ng bug sa mga developer. Makakatulong ito sa amin na magsiyasat at ayusin ang isyu.</string>\n    <string name=\"tags_warnings\">I-highlight ang mga mapanganib na genre</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"clear_browser_data\">Linisin ang data ng browser</string>\n    <string name=\"no_write_permission_to_file\">Walang pahintulot na magsulat ng file</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Ang manga pang-matanda ay hindi ipapakita sa mga mungkahi. Maaaring gumana nang hindi tumpak ang opsyong ito sa ilang source</string>\n    <string name=\"include_disabled_sources\">Isama ang mga hindi pinagana na source</string>\n    <string name=\"suggestions_disabled_sources_summary\">Magpakita ng mga mungkahi mula sa lahat ng manga source, kabilang ang mga hindi napagana</string>\n    <string name=\"tags_warnings_summary\">I-highlight ang mga genre na maaaring hindi naaangkop para sa karamihan ng mga user</string>\n    <string name=\"link_to_manga_in_app\">Link sa manga sa Kotatsu</string>\n    <string name=\"simple\">Pinasimple</string>\n    <string name=\"link_to_manga_on_s\">Link sa manga sa %s</string>\n    <string name=\"clear_browser_data_summary\">Linisin ang data ng browser tulad ng cache at mga cookie. Babala: Ang awtorisasyon sa mga source ng manga ay maaaring maging di-balido</string>\n    <string name=\"error_non_file_uri\">Hindi magagamit ang napiling path dahil hindi ito nagsasaad ng file o directory</string>\n    <string name=\"reader_info_bar_transparent\">Nakikitang informatian bar</string>\n    <string name=\"use_default_cover\">Gamitin ang default na cover</string>\n    <string name=\"pick_manga_page\">Pumili ng pahina sa manga</string>\n    <string name=\"pick_custom_file\">Pumili ng custom na file</string>\n    <string name=\"change_cover\">Palitan ang cover</string>\n    <string name=\"manga_override_hint\">Ang mga pagbabagong ito ay makakaapekto sa kung paano ipinapakita ang manga sa app</string>\n    <string name=\"dont_ask_again\">Huwag nang itanong muli</string>\n    <string name=\"incognito_mode_hint_nsfw\">Ang manga na ito ay naglalaman ng nakakatandang content. Gusto mo bang gumamit ng incognito na mode?</string>\n    <string name=\"incognito_for_nsfw\">Incognito na mode para sa NSFW na manga</string>\n    <string name=\"page_switch_timer\">Ang pahina ay lilipat bawat ~%d segundo</string>\n    <string name=\"additional_action_required\">Kinakailangan ang karagdagang aksyon</string>\n    <string name=\"hide_from_main_screen\">Itago mula sa main screen</string>\n    <string name=\"collapse\">Paliitin</string>\n    <string name=\"expand\">Palakihin</string>\n    <string name=\"adblock\">I-block ang mga ad sa browser</string>\n    <string name=\"adblock_summary\">I-block ang mga patalastas (ad) sa built-in na browser (beta)</string>\n    <string name=\"changelog_summary\">Kasaysayan ng mga pagbabago para sa mga kamakailang inilabas na bersyon</string>\n    <string name=\"collapse_long_description\">Itago ang mahabang paglalarawan</string>\n    <string name=\"creating_backup\">Ginagawa ang backup</string>\n    <string name=\"share_backup\">I-share ang backup</string>\n    <string name=\"reader_navigation_inverted\">Baliktarin ang mga na kontrol sa nabigasyon</string>\n    <string name=\"reader_navigation_inverted_summary\">Baliktarin ang direksyon ng volume button at ang nabigasyon ng directional hardware key (kaliwa/tass/baba/kanan)</string>\n    <string name=\"reader_multitask\">Buksan ang reader sa isang hiwalay na gawain</string>\n    <string name=\"reader_multitask_summary\">Binibigyang-daan kang panatilihing bukas ang maraming reader na may magkakaibang manga nang sabay-sabay</string>\n    <string name=\"book_effect\">Mala-dilaw na background (asul na filter)</string>\n    <string name=\"unavailable\">Di-available</string>\n    <string name=\"show_floating_control_button\">Ipakita ang lumulutang na control button</string>\n    <string name=\"invalid_token\">Imbalidong token: %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Huwag gamitin ang RPC para sa pangmatandang content</string>\n    <string name=\"read_on_s\">Basahin sa %s</string>\n    <string name=\"reading_s\">Binabasa ng %s</string>\n    <string name=\"discord_rpc_description\">Bumamasa ng manga sa Kotatsu - isang manga reader app</string>\n    <string name=\"obtain\">Kumuha</string>\n    <string name=\"discord_rpc_summary\">Ipakita ng status ng pagbabasa sa Discord</string>\n    <string name=\"discord_token_hint\">I-paste ang Discord Token dito</string>\n    <string name=\"discord_token_description\">Ilagay ang iyong Discord Token o i-click ang %s para makuha ito gamit ang browser</string>\n    <string name=\"discord_token_summary\">Ilagay ang iyong Discord Token para mapagana ang Rich Presence</string>\n    <string name=\"error_corrupted_zip\">Na-corrupt na ZIP archive (%s)</string>\n    <string name=\"main_screen_fab_summary\">Pinapayagan na magpatuloy sa pagbabasa sa isang click. Ang button na ito ay hindi lilitaw sa incognito na mode o kung walang laman ang kasaysayan</string>\n    <string name=\"main_screen_fab\">Ipakita ang lumulutang na Magpatuloy na button</string>\n    <string name=\"main_screen\">Main na screen</string>\n    <string name=\"packup_creation_failed\">Bigong malikha ang backup</string>\n    <string name=\"local_storage_cleanup\">Paglilinis ng lokal na storage</string>\n    <string name=\"changelog\">Mga pagbabago</string>\n    <string name=\"manga_restricted_description\">Ang manga na ito ay hindi magagamit na basahin sa source na ito. Subukang hanapin ito sa ibang mga source o buksan ito sa isang browser para sa higit pang impormasyon</string>\n    <string name=\"no_chapters_in_manga\">Ang manga na ito ay walang anumang mga kabanata</string>\n    <string name=\"chapters_load_failed\">Nabigong i-load ang listahan ng kabanata</string>\n    <string name=\"pull_to_prev_chapter\">Bitawan upang mabuksan ang nakaraang kabanata</string>\n    <string name=\"pull_to_next_chapter\">Bitawan upang mabuksan ang susunod na kabanata</string>\n    <string name=\"pull_top_no_prev\">Walang nakaraang kabanata</string>\n    <string name=\"pull_bottom_no_next\">Walang susunod na kabanata</string>\n    <string name=\"enable_pull_gesture_title\">Paganahin ang paghila na gesture</string>\n    <string name=\"enable_pull_gesture_summary\">Gamitin ang paghila na gesture para makapagpalit ng kabanata sa webtoon</string>\n    <string name=\"saved_filters\">Naka-save na mga filter</string>\n    <string name=\"enter_name\">Ipasok ang pangalan</string>\n    <string name=\"reader_chapter_toast\">Ipakita ang popup sa pagpalit ng kabanata</string>\n    <string name=\"reader_chapter_toast_summary\">Magpakita ng pop-up na mensahe na may pamagat ng kabanata kapag binago ito</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fr/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">Il y a %1$d minute</item>\n        <item quantity=\"many\">Il y a %1$d minutes</item>\n        <item quantity=\"other\">Il y a %1$d minutes</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d élément</item>\n        <item quantity=\"many\">%1$d éléments</item>\n        <item quantity=\"other\">%1$d éléments</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nouveau chapitre</item>\n        <item quantity=\"many\">%1$d nouveaux chapitres</item>\n        <item quantity=\"other\">%1$d nouveaux chapitres</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d chapitre</item>\n        <item quantity=\"many\">%1$d chapitres</item>\n        <item quantity=\"other\">%1$d chapitres</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">Il y a %1$d heure</item>\n        <item quantity=\"many\">Il y a %1$d heures</item>\n        <item quantity=\"other\">Il y a %1$d heures</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">Il y a %1$d jour</item>\n        <item quantity=\"many\">Il y a %1$d jours</item>\n        <item quantity=\"other\">Il y a %1$d jours</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">Il y a %1$d mois</item>\n        <item quantity=\"many\">Il y a %1$d mois</item>\n        <item quantity=\"other\">Il y a %1$d mois</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d heure</item>\n        <item quantity=\"many\">%1$d heures</item>\n        <item quantity=\"other\">%1$d heures</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minute</item>\n        <item quantity=\"many\">%1$d minutes</item>\n        <item quantity=\"other\">%1$d minutes</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"read_more\">Lire la suite</string>\n    <string name=\"tracker_warning\">Certains appareils ont un comportement différent du système, ce qui peut interrompre les tâches d\\'arrière-plan.</string>\n    <string name=\"backup_saved\">Sauvegarde enregistrée</string>\n    <string name=\"welcome\">Bienvenue</string>\n    <string name=\"text_clear_search_history_prompt\">Supprimer définitivement toutes les requêtes de recherche récentes \\?</string>\n    <string name=\"password_length_hint\">Le mot de passe doit comporter 4 caractères ou plus</string>\n    <string name=\"confirm\">Confirmer</string>\n    <string name=\"protect_application_subtitle\">Entrez un mot de passe pour démarrer l\\'application avec</string>\n    <string name=\"next\">Suivant</string>\n    <string name=\"default_s\">Par défaut : %s</string>\n    <string name=\"auth_required\">Connectez-vous pour voir ce contenu</string>\n    <string name=\"sign_in\">Se connecter</string>\n    <string name=\"reverse\">Inverser</string>\n    <string name=\"check_for_new_chapters\">Vérifier la présence de nouveaux chapitres</string>\n    <string name=\"text_clear_updates_feed_prompt\">Effacer définitivement l\\'historique des mises à jour ?</string>\n    <string name=\"clear_feed\">Effacer le flux</string>\n    <string name=\"cookies_cleared\">Tous les cookies ont été supprimés</string>\n    <string name=\"clear_cookies\">Effacer les cookies</string>\n    <string name=\"captcha_solve\">Résoudre</string>\n    <string name=\"captcha_required\">CAPTCHA requis</string>\n    <string name=\"silent\">Silencieux</string>\n    <string name=\"reader_mode_hint\">La configuration choisie sera mémorisée pour ce manga</string>\n    <string name=\"tap_to_try_again\">Appuyez pour réessayer</string>\n    <string name=\"today\">Aujourd\\'hui</string>\n    <string name=\"group\">Groupe</string>\n    <string name=\"long_ago\">Il y a longtemps</string>\n    <string name=\"yesterday\">Hier</string>\n    <string name=\"just_now\">À l\\'instant</string>\n    <string name=\"backup_information\">Vous pouvez créer une sauvegarde de votre historique et de vos favoris et la restaurer</string>\n    <string name=\"data_restored_with_errors\">Les données ont été restaurées, mais il y a des erreurs</string>\n    <string name=\"data_restored_success\">Toutes les données ont été restaurées</string>\n    <string name=\"file_not_found\">Fichier introuvable</string>\n    <string name=\"preparing_\">Préparation…</string>\n    <string name=\"data_restored\">Restauré</string>\n    <string name=\"restore_backup\">Restaurer à partir d\\'une sauvegarde</string>\n    <string name=\"create_backup\">Créer une sauvegarde des données</string>\n    <string name=\"backup_restore\">Sauvegarde et restauration</string>\n    <string name=\"black_dark_theme_summary\">Utilise moins d\\'énergie pour les écrans AMOLED</string>\n    <string name=\"black_dark_theme\">Noir</string>\n    <string name=\"zoom_mode_keep_start\">Garder au début</string>\n    <string name=\"zoom_mode_fit_width\">Ajuster à la largeur</string>\n    <string name=\"zoom_mode_fit_height\">Ajuster à la hauteur</string>\n    <string name=\"zoom_mode_fit_center\">Ajuster au centre</string>\n    <string name=\"scale_mode\">Mode mise à l\\'échelle</string>\n    <string name=\"create_category\">Nouvelle catégorie</string>\n    <string name=\"right_to_left\">De droite à gauche</string>\n    <string name=\"no_update_available\">Aucune mise à jour disponible</string>\n    <string name=\"check_for_updates\">Vérifier les mises à jour</string>\n    <string name=\"app_version\">Version %s</string>\n    <string name=\"about\">À propos</string>\n    <string name=\"passwords_mismatch\">Les mots de passe ne correspondent pas</string>\n    <string name=\"repeat_password\">Répéter le mot de passe</string>\n    <string name=\"protect_application_summary\">Demander le mot de passe au démarrage de Kotatsu</string>\n    <string name=\"protect_application\">Protéger l\\'application</string>\n    <string name=\"wrong_password\">Mot de passe erroné</string>\n    <string name=\"enter_password\">Entrez le mot de passe</string>\n    <string name=\"dont_check\">Ne pas vérifier</string>\n    <string name=\"track_sources\">Rechercher les mises à jour</string>\n    <string name=\"feed_will_update_soon\">La mise à jour des flux commencera bientôt</string>\n    <string name=\"update\">Mettre à Jour</string>\n    <string name=\"rotate_screen\">Faire pivoter l\\'écran</string>\n    <string name=\"updates_feed_cleared\">Effacé</string>\n    <string name=\"clear_updates_feed\">Effacer le flux des mises à jour</string>\n    <string name=\"size_s\">Taille : %s</string>\n    <string name=\"new_version_s\">Nouvelle version : %s</string>\n    <string name=\"search_results\">Résultats de la recherche</string>\n    <string name=\"text_feed_holder\">Les nouveaux chapitres de ce que vous lisez sont affichés ici</string>\n    <string name=\"updates\">Mises à jour</string>\n    <string name=\"read_later\">Lire plus tard</string>\n    <string name=\"favourites_category_empty\">Catégorie vide</string>\n    <string name=\"all_favourites\">Tous les favoris</string>\n    <string name=\"done\">Terminé</string>\n    <string name=\"other_storage\">Autre stockage</string>\n    <string name=\"cannot_find_available_storage\">Pas de stockage disponible</string>\n    <string name=\"not_available\">Non disponible</string>\n    <string name=\"manga_save_location\">Dossier pour les téléchargements</string>\n    <string name=\"pages_animation\">Animation de page</string>\n    <string name=\"recent_manga\">Récents</string>\n    <string name=\"manga_shelf\">Étagère</string>\n    <string name=\"text_local_holder_secondary\">Enregistrez quelque chose à partir d\\'un catalogue en ligne ou importez-le à partir d\\'un fichier.</string>\n    <string name=\"text_local_holder_primary\">Sauvegardez d\\'abord quelque chose</string>\n    <string name=\"text_history_holder_secondary\">Trouver quoi lire dans la section « Explorer »</string>\n    <string name=\"text_history_holder_primary\">Ce que vous lisez sera affiché ici</string>\n    <string name=\"text_search_holder_secondary\">Essayez de reformuler la requête.</string>\n    <string name=\"text_empty_holder_primary\">C\\'est un peu vide ici…</string>\n    <string name=\"remove_category\">Retirer</string>\n    <string name=\"favourites_categories\">Catégories favorites</string>\n    <string name=\"vibration\">Vibration</string>\n    <string name=\"light_indicator\">Indicateur lumineux</string>\n    <string name=\"notification_sound\">Son de notification</string>\n    <string name=\"notifications_settings\">Paramètres des notifications</string>\n    <string name=\"download\">Télécharger</string>\n    <string name=\"new_chapters\">Nouveaux chapitres</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d de %2$d activé(s)</string>\n    <string name=\"notifications\">Notifications</string>\n    <string name=\"open_in_browser\">Ouvrir dans le navigateur</string>\n    <string name=\"app_update_available\">Une nouvelle version de l\\'application est disponible</string>\n    <string name=\"domain\">Domaine</string>\n    <string name=\"external_storage\">Stockage externe</string>\n    <string name=\"internal_storage\">Stockage interne</string>\n    <string name=\"search_history_cleared\">Effacé</string>\n    <string name=\"clear_search_history\">Effacer l\\'historique de recherche</string>\n    <string name=\"clear_thumbs_cache\">Vider le cache des miniatures</string>\n    <string name=\"error\">Erreur</string>\n    <string name=\"_continue\">Continuer</string>\n    <string name=\"switch_pages\">Changer de pages</string>\n    <string name=\"reader_settings\">Paramètres du lecteur</string>\n    <string name=\"text_delete_local_manga\">Supprimer \\\"%s\\\" de l\\'appareil de façon permanente ?</string>\n    <string name=\"delete_manga\">Supprimer le manga</string>\n    <string name=\"search_on_s\">Rechercher sur %s</string>\n    <string name=\"grid_size\">Taille de la grille</string>\n    <string name=\"read_mode\">Mode lecture</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"text_file_sizes\">o|ko|Mo|Go|To</string>\n    <string name=\"clear_pages_cache\">Vider le cache de la page</string>\n    <string name=\"no_description\">Aucune description</string>\n    <string name=\"text_file_not_supported\">Choisissez un fichier ZIP ou CBZ.</string>\n    <string name=\"operation_not_supported\">Cette opération n\\'est pas prise en charge</string>\n    <string name=\"delete\">Supprimer</string>\n    <string name=\"_import\">Importer</string>\n    <string name=\"share_image\">Partager l\\'image</string>\n    <string name=\"page_saved\">Page sauvegardée</string>\n    <string name=\"save_page\">Sauvegarder la page</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" supprimé du stockage local</string>\n    <string name=\"remove\">Retirer</string>\n    <string name=\"clear\">Effacer</string>\n    <string name=\"pages\">Pages</string>\n    <string name=\"follow_system\">Suivre le système</string>\n    <string name=\"dark\">Sombre</string>\n    <string name=\"light\">Clair</string>\n    <string name=\"theme\">Thème</string>\n    <string name=\"filter\">Filtre</string>\n    <string name=\"sort_order\">Ordre de tri</string>\n    <string name=\"by_rating\">Note</string>\n    <string name=\"newest\">Le plus récent</string>\n    <string name=\"updated\">Mis à jour</string>\n    <string name=\"popular\">Populaire</string>\n    <string name=\"by_name\">Nom</string>\n    <string name=\"downloads\">Téléchargements</string>\n    <string name=\"download_complete\">Téléchargé</string>\n    <string name=\"processing_\">Traitement…</string>\n    <string name=\"manga_downloading_\">Téléchargement…</string>\n    <string name=\"search_manga\">Rechercher un manga</string>\n    <string name=\"search\">Rechercher</string>\n    <string name=\"share_s\">Partager %s</string>\n    <string name=\"create_shortcut\">Créer un raccourci</string>\n    <string name=\"share\">Partager</string>\n    <string name=\"save\">Enregistrer</string>\n    <string name=\"add\">Ajouter</string>\n    <string name=\"add_new_category\">Nouvelle catégorie</string>\n    <string name=\"add_to_favourites\">Ajouter aux favoris</string>\n    <string name=\"you_have_not_favourites_yet\">Aucun favoris pour le moment</string>\n    <string name=\"read\">Lire</string>\n    <string name=\"history_is_empty\">Pas encore d\\'historique</string>\n    <string name=\"nothing_found\">Rien n\\'a été trouvé</string>\n    <string name=\"clear_history\">Effacer l\\'historique</string>\n    <string name=\"try_again\">Réessayer</string>\n    <string name=\"close\">Fermer</string>\n    <string name=\"chapter_d_of_d\">Chapitre %1$d sur %2$d</string>\n    <string name=\"loading_\">Chargement…</string>\n    <string name=\"remote_sources\">Sources des mangas</string>\n    <string name=\"settings\">Paramètres</string>\n    <string name=\"list_mode\">Mode liste</string>\n    <string name=\"grid\">Grille</string>\n    <string name=\"detailed_list\">Liste détaillée</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"chapters\">Chapitres</string>\n    <string name=\"details\">Détails</string>\n    <string name=\"network_error\">Erreur réseau</string>\n    <string name=\"error_occurred\">Une erreur s\\'est produite</string>\n    <string name=\"history\">Historique</string>\n    <string name=\"favourites\">Favoris</string>\n    <string name=\"local_storage\">Stockage local</string>\n    <string name=\"chapter_is_missing\">Le chapitre est manquant</string>\n    <string name=\"queued\">En file d\\'attente</string>\n    <string name=\"about_app_translation\">Traduction</string>\n    <string name=\"about_app_translation_summary\">Traduire cette application</string>\n    <string name=\"genres\">Genres</string>\n    <string name=\"text_clear_cookies_prompt\">Vous serez déconnecté de toutes les sources</string>\n    <string name=\"auth_not_supported_by\">La connexion sur %s n\\'est pas prise en charge</string>\n    <string name=\"auth_complete\">Autorisé</string>\n    <string name=\"state_finished\">Terminé</string>\n    <string name=\"state_ongoing\">En cours</string>\n    <string name=\"system_default\">Par défaut</string>\n    <string name=\"exclude_nsfw_from_history\">Exclure les mangas explicites de l\\'historique</string>\n    <string name=\"show_pages_numbers\">Pages numérotées</string>\n    <string name=\"computing_\">Calcul…</string>\n    <string name=\"screenshots_block_nsfw\">Bloquer le contenu explicite</string>\n    <string name=\"screenshots_block_all\">Toujours bloquer</string>\n    <string name=\"screenshots_policy\">Politique relative aux captures d\\'écran</string>\n    <string name=\"screenshots_allow\">Autoriser</string>\n    <string name=\"suggestions\">Suggestions</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Ne pas suggérer de mangas explicites</string>\n    <string name=\"suggestions_enable\">Activer les suggestions</string>\n    <string name=\"suggestions_summary\">Suggérer des mangas en fonction de vos préférences</string>\n    <string name=\"suggestions_info\">Toutes les données sont analysées localement sur cet appareil et ne sont jamais envoyées ailleurs.</string>\n    <string name=\"text_suggestion_holder\">Commencez à lire des mangas et vous recevrez des suggestions personnalisées</string>\n    <string name=\"enabled\">Activé</string>\n    <string name=\"disabled\">Désactivé</string>\n    <string name=\"only_using_wifi\">Uniquement en Wi-Fi</string>\n    <string name=\"always\">Toujours</string>\n    <string name=\"preload_pages\">Précharger les pages</string>\n    <string name=\"never\">Jamais</string>\n    <string name=\"reset_filter\">Réinitialiser le filtre</string>\n    <string name=\"onboard_text\">Sélectionnez les langues dans lesquelles vous souhaitez lire les mangas. Vous pouvez le changer plus tard dans les paramètres.</string>\n    <string name=\"logged_in_as\">Connecté en tant que %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Langues diverses</string>\n    <string name=\"search_chapters\">Trouver un chapitre</string>\n    <string name=\"chapters_empty\">Pas de chapitres dans ce manga</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Apparence</string>\n    <string name=\"suggestions_updating\">Mise à jour des suggestions</string>\n    <string name=\"suggestions_excluded_genres\">Exclure des genres</string>\n    <string name=\"suggestions_excluded_genres_summary\">Spécifiez les genres que vous ne voulez pas voir apparaître dans les suggestions</string>\n    <string name=\"text_delete_local_manga_batch\">Supprimer définitivement les éléments sélectionnés de l\\'appareil \\?</string>\n    <string name=\"removal_completed\">Suppression terminée</string>\n    <string name=\"download_slowdown\">Ralentissement du téléchargement</string>\n    <string name=\"download_slowdown_summary\">Permet d\\'éviter le blocage de votre adresse IP</string>\n    <string name=\"chapters_will_removed_background\">Les chapitres seront supprimés en arrière-plan</string>\n    <string name=\"local_manga_processing\">Traitement des mangas sauvegardés</string>\n    <string name=\"hide\">Masquer</string>\n    <string name=\"new_sources_text\">De nouvelles sources de mangas sont disponibles</string>\n    <string name=\"check_new_chapters_title\">Vérifier les nouveaux chapitres et les notifier</string>\n    <string name=\"notifications_enable\">Activer les notifications</string>\n    <string name=\"show_notification_new_chapters_on\">Vous recevrez des notifications sur les mises à jour des mangas que vous lisez</string>\n    <string name=\"show_notification_new_chapters_off\">Vous ne recevrez pas de notifications mais les nouveaux chapitres seront mis en évidence dans les listes</string>\n    <string name=\"empty_favourite_categories\">Pas de catégories préférées</string>\n    <string name=\"name\">Nom</string>\n    <string name=\"edit\">Modifier</string>\n    <string name=\"edit_category\">Modifier la catégorie</string>\n    <string name=\"bookmark_add\">Ajouter un marque-page</string>\n    <string name=\"bookmark_remove\">Retirer le marque-page</string>\n    <string name=\"bookmarks\">Marque-pages</string>\n    <string name=\"bookmark_added\">Marque-page ajouté</string>\n    <string name=\"bookmark_removed\">Marque-page retiré</string>\n    <string name=\"undo\">Annuler</string>\n    <string name=\"removed_from_history\">Retiré de l\\'historique</string>\n    <string name=\"dns_over_https\">DNS sur HTTPS</string>\n    <string name=\"default_mode\">Mode par défaut</string>\n    <string name=\"detect_reader_mode\">Mode de détection automatique du lecteur</string>\n    <string name=\"detect_reader_mode_summary\">Détecter automatiquement si un manga est un webtoon</string>\n    <string name=\"disable_battery_optimization\">Désactiver l\\'optimisation de la batterie</string>\n    <string name=\"disable_battery_optimization_summary\">Aide à la vérification des mises à jour des antécédents</string>\n    <string name=\"crash_text\">Un problème est survenu. Veuillez soumettre un rapport de bogue aux développeurs pour nous aider à le corriger.</string>\n    <string name=\"send\">Envoyer</string>\n    <string name=\"disable_all\">Tout désactiver</string>\n    <string name=\"use_fingerprint\">Utiliser la biométrie si elle est disponible</string>\n    <string name=\"appwidget_recent_description\">Vos mangas récemment lus</string>\n    <string name=\"appwidget_shelf_description\">Vos mangas favoris</string>\n    <string name=\"report\">Signaler</string>\n    <string name=\"tracking\">Suivi</string>\n    <string name=\"status_planned\">Planifié</string>\n    <string name=\"status_reading\">Lecture</string>\n    <string name=\"show_reading_indicators\">Afficher les indicateurs de progression de lecture</string>\n    <string name=\"show_reading_indicators_summary\">Afficher le pourcentage de lecture dans l\\'historique et les favoris</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Les mangas marqués comme étant explicites ne seront jamais ajoutés à l\\'historique et votre progression ne sera pas sauvegardée</string>\n    <string name=\"clear_cookies_summary\">Peut aider en cas de problème. Toutes les autorisations seront invalidées</string>\n    <string name=\"show_all\">Tout afficher</string>\n    <string name=\"status_on_hold\">En attente</string>\n    <string name=\"status_dropped\">Abandonné</string>\n    <string name=\"data_deletion\">Suppression des données</string>\n    <string name=\"logout\">Se déconnecter</string>\n    <string name=\"status_completed\">Terminé</string>\n    <string name=\"status_re_reading\">Relecture</string>\n    <string name=\"invalid_domain_message\">Domaine invalide</string>\n    <string name=\"select_range\">Sélectionner une plage</string>\n    <string name=\"not_found_404\">Contenu non trouvé ou supprimé</string>\n    <string name=\"manga_error_description_pattern\">Détails de l\\'erreur:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Essayez d\\'&lt;a href=%2$s&gt;ouvrir le manga dans un navigateur web&lt;/a&gt; pour vous assurer qu\\'il est disponible sur sa source&lt;br&gt;2. Assurez-vous que vous utilisez la &lt;a href=kotatsu://about&gt;dernière version de Kotatsu&lt;/a&gt;&lt;br&gt;3. Si elle est disponible, envoyez un rapport d\\'erreur aux développeurs.</string>\n    <string name=\"confirm_exit\">Appuyez à nouveau sur Retour pour quitter</string>\n    <string name=\"categories_delete_confirm\">Êtes-vous sûr(e) de vouloir supprimer les catégories de favoris sélectionnées ?\\nTous les mangas qui s\\'y trouvent seront perdus et ceci ne peut pas être annulé.</string>\n    <string name=\"exit_confirmation_summary\">Appuyez deux fois sur la touche Retour pour quitter l\\'appli</string>\n    <string name=\"available\">Disponible</string>\n    <string name=\"exit_confirmation\">Confirmation de sortie</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"feed\">Flux</string>\n    <string name=\"importing_manga\">Importation de mangas</string>\n    <string name=\"removed_from_favourites\">Supprimé des favoris</string>\n    <string name=\"clear_all_history\">Effacer tout l\\'historique</string>\n    <string name=\"last_2_hours\">Les 2 dernières heures</string>\n    <string name=\"history_cleared\">Historique effacé</string>\n    <string name=\"manage\">Gérer</string>\n    <string name=\"no_bookmarks_yet\">Aucun marque-page</string>\n    <string name=\"no_bookmarks_summary\">Vous pouvez créer un marque-page pendant la lecture d\\'un manga</string>\n    <string name=\"bookmarks_removed\">Marque-pages supprimés</string>\n    <string name=\"no_manga_sources\">Aucune source de mangas</string>\n    <string name=\"no_manga_sources_text\">Autoriser les sources de mangas de lire des mangas en ligne</string>\n    <string name=\"random\">Aléatoire</string>\n    <string name=\"reorder\">Réordonner</string>\n    <string name=\"empty\">Vide</string>\n    <string name=\"explore\">Explorer</string>\n    <string name=\"canceled\">Annulé</string>\n    <string name=\"account_already_exists\">Le compte existe déjà</string>\n    <string name=\"back\">Retour</string>\n    <string name=\"sync\">Synchronisation</string>\n    <string name=\"sync_title\">Synchronisez vos données</string>\n    <string name=\"email_enter_hint\">Entrez votre courriel pour continuer</string>\n    <string name=\"saved_manga\">Mangas sauvegardés</string>\n    <string name=\"pages_cache\">Cache des pages</string>\n    <string name=\"other_cache\">Autre cache</string>\n    <string name=\"storage_usage\">Utilisation du stockage</string>\n    <string name=\"options\">Options</string>\n    <string name=\"incognito_mode\">Mode incognito</string>\n    <string name=\"no_chapters\">Aucun chapitre</string>\n    <string name=\"automatic_scroll\">Défilement automatique</string>\n    <string name=\"reader_info_pattern\">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Afficher la barre d\\'infos dans le lecteur</string>\n    <string name=\"comics_archive\">Archives des BD</string>\n    <string name=\"folder_with_images\">Dossier avec des images</string>\n    <string name=\"import_completed\">Importation terminée</string>\n    <string name=\"import_completed_hint\">Vous pouvez supprimer le fichier original du stockage pour gagner de l\\'espace</string>\n    <string name=\"import_will_start_soon\">L\\'importation va bientôt commencer</string>\n    <string name=\"text_unsaved_changes_prompt\">Sauvegarde ou abandon des modifications non sauvegardées \\?</string>\n    <string name=\"discard\">Abandonner</string>\n    <string name=\"history_shortcuts_summary\">Rendre les mangas récents disponibles en appuyant longuement sur l\\'icône de l\\'application</string>\n    <string name=\"reader_control_ltr_summary\">Ne pas ajuster la direction de la page au mode lecteur, par ex. en appuyant sur la bonne touche, passe toujours à la page suivante. Cette option n\\'affecte que les périphériques d\\'entrée matérielle</string>\n    <string name=\"reader_control_ltr\">Contrôle ergonomique du lecteur</string>\n    <string name=\"history_shortcuts\">Afficher les raccourcis des mangas récents</string>\n    <string name=\"color_correction\">Correction des couleurs</string>\n    <string name=\"brightness\">Luminosité</string>\n    <string name=\"contrast\">Contraste</string>\n    <string name=\"reset\">Réinitialiser</string>\n    <string name=\"error_no_space_left\">Il n\\'y a plus d\\'espace sur l\\'appareil</string>\n    <string name=\"reader_slider\">Afficher le curseur de changement de page</string>\n    <string name=\"webtoon_zoom\">Zoom Webtoon</string>\n    <string name=\"network_unavailable_hint\">Activez le Wi-Fi ou le réseau mobile pour lire les mangas en ligne</string>\n    <string name=\"network_unavailable\">Le réseau n\\'est pas disponible</string>\n    <string name=\"compact\">Compact</string>\n    <string name=\"server_error\">Erreur côté serveur (%1$d). Veuillez réessayer plus tard</string>\n    <string name=\"clear_new_chapters_counters\">Effacer aussi les informations sur les nouveaux chapitres</string>\n    <string name=\"source_disabled\">Source désactivée</string>\n    <string name=\"prefetch_content\">Préchargement du contenu</string>\n    <string name=\"mark_as_current\">Marquer comme actuel</string>\n    <string name=\"share_logs\">Partager les journaux</string>\n    <string name=\"enable_logging\">Activer la journalisation</string>\n    <string name=\"enable_logging_summary\">Enregistrer quelques actions à des fins de débogage. Ne l\\'activez pas si vous ne savez pas ce que vous faites</string>\n    <string name=\"language\">Langue</string>\n    <string name=\"show_suspicious_content\">Afficher le contenu suspect</string>\n    <string name=\"theme_name_dynamic\">Dynamique</string>\n    <string name=\"color_theme\">Schéma de couleurs</string>\n    <string name=\"show_in_grid_view\">Afficher en vue grille</string>\n    <string name=\"scrobbling_empty_hint\">Pour suivre la progression de la lecture, sélectionnez Menu → Suivre sur l\\'écran des détails du manga.</string>\n    <string name=\"services\">Prestations de service</string>\n    <string name=\"nothing_here\">Il n\\'y a rien ici</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"allow_unstable_updates\">Autoriser les mises à jour instables</string>\n    <string name=\"download_started\">Téléchargement commencé</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"allow_unstable_updates_summary\">Recevoir des notifications sur les moutures instables</string>\n    <string name=\"user_agent\">En-tête UserAgent</string>\n    <string name=\"settings_apply_restart_required\">Veuillez redémarrer l\\'application pour appliquer ces changements</string>\n    <string name=\"got_it\">Compris</string>\n    <string name=\"speed\">Vitesse</string>\n    <string name=\"folder_with_images_import_description\">Vous pouvez sélectionner un répertoire contenant des archives ou des images. Chaque archive (ou sous-répertoire) sera reconnue comme un chapitre.</string>\n    <string name=\"comics_archive_import_description\">Vous pouvez sélectionner un ou plusieurs fichiers .cbz ou .zip, chaque fichier sera reconnu comme un manga séparé.</string>\n    <string name=\"show_on_shelf\">Afficher sur l\\'étagère</string>\n    <string name=\"sources_reorder_tip\">Appuyez sur un élément et maintenez-le enfoncé pour le réorganiser</string>\n    <string name=\"find_similar\">Trouver des similaires</string>\n    <string name=\"downloads_wifi_only_summary\">Arrêter le téléchargement lors du passage à un réseau mobile</string>\n    <string name=\"sync_auth_hint\">Vous pouvez vous connecter à un compte existant ou en créer un nouveau</string>\n    <string name=\"server_address\">Adresse du serveur</string>\n    <string name=\"sync_host_description\">Vous pouvez utiliser un serveur de synchronisation autohébergé ou un serveur par défaut. Ne modifiez pas ce paramètre si vous n\\'êtes pas sûr(e) de ce que vous faites.</string>\n    <string name=\"remove_completed\">Retrait terminé</string>\n    <string name=\"enable\">Activer</string>\n    <string name=\"no_thanks\">Non merci</string>\n    <string name=\"sync_settings\">Paramètres de synchronisation</string>\n    <string name=\"ignore_ssl_errors\">Ignorer les erreurs SSL</string>\n    <string name=\"mirror_switching\">Choisir le miroir automatiquement</string>\n    <string name=\"mirror_switching_summary\">Changement automatique de domaine pour les sources de mangas en cas d\\'erreur si des miroirs sont disponibles</string>\n    <string name=\"pause\">Mettre en pause</string>\n    <string name=\"resume\">Reprendre</string>\n    <string name=\"paused\">En pause</string>\n    <string name=\"cancel_all\">Tout annuler</string>\n    <string name=\"downloads_wifi_only\">Téléchargement uniquement via Wi-Fi</string>\n    <string name=\"remove_completed_downloads_confirm\">Votre historique de téléchargement sera définitivement supprimé. Aucun fichier téléchargé ne sera affecté</string>\n    <string name=\"suggestions_notifications_summary\">Notifications parfois affichées avec des mangas suggérés</string>\n    <string name=\"cancel_all_downloads_confirm\">Tous les téléchargements actifs seront annulés, les données partiellement téléchargées seront perdues</string>\n    <string name=\"downloads_paused\">Les téléchargements ont été interrompus</string>\n    <string name=\"suggestion_manga\">Suggestion : %s</string>\n    <string name=\"more\">Plus</string>\n    <string name=\"text_downloads_list_holder\">Vous n\\'avez pas de téléchargements</string>\n    <string name=\"downloads_resumed\">Les téléchargements ont repris</string>\n    <string name=\"downloads_removed\">Les téléchargements ont été supprimés</string>\n    <string name=\"downloads_cancelled\">Les téléchargements ont été annulés</string>\n    <string name=\"suggestions_enable_prompt\">Voulez-vous recevoir des suggestions de mangas personnalisées \\?</string>\n    <string name=\"web_view_unavailable\">WebView non disponible : vérifier si le fournisseur WebView est installé</string>\n    <string name=\"clear_network_cache\">Effacer le cache réseau</string>\n    <string name=\"type\">Taper</string>\n    <string name=\"address\">Adresse</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"downloaded\">Téléchargé</string>\n    <string name=\"username\">Pseudonyme</string>\n    <string name=\"images_proxy_title\">Proxy d\\'optimisation des images</string>\n    <string name=\"invert_colors\">Inverser les couleurs</string>\n    <string name=\"images_procy_description\">Utilisez le service wsrv.nl pour réduire le trafic et augmenter la vitesse de chargement des images si possible</string>\n    <string name=\"password\">Mot de passe</string>\n    <string name=\"invalid_value_message\">Valeur invalide</string>\n    <string name=\"authorization_optional\">Autorisation (optionnel)</string>\n    <string name=\"invalid_port_number\">Numéro de port invalide</string>\n    <string name=\"restore_summary\">Restaurer la sauvegarde précédemment créée</string>\n    <string name=\"webtoon_zoom_summary\">Autoriser le geste de zoom en mode webtoon</string>\n    <string name=\"reader_info_bar_summary\">Afficher l\\'heure actuelle et la progression de la lecture en haut de l\\'écran</string>\n    <string name=\"network\">Réseau</string>\n    <string name=\"data_and_privacy\">Données et confidentialité</string>\n    <string name=\"show_pages_numbers_summary\">Afficher les numéros de page dans le coin inférieur</string>\n    <string name=\"clear_source_cookies_summary\">Effacer les cookies pour le domaine spécifié uniquement. Dans la plupart des cas, l\\'autorisation sera invalidée</string>\n    <string name=\"download_option_first_n_chapters\">%s premier(s)</string>\n    <string name=\"download_option_next_unread_n_chapters\">%s prochain(s) non lu(s)</string>\n    <string name=\"download_option_all_unread_b\">Tous les chapitres non lus (%s)</string>\n    <string name=\"download_option_all_chapters\">Tous les chapitres avec traduction %s</string>\n    <string name=\"download_option_all_unread\">Tous les chapitres non lus</string>\n    <string name=\"download_option_whole_manga\">Tout le manga</string>\n    <string name=\"download_option_manual_selection\">Sélection manuelle des chapitres</string>\n    <string name=\"no_access_to_file\">Vous n\\'avez pas accès à ce fichier ou répertoire</string>\n    <string name=\"pick_custom_directory\">Choisir un répertoire personnalisé</string>\n    <string name=\"local_manga_directories\">Annuaires locaux de mangas</string>\n    <string name=\"progress\">Progression</string>\n    <string name=\"order_added\">Ajouté</string>\n    <string name=\"tracker_wifi_only_summary\">Ne pas vérifier les nouveaux chapitres en utilisant des connexions réseau tarifées</string>\n    <string name=\"search_hint\">Entrez le titre du manga, le genre ou le nom de la source</string>\n    <string name=\"description\">Description</string>\n    <string name=\"this_month\">Ce mois-ci</string>\n    <string name=\"voice_search\">Recherche vocale</string>\n    <string name=\"related_manga\">Mangas connexes</string>\n    <string name=\"color_light\">Clair</string>\n    <string name=\"color_dark\">Sombre</string>\n    <string name=\"color_white\">Blanc</string>\n    <string name=\"color_black\">Noir</string>\n    <string name=\"background\">Arrière-plan</string>\n    <string name=\"data_not_restored\">Les données n\\'ont pas été restaurées</string>\n    <string name=\"data_not_restored_text\">Assurez-vous que vous avez sélectionné le bon fichier de sauvegarde</string>\n    <string name=\"suggestions_wifi_only_summary\">Ne pas mettre à jour les suggestions en utilisant des connexions réseau</string>\n    <string name=\"manage_categories\">Gérer les catégories</string>\n    <string name=\"show\">Afficher</string>\n    <string name=\"captcha_required_summary\">%s exige qu\\'un Captcha soit résolu pour fonctionner correctement</string>\n    <string name=\"languages\">Langues</string>\n    <string name=\"unknown\">Inconnu</string>\n    <string name=\"in_progress\">En cours</string>\n    <string name=\"disable_nsfw\">Désactiver le contenu explicite</string>\n    <string name=\"too_many_requests_message\">Trop de requêtes. Réessayez plus tard</string>\n    <string name=\"related_manga_summary\">Afficher une liste de mangas connexes. Dans certains cas, elle peut être inexacte ou manquante</string>\n    <string name=\"zoom_in\">Agrandir</string>\n    <string name=\"error_corrupted_file\">Les données invalides sont retournées ou le fichier est corrompu</string>\n    <string name=\"reader_zoom_buttons_summary\">Pour afficher ou pas les boutons de commande de zoom dans le coin inférieur droit</string>\n    <string name=\"on_device\">Sur l\\'appareil</string>\n    <string name=\"moved_to_top\">Déplacé vers le haut</string>\n    <string name=\"items_limit_exceeded\">Plus d\\'éléments peuvent être ajoutés</string>\n    <string name=\"directories\">Répertoires</string>\n    <string name=\"reader_zoom_buttons\">Afficher les boutons de zoom</string>\n    <string name=\"main_screen_sections\">Principales sections de l\\'écran</string>\n    <string name=\"advanced\">Avancé</string>\n    <string name=\"zoom_out\">Réduire</string>\n    <string name=\"manga_list\">Liste des mangas</string>\n    <string name=\"to_top\">En haut</string>\n    <string name=\"sources_catalog\">Sources catalogue</string>\n    <string name=\"frequency_every_day\">Tous les jours</string>\n    <string name=\"categories\">Catégories</string>\n    <string name=\"list_options\">Options de liste</string>\n    <string name=\"content_type_manga\">Mangas</string>\n    <string name=\"error_multiple_states_not_supported\">Filtrer par plusieurs états n\\'est pas pris en charge par cette source de mangas</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"backup_frequency\">Fréquence de création de sauvegarde</string>\n    <string name=\"content_type_hentai\">Hentais</string>\n    <string name=\"suggest_new_sources\">Suggérer de nouvelles sources après la mise à jour de l\\'application</string>\n    <string name=\"periodic_backups_enable\">Activer les sauvegardes périodiques</string>\n    <string name=\"content_type_comics\">Comics</string>\n    <string name=\"catalog\">Catalogue</string>\n    <string name=\"enhanced_colors_summary\">Réduit le crénelage, mais peut avoir une incidence sur les performances</string>\n    <string name=\"frequency_every_2_days\">Tous les 2 jours</string>\n    <string name=\"reader_optimize\">Réduire la consommation de mémoire (bêta)</string>\n    <string name=\"manage_sources\">Gérer les sources</string>\n    <string name=\"no_manga_sources_found\">Aucune source de manga disponible trouvée par votre requête</string>\n    <string name=\"frequency_once_per_week\">Une fois par semaine</string>\n    <string name=\"periodic_backups\">Sauvegardes périodiques</string>\n    <string name=\"frequency_twice_per_month\">Deux fois par mois</string>\n    <string name=\"online_variant\">Variante en ligne</string>\n    <string name=\"error_multiple_genres_not_supported\">Filtrer par plusieurs genres n\\'est pas pris en charge par cette source manga</string>\n    <string name=\"lock_screen_rotation\">Verrouiller la rotation de l\\'écran</string>\n    <string name=\"by_relevance\">Pertinence</string>\n    <string name=\"state_abandoned\">Abandonné</string>\n    <string name=\"keep_screen_on\">Garder l\\'écran allumé</string>\n    <string name=\"error_search_not_supported\">La recherche n\\'est pas prise en charge par cette source de mangas</string>\n    <string name=\"frequency_once_per_month\">Une fois par mois</string>\n    <string name=\"manual\">Manuel</string>\n    <string name=\"reader_optimize_summary\">Réduire la qualité des pages hors écran pour utiliser moins de mémoire</string>\n    <string name=\"source_enabled\">Source activée</string>\n    <string name=\"enhanced_colors\">mode couleur 32-bit</string>\n    <string name=\"disable_nsfw_summary\">Désactiver les sources explicites et masquer les mangas pour adultes de la liste si possible</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"keep_screen_on_summary\">Ne pas désactiver l\\'écran pendant que vous lisez des mangas</string>\n    <string name=\"no_manga_sources_catalog_text\">Il n\\'y a pas de sources disponibles dans cette section, ou tout cela aurait pu être ajouté.\n\\nRestez à l\\'écoute</string>\n    <string name=\"available_d\">Disponible : %1$d</string>\n    <string name=\"state\">État</string>\n    <string name=\"last_successful_backup\">Dernière sauvegarde réussie : %s</string>\n    <string name=\"state_paused\">En pause</string>\n    <string name=\"backups_output_directory\">Sauvegardes répertoire de sortie</string>\n    <string name=\"suggest_new_sources_summary\">Prompt pour permettre des sources nouvellement ajoutées après la mise à jour de l\\'application</string>\n    <string name=\"content_type_other\">Autre</string>\n    <string name=\"state_upcoming\">À venir</string>\n    <string name=\"welcome_text\">Veuillez choisir les sources que vous souhaitez activer. Cela peut aussi être configuré plus tard dans les paramètres</string>\n    <string name=\"sync_auth\">Connectez-vous pour synchroniser le compte</string>\n    <string name=\"skip\">Passer</string>\n    <string name=\"restore\">Restaurer</string>\n    <string name=\"content_rating\">Classification du contenu</string>\n    <string name=\"genres_exclude\">Exclure les genres</string>\n    <string name=\"rating_safe\">Tout public</string>\n    <string name=\"rating_adult\">Adulte</string>\n    <string name=\"default_tab\">Onglet par défaut</string>\n    <string name=\"color_correction_apply_text\">Ces paramètres peuvent être appliqué globalement ou uniquement au manga actuel. S\\'il est activé globalement il ne remplacera les paramètres individuel.</string>\n    <string name=\"globally\">Globalement</string>\n    <string name=\"this_manga\">Ce manga</string>\n    <string name=\"apply\">Appliqué</string>\n    <string name=\"genres_search_hint\">Commence a écrit un nom de genre</string>\n    <string name=\"downloads_settings_info\">Vous pouvez réduire la vitesse de téléchargement individuellement pour chaque source de manga dans les paramètres si vous rencontrez des problèmes de blocage avec le serveur</string>\n    <string name=\"backup_date_\">Date de sauvegarde : %s</string>\n    <string name=\"by_name_reverse\">Nom inversé</string>\n    <string name=\"mark_as_completed\">Marquer comme terminé</string>\n    <string name=\"mark_as_completed_prompt\">Marquer le manga sélectionné comme terminé ?\\n\\nAttention : la progression actuelle sera perdue.</string>\n    <string name=\"incognito_mode_hint\">Votre progression de lecture ne sera pas sauvegardée</string>\n    <string name=\"last_read\">Dernier lu</string>\n    <string name=\"vertical\">Vertical</string>\n    <string name=\"email_password_enter_hint\">Entrez votre courriel et mot de passe pour continuer</string>\n    <string name=\"grayscale\">Niveaux de gris</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Le filtrage à la fois par genres et par langues n\\'est pas supporté par cette source</string>\n    <string name=\"error_filter_states_genre_not_supported\">Le filtrage à la fois par genres et par statut n\\'est pas supporté par cette source</string>\n    <string name=\"volume_\">Volume %d</string>\n    <string name=\"volume_unknown\">Volume inconnu</string>\n    <string name=\"show_menu\">Afficher le menu</string>\n    <string name=\"toggle_ui\">Afficher/cacher UI</string>\n    <string name=\"prev_chapter\">Chapitre précèdent</string>\n    <string name=\"next_chapter\">Chapitre suivant</string>\n    <string name=\"prev_page\">Page précédente</string>\n    <string name=\"next_page\">Page suivante</string>\n    <string name=\"switch_pages_volume_buttons\">Activer les boutons de volume</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Utiliser les boutons de volume pour changer de page</string>\n    <string name=\"none\">Aucun</string>\n    <string name=\"config_reset_confirm\">Revenir aux paramètres par défaut ? Cette action ne pourra pas être annulée.</string>\n    <string name=\"use_two_pages_landscape\">Utiliser la disposition pages doubles en orientation paysage (bêta)</string>\n    <string name=\"fullscreen_mode\">Mode plein écran</string>\n    <string name=\"category_hidden_done\">Cette catégorie a été cachée du menu principal et est accessible via Menu → Gérer les catégories</string>\n    <string name=\"reading_time_estimation\">Afficher le temps de lecture estimé</string>\n    <string name=\"reading_time_estimation_summary\">Le temps estimé peut être inexact</string>\n    <string name=\"ask_for_dest_dir_every_time\">Demander pour le répertoire de destination à chaque fois</string>\n    <string name=\"default_page_save_dir\">Répertoire par défaut pour les pages sauvegardées</string>\n    <string name=\"remove_from_history\">Retirer de l\\'historique</string>\n    <string name=\"reader_actions\">Actions du lecteur</string>\n    <string name=\"reader_fullscreen_summary\">Cache la barre d\\'état et les barres de navigations</string>\n    <string name=\"suggestions_unavailable_text\">La fonctionnalité de suggestions est désactivée</string>\n    <string name=\"check_for_new_chapters_disabled\">La vérification des nouveaux chapitres est désactivée</string>\n    <string name=\"rating_suggestive\">Suggestif</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Peut aider à démarrer le téléchargement si vous avez des problèmes avec</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"reader_actions_summary\">Configurer les actions pour les zones d’écran tactiles</string>\n    <string name=\"tap_action\">Action de tapoter</string>\n    <string name=\"long_tap_action\">Action d\\'appuyez longuement</string>\n    <string name=\"show_labels_in_navbar\">Afficher les étiquettes dans la barre de navigation</string>\n    <string name=\"pages_saving\">Sauvegarder les pages</string>\n    <string name=\"default_webtoon_zoom_out\">Zoom webtoon par défaut</string>\n    <string name=\"unsupported_backup_message\">Veuillez sélectionner un fichier de sauvegarde Kotatsu valide</string>\n    <string name=\"preferred_download_format\">Format de téléchargement préféré</string>\n    <string name=\"clear_stats_confirm\">Voulez-vous vraiment effacer tout les statistiques de lecture ? Cette action ne peut pas annuler.</string>\n    <string name=\"reading_stats\">Statistiques de lecture</string>\n    <string name=\"week\">Semaine</string>\n    <string name=\"month\">Mois</string>\n    <string name=\"three_months\">Trois mois</string>\n    <string name=\"automatic\">Automatique</string>\n    <string name=\"chapters_grid_view\">Vue en grille</string>\n    <string name=\"location\">Emplacement</string>\n    <string name=\"single_cbz_file\">Fichier CBZ unique</string>\n    <string name=\"multiple_cbz_files\">Fichiers CBZ multiple</string>\n    <string name=\"other_manga\">Autres mangas</string>\n    <string name=\"less_than_minute\">Moins d\\'une minute</string>\n    <string name=\"statistics\">Statistiques</string>\n    <string name=\"clear_stats\">Effacer les statistiques</string>\n    <string name=\"stats_cleared\">Statistiques effacés</string>\n    <string name=\"all_time\">Tous les temps</string>\n    <string name=\"day\">Jour</string>\n    <string name=\"empty_stats_text\">Il n\\'y a pas de statistiques pour la période sélectionné</string>\n    <string name=\"pages_read_s\">Pages lues : %s</string>\n    <string name=\"alternatives\">Alternatives</string>\n    <string name=\"migrate\">Migrer</string>\n    <string name=\"migrate_confirmation\">Le manga \\\"%1$s\\\" de \\\"%2$s\\\" sera remplacé par \\\"%3$s\\\" de \\\"%4$s\\\" dans votre historique et vos favoris (si présent)</string>\n    <string name=\"manga_migration\">Migration du manga</string>\n    <string name=\"migration_completed\">Migration effectuée</string>\n    <string name=\"no_chapters_deleted\">Aucun chapitres n\\'a été effacé</string>\n    <string name=\"delete_read_chapters\">Effacer les chapitres lus</string>\n    <string name=\"chapters_deleted_pattern\">%1$s supprimé, %2$s effacé</string>\n    <string name=\"delete_read_chapters_summary\">Effacer les chapitres déjà lus pour libérer de l\\'espace de stockage</string>\n    <string name=\"delete_read_chapters_auto\">Effacer automatiquement les chapitres lus</string>\n    <string name=\"show_pages_thumbs\">Afficher les miniatures des pages</string>\n    <string name=\"split_by_translations\">Filtré par traductions</string>\n    <string name=\"order_oldest\">Le plus ancien</string>\n    <string name=\"long_ago_read\">Lu il y a longtemps</string>\n    <string name=\"unread\">Non lu</string>\n    <string name=\"split_by_translations_summary\">Afficher les chapitres avec des traductions différentes séparément, au lieu d\\'une seule liste</string>\n    <string name=\"enable_source\">Activer la source</string>\n    <string name=\"unsupported_source\">Cette source de manga n\\'est pas supporté</string>\n    <string name=\"fix\">Corrigé</string>\n    <string name=\"delete_read_chapters_prompt\">Cela va définitivement effacé tout les chapitres marqués comme lus de l\\'espace de stockage. Vous pouvez les re-télécharger plus tard, mais les chapitres importé pourraient ne pas être récupéré.</string>\n    <string name=\"last_used\">Dernière utilisation</string>\n    <string name=\"missing_storage_permission\">Il n\\'y a aucune autorisation pour accéder aux mangas sur le stockage externe</string>\n    <string name=\"pin_navigation_ui\">Épingler l\\'interface de navigation</string>\n    <string name=\"hours_short\">%d h</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"hours_minutes_short\">%1$d h %2$d m</string>\n    <string name=\"runs_on_app_start\">S\\'exécute au démarrage de l\\'application</string>\n    <string name=\"show_pages_thumbs_summary\">Active l\\'onglet \\\"Pages\\\" sur l\\'écran de détails</string>\n    <string name=\"show_updated\">Afficher mis à jour</string>\n    <string name=\"webtoon_gaps\">Séparateurs en mode webtoon</string>\n    <string name=\"webtoon_gaps_summary\">Afficher des espaces verticaux entre les pages en mode webtoon</string>\n    <string name=\"search_suggestions\">Suggestions de recherche</string>\n    <string name=\"suggested_queries\">Requêtes suggérées</string>\n    <string name=\"recent_queries\">Requêtes récentes</string>\n    <string name=\"authors\">Auteurs</string>\n    <string name=\"error_no_data_received\">Aucune donnée n\\'a été reçue du serveur</string>\n    <string name=\"blocked_by_server_message\">Vous êtes bloqué par le serveur. Essayez d\\'utiliser une autre connexion réseau (VPN, proxy, etc.)</string>\n    <string name=\"more_frequently\">Plus souvent</string>\n    <string name=\"less_frequently\">Moins souvent</string>\n    <string name=\"new_chapters_pattern\">%1$s : %2$d</string>\n    <string name=\"pin_navigation_ui_summary\">Ne pas masquer la barre de navigation et la vue de recherche lors du défilement</string>\n    <string name=\"ignore_ssl_errors_summary\">Vous pouvez désactiver la vérification des certificats SSL au cas où vous rencontreriez des problèmes liés à SSL lors de l\\'accès aux ressources réseau. Cela peut affecter votre sécurité. Le redémarrage de l\\'application est requis après avoir modifié ce paramètre.</string>\n    <string name=\"frequency_of_check\">Fréquence de vérification</string>\n    <string name=\"disable\">Désactiver</string>\n    <string name=\"sources_disabled\">Sources désactivées</string>\n    <string name=\"disable_connectivity_check\">Désactiver la vérification de la connectivité</string>\n    <string name=\"disable_connectivity_check_summary\">Ignorez la vérification de la connectivité au cas où vous rencontreriez des problèmes (par exemple, passage en mode hors ligne alors que le réseau est connecté)</string>\n    <string name=\"disable_nsfw_notifications\">Désactiver les notifications de contenu explicite</string>\n    <string name=\"disable_nsfw_notifications_summary\">Ne pas afficher les notifications concernant les mises à jour des mangas explicites</string>\n    <string name=\"tracker_debug_info\">Vérification du journal des nouveaux chapitres</string>\n    <string name=\"tracker_debug_info_summary\">Informations de débogage sur la vérification en arrière-plan des nouveaux chapitres</string>\n    <string name=\"_new\">Nouveaux</string>\n    <string name=\"unstable_feature\">Fonctionnalité instable</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Il n\\'y a aucun manga correspondant aux filtres que vous avez sélectionnés</string>\n    <string name=\"connection_ok\">La connexion est bonne</string>\n    <string name=\"content_type_novel\">Roman</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"recently_added\">Récemment ajouté</string>\n    <string name=\"added_long_ago\">Ajouté il y a longtemps</string>\n    <string name=\"popular_in_hour\">Populaire cette heure-ci</string>\n    <string name=\"popular_today\">Populaire aujourd\\'hui</string>\n    <string name=\"popular_in_month\">Populaire ce mois-ci</string>\n    <string name=\"popular_in_week\">Populaire cette semaine-ci</string>\n    <string name=\"popular_in_year\">Populaire cette année</string>\n    <string name=\"original_language\">Langue originale</string>\n    <string name=\"year\">Année</string>\n    <string name=\"demographics\">Public cible</string>\n    <string name=\"demographic_shounen\">Shōnen</string>\n    <string name=\"demographic_shoujo\">Shōjo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"years\">Années</string>\n    <string name=\"any\">N’importe</string>\n    <string name=\"filter_search_warning\">Cette source ne prend pas en charge la recherche avec des filtres. Vos filtres ont été effacés</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_one_shot\">One shot</string>\n    <string name=\"screen_orientation\">Orientation de l\\'écran</string>\n    <string name=\"content_type_doujinshi\">Dōjinshi</string>\n    <string name=\"content_type_artist_cg\">Artiste en infographie</string>\n    <string name=\"content_type_game_cg\">Infographie de jeu</string>\n    <string name=\"screenshots_block_incognito\">Bloquer en mode incognito</string>\n    <string name=\"debug\">Déboguer</string>\n    <string name=\"image_server\">Serveur d\\'images privilégié</string>\n    <string name=\"invalid_server_address_message\">Adresse du serveur invalide</string>\n    <string name=\"chapters_left\">Chapitres restants</string>\n    <string name=\"sort_order_desc\">Descendant</string>\n    <string name=\"by_date\">Date</string>\n    <string name=\"popularity\">Popularité</string>\n    <string name=\"scrobbler_auth_required\">Connectez-vous à %s pour continuer</string>\n    <string name=\"scrobbler_auth_intro\">Connectez-vous pour configurer l\\'intégration avec %s. Ceci vous permettra de suivre vos progrès de lecture de manga et l\\'état</string>\n    <string name=\"unstable_feature_summary\">Cette fonction est expérimentale. Assurez-vous d\\'avoir une sauvegarde pour éviter la perte de données</string>\n    <string name=\"source_code\">Code source</string>\n    <string name=\"user_manual\">Manuel d\\'utilisation</string>\n    <string name=\"telegram_group\">Groupe Telegram</string>\n    <string name=\"portrait\">Portrait</string>\n    <string name=\"landscape\">Paysage</string>\n    <string name=\"crop_pages\">Recadrer les pages</string>\n    <string name=\"pin\">Épingler</string>\n    <string name=\"source_unpinned\">Source désépinglée</string>\n    <string name=\"unpin\">Désépingler</string>\n    <string name=\"source_pinned\">Source épinglée</string>\n    <string name=\"error_image_format\">Format d\\'image non pris en charge : %s</string>\n    <string name=\"downloads_background\">Téléchargements en arrière-plan</string>\n    <string name=\"download_new_chapters\">Télécharger de nouveaux chapitres</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga avec chapitres téléchargés</string>\n    <string name=\"fixing_manga\">Réparation du manga</string>\n    <string name=\"fixed\">Correction réussie</string>\n    <string name=\"manga_fix_prompt\">Cette fonction trouvera d\\'autres sources pour le manga sélectionné. La tâche prendra du temps et se déroulera en arrière-plan</string>\n    <string name=\"no_fix_required\">Pas de correction requise pour \\\"%s\\\"</string>\n    <string name=\"no_alternatives_found\">Aucune alternative trouvée pour \\\"%s\\\"</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) remplacé par \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"external_source\">Externe/module d\\'extension</string>\n    <string name=\"start_download\">Commencer à télécharger</string>\n    <string name=\"save_manga_confirm\">Enregistrer le manga sélectionné ? Ceci peut consommer des données et de l\\'espace disque</string>\n    <string name=\"save_manga\">Enregistrer le manga</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"minutes_seconds_short\">%1$d min %2$d s</string>\n    <string name=\"recent_sources\">Sources récentes</string>\n    <string name=\"invalid_proxy_configuration\">Configuration par proxy invalide</string>\n    <string name=\"show_quick_filters\">Afficher les filtres rapides</string>\n    <string name=\"show_quick_filters_summary\">Fournit la capacité de filtrer les listes de manga par certains paramètres</string>\n    <string name=\"skip_all\">Tout ignorer</string>\n    <string name=\"stuck\">Bloqué</string>\n    <string name=\"retry\">Réessayer</string>\n    <string name=\"too_many_requests_message_retry\">Trop de demandes. Essayez encore après %s</string>\n    <string name=\"not_in_favorites\">Pas dans les favoris</string>\n    <string name=\"plugin_incompatible\">Greffon incompatible ou erreur interne. Assurez-vous d\\'utiliser la dernière version du greffon et de Kotatsu</string>\n    <string name=\"all_languages\">Toutes les langues</string>\n    <string name=\"sources_unpinned\">Sources désépinglées</string>\n    <string name=\"sources_pinned\">Sources épinglées</string>\n    <string name=\"percent_left\">Pourcentage restant</string>\n    <string name=\"percent_read\">Pourcentage lu</string>\n    <string name=\"chapters_read\">Chapitres lus</string>\n    <string name=\"genre\">Genre</string>\n    <string name=\"updated_long_ago\">Mis à jour il y a longtemps</string>\n    <string name=\"unpopular\">Impopulaire</string>\n    <string name=\"low_rating\">Notes faibles</string>\n    <string name=\"sort_order_asc\">Ascendant</string>\n    <string name=\"chapter_selection_hint\">Vous pouvez sélectionner des chapitres à télécharger en cliquant sur l\\'élément dans la liste de chapitres.</string>\n    <string name=\"download_added\">Télécharger ajouté</string>\n    <string name=\"more_options\">Autres options</string>\n    <string name=\"destination_directory\">Répertoire de la destination</string>\n    <string name=\"chapters_all\">Tous</string>\n    <string name=\"download_over_cellular\">Téléchargement via réseau mobile</string>\n    <string name=\"download_cellular_confirm\">Autoriser les téléchargements via réseau mobile ?</string>\n    <string name=\"dont_allow\">Ne pas autoriser</string>\n    <string name=\"allow_always\">Toujours autoriser</string>\n    <string name=\"allow_once\">Autoriser une fois</string>\n    <string name=\"ask_every_time\">Demander à chaque fois</string>\n    <string name=\"plugin_incompatible_with_cause\">Erreur du module d\\'extension : %s\\n- Assurez-vous que vous utilisez la dernière version du module d\\'extension et de Kotatsu</string>\n    <string name=\"content_type_image_set\">Collection d\\'images</string>\n    <string name=\"error_not_image\">Format invalide : une image était attendue mais a reçu %s</string>\n    <string name=\"access_denied_403\">Accès refusé (403)</string>\n    <string name=\"max_backups_count\">Nombre maximal de sauvegardes</string>\n    <string name=\"sfw\">Contenu non explicite</string>\n    <string name=\"pages_saved\">Pages sauvegardées</string>\n    <string name=\"delete_old_backups_summary\">Suppression automatique des anciens fichiers de sauvegarde pour économiser de l\\'espace de stockage</string>\n    <string name=\"delete_old_backups\">Supprimer les anciennes sauvegardes</string>\n    <string name=\"handle_links\">Gérer les liens</string>\n    <string name=\"email\">Courriel</string>\n    <string name=\"handle_links_summary\">Gérer les liens vers des mangas à partir d\\'applications externes (par exemple, un navigateur web). Il se peut que vous deviez également l\\'activer manuellement dans les paramètres système de l\\'application</string>\n    <string name=\"captcha_required_message\">Cette source nécessite la résolution d\\'un captcha pour continuer</string>\n    <string name=\"author\">Auteur</string>\n    <string name=\"source\">Source</string>\n    <string name=\"translation\">Traduction</string>\n    <string name=\"backup_tg_check\">Vérifier si l\\'API fonctionne</string>\n    <string name=\"open_telegram_bot\">Ouvrir le bot Telegram</string>\n    <string name=\"incognito\">Mode Incognito</string>\n    <string name=\"telegram_chat_id\">ID de chat Telegram</string>\n    <string name=\"backup_tg_echo\">Message de test</string>\n    <string name=\"test_connection\">Tester la connexion</string>\n    <string name=\"open_telegram_bot_summary\">Appuyez pour ouvrir le chat avec le bot de sauvegarde Kotatsu</string>\n    <string name=\"error_connection_reset\">Connexion réinitialisée par l\\'hôte distant</string>\n    <string name=\"backup_tg_id_not_set\">L\\'ID du chat n\\'est pas défini</string>\n    <string name=\"telegram_chat_id_summary\">Entrez l\\'ID du chat où les sauvegardes doivent être envoyées</string>\n    <string name=\"send_backups_telegram\">Envoyer les sauvegardes sur Telegram</string>\n    <string name=\"show_slider\">Afficher le curseur</string>\n    <string name=\"clear_database\">Effacer la base de données</string>\n    <string name=\"clear_database_summary\">Supprimer les informations sur les mangas qui ne sont pas utilisées</string>\n    <string name=\"rating\">Note</string>\n    <string name=\"enable_all_sources_summary\">Toutes les sources de mangas disponibles seront activées de façon permanente</string>\n    <string name=\"all_sources_enabled\">Toutes les sources sont activées</string>\n    <string name=\"enable_all_sources\">Activer toutes les sources de mangas</string>\n    <string name=\"backup_restored_background\">La sauvegarde sera restaurée en arrière-plan</string>\n    <string name=\"restoring_backup\">Restauration de la sauvegarde</string>\n    <string name=\"error_details\">Détails de l\\'erreur</string>\n    <string name=\"error_disclaimer_manga\">Essayez d\\'ouvrir le manga dans un navigateur Web pour vous assurer qu\\'il est disponible sur sa source.</string>\n    <string name=\"error_disclaimer_app_outdated\">Il semble que votre version de Kotatsu soit obsolète. Veuillez installer la dernière version pour obtenir tous les correctifs disponibles.</string>\n    <string name=\"error_disclaimer_report\">Vous pouvez envoyer un rapport des erreurs aux développeurs. Cela nous aidera à enquêter sur le problème et à le résoudre.</string>\n    <string name=\"search_disabled_sources\">Rechercher dans les sources désactivées</string>\n    <string name=\"reader_info_bar_transparent\">Barre d\\'informations transparente pour le lecteur</string>\n    <string name=\"chapter_volume_number\">Vol %1$s chapitre %2$s</string>\n    <string name=\"chapter_number\">Chapitre %s</string>\n    <string name=\"unnamed_chapter\">Chapitre sans nom</string>\n    <string name=\"reader_controls_in_bottom_bar\">Commandes du lecteur dans la barre inférieure</string>\n    <string name=\"chapters_and_pages\">Chapitres et pages</string>\n    <string name=\"pages_slider\">Curseur de changement de page</string>\n    <string name=\"screen_rotation_locked\">La rotation de l\\'écran a été verrouillée</string>\n    <string name=\"screen_rotation_unlocked\">La rotation de l\\'écran a été déverrouillée</string>\n    <string name=\"link_to_manga_on_s\">Lien vers le manga sur %s</string>\n    <string name=\"link_to_manga_in_app\">Lien vers le manga dans Kotatsu</string>\n    <string name=\"simple\">Simple</string>\n    <string name=\"disable_captcha_notifications\">Désactiver les notifications captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Vous ne recevrez pas de notifications concernant la résolution du CAPTCHA pour cette source, mais cela peut entraîner l\\'interruption des opérations en arrière-plan (vérification de nouveaux chapitres, obtention de recommandations, etc)</string>\n    <string name=\"global_search\">Recherche globale</string>\n    <string name=\"search_everywhere\">Chercher partout</string>\n    <string name=\"badges_in_lists\">Badges dans les listes</string>\n    <string name=\"no_write_permission_to_file\">N\\'a pas la permission d\\'écrire un fichier</string>\n    <string name=\"clear_browser_data\">Effacer les données du navigateur</string>\n    <string name=\"clear_browser_data_summary\">Efface les données du navigateur telles que le cache et les cookies. Avertissement : l\\'autorisation sur les sources de mangas peut devenir invalide</string>\n    <string name=\"include_disabled_sources\">Inclure les sources désactivées</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Les mangas pour adultes ne seront pas affichés dans les suggestions. Cette option peut fonctionner de manière inexacte avec certaines sources</string>\n    <string name=\"suggestions_disabled_sources_summary\">Afficher les suggestions de toutes les sources de mangas, y compris celles désactivées</string>\n    <string name=\"tags_warnings\">Mettre en évidence les genres dangereux</string>\n    <string name=\"incognito_mode_hint_nsfw\">Ce manga peut contenir du contenu réservé aux adultes. Voulez-vous utiliser le mode navigation privée ?</string>\n    <string name=\"incognito_for_nsfw\">Mode navigation privée pour les mangas NSFW</string>\n    <string name=\"dont_ask_again\">Ne plus demander</string>\n    <string name=\"hide_from_main_screen\">Masquer de l\\'écran principal</string>\n    <string name=\"changelog\">Journal des modifications</string>\n    <string name=\"pick_manga_page\">Choisissez une page de manga</string>\n    <string name=\"manga_override_hint\">Ces changements affecteront la façon dont les mangas sont affichés dans l\\'application</string>\n    <string name=\"tags_warnings_summary\">Mettre en évidence les genres qui peuvent être inappropriés pour la plupart des utilisateurs</string>\n    <string name=\"change_cover\">Changer la couverture</string>\n    <string name=\"changelog_summary\">Historique des modifications pour les versions récemment publiées</string>\n    <string name=\"use_default_cover\">Utiliser la couverture par défaut</string>\n    <string name=\"additional_action_required\">Des mesures supplémentaires sont nécessaires</string>\n    <string name=\"page_switch_timer\">La page changera toutes les ~%d secondes</string>\n    <string name=\"error_non_file_uri\">Le chemin sélectionné ne peut pas être utilisé car il ne désigne pas un fichier ou un répertoire</string>\n    <string name=\"pick_custom_file\">Choisir un fichier personnalisé</string>\n    <string name=\"theme_name_expressive\">Expressif (Test)</string>\n    <string name=\"collapse\">Réduire</string>\n    <string name=\"expand\">Développer</string>\n    <string name=\"adblock\">Bloquer les publicités dans le navigateur</string>\n    <string name=\"adblock_summary\">Bloquer les publicités dans le navigateur intégré (bêta)</string>\n    <string name=\"share_backup\">Partager la sauvegarde</string>\n    <string name=\"creating_backup\">Création de sauvegarde</string>\n    <string name=\"collapse_long_description\">Réduire la description longue</string>\n    <string name=\"reader_multitask_summary\">Permet de garder plusieurs lecteurs avec différents mangas ouverts en même temps</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"reader_multitask\">Ouvrir le lecteur dans une tâche séparée</string>\n    <string name=\"reader_navigation_inverted\">Inverser les commandes de navigation</string>\n    <string name=\"reader_navigation_inverted_summary\">Inverser le sens des boutons de volume et des touches directionnelles matérielles (gauche/haut/bas/droite)</string>\n    <string name=\"book_effect\">Fond jaunâtre (filtre bleu)</string>\n    <string name=\"local_storage_cleanup\">Nettoyage du stockage local</string>\n    <string name=\"packup_creation_failed\">Échec de la création de la sauvegarde</string>\n    <string name=\"main_screen\">Écran principal</string>\n    <string name=\"main_screen_fab\">Afficher le bouton flottant Continuer</string>\n    <string name=\"main_screen_fab_summary\">Permet de continuer la lecture en un clic. Ce bouton n’apparaîtra pas en mode navigation privée ou lorsque l’historique est vide</string>\n    <string name=\"error_corrupted_zip\">Archive ZIP corrompue (%s)</string>\n    <string name=\"discord_rpc\">Présence enrichie Discord</string>\n    <string name=\"discord_token\">Jeton Discord</string>\n    <string name=\"discord_token_summary\">Entre ton jeton Discord pour activer la présence enrichie</string>\n    <string name=\"discord_token_description\">Entre ton jeton Discord ou clique sur %s pour l’obtenir via le navigateur</string>\n    <string name=\"discord_token_hint\">Colle ton jeton Discord ici</string>\n    <string name=\"discord_rpc_summary\">Affiche ton statut de lecture sur Discord</string>\n    <string name=\"obtain\">Obtenir</string>\n    <string name=\"discord_rpc_description\">Lecture de manga sur Kotatsu – une application de lecture de manga</string>\n    <string name=\"reading_s\">Lecture de %s</string>\n    <string name=\"read_on_s\">Lire sur %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Ne pas utiliser la présence enrichie pour du contenu adulte</string>\n    <string name=\"invalid_token\">Jeton invalide : %s</string>\n    <string name=\"show_floating_control_button\">Afficher le bouton de commande flottant</string>\n    <string name=\"unavailable\">Non disponible</string>\n    <string name=\"manga_restricted_description\">Ce manga n’est pas disponible à la lecture sur cette source. Essaie de le chercher dans d’autres sources ou ouvre-le dans un navigateur pour plus d’informations</string>\n    <string name=\"no_chapters_in_manga\">Ce manga ne contient aucun chapitre</string>\n    <string name=\"chapters_load_failed\">Échec du chargement de la liste des chapitres</string>\n    <string name=\"telegram_integration\">Intégration Telegram</string>\n    <string name=\"pull_to_prev_chapter\">Relâchez pour ouvrir le chapitre précédent</string>\n    <string name=\"pull_to_next_chapter\">Relâchez pour ouvrir le chapitre suivant</string>\n    <string name=\"pull_top_no_prev\">Pas de chapitre précédent</string>\n    <string name=\"pull_bottom_no_next\">Pas de chapitre suivant</string>\n    <string name=\"enable_pull_gesture_title\">Activer le geste de glissement</string>\n    <string name=\"enable_pull_gesture_summary\">Utilisez le geste de glissement pour changer de chapitre dans un webtoon</string>\n    <string name=\"test_parser\">Tester la source manga</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-frp/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-got/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d 𐍈𐌴𐌹𐌻𐌰</item>\n        <item quantity=\"other\">%1$d 𐍈𐌴𐌹𐌻𐍉𐍃</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d 𐌼𐌹𐌽𐌿𐍄𐌿𐍃</item>\n        <item quantity=\"other\">%1$d 𐌼𐌹𐌽𐌿𐍄𐌾𐌿𐍃</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">𐍆𐌰𐌿𐍂𐌰 %1$d 𐌳𐌰𐌲</item>\n        <item quantity=\"other\">𐍆𐌰𐌿𐍂𐌰 %1$d 𐌳𐌰𐌲𐌰𐌽𐍃</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">𐍆𐌰𐌿𐍂𐌰 𐌼𐌴𐌽𐍉𐌸 %1$d</item>\n        <item quantity=\"other\">𐍆𐌰𐌿𐍂𐌰 𐌼𐌴𐌽𐍉𐌸𐌿𐌼 %1$d</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-got/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"history\">𐍃𐍀𐌹𐌻𐌻</string>\n    <string name=\"error_occurred\">𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸</string>\n    <string name=\"remote_sources\">𐌼𐌰𐌲𐌲𐌹𐌽𐍃 𐌱𐍂𐌿𐌽𐌽𐌰𐌽𐍃</string>\n    <string name=\"computing_\">𐍂𐌰𐌷𐌽𐌾𐌰𐌳𐌰…</string>\n    <string name=\"read\">𐌰𐌽𐌰𐌺𐌿𐌽𐌽𐌰𐌽</string>\n    <string name=\"share_s\">%s 𐌳𐌰𐌹𐌻𐌾𐌰𐌽</string>\n    <string name=\"search\">𐍃𐍉𐌺𐌾𐌰𐌽</string>\n    <string name=\"search_manga\">𐌼𐌰𐌲𐌲𐌰 𐍃𐍉𐌺𐌾𐌰𐌽</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-gu/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-hi/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d आइटम</item>\n        <item quantity=\"other\">%1$d आइटम्स</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d कुछ_मिनट_पहले</item>\n        <item quantity=\"other\">%1$d कुछ_मिनेटो_पहले</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d घंटेभर_पहले</item>\n        <item quantity=\"other\">%1$d घंटोंभर_पहले</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d नया अध्याय</item>\n        <item quantity=\"other\">%1$d नए अध्याय</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d अध्याय</item>\n        <item quantity=\"other\">%1$d अध्याय</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d कुछ_दिन_पहले</item>\n        <item quantity=\"other\">%1$d कुछ_दिनों_पहले</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d महीने पहले</item>\n        <item quantity=\"other\">%1$d महीनो पहले</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d घंटा</item>\n        <item quantity=\"other\">%1$d घंटे</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d मिनट</item>\n        <item quantity=\"other\">%1$d मिनट</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-hi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"details\">विवरण</string>\n    <string name=\"chapters\">अध्याय</string>\n    <string name=\"nothing_found\">कुछ नहीं मिला</string>\n    <string name=\"history_is_empty\">अभी तक कोई इतिहास नहीं है</string>\n    <string name=\"read\">पढ़ें</string>\n    <string name=\"add_to_favourites\">इसे पसंदीदा बनाएं</string>\n    <string name=\"add\">जोड़ें</string>\n    <string name=\"save\">सहेजें</string>\n    <string name=\"newest\">नवीनतम</string>\n    <string name=\"light\">हल्की</string>\n    <string name=\"dark\">गहरी</string>\n    <string name=\"close\">बंद करे</string>\n    <string name=\"try_again\">पुनः प्रयास करें</string>\n    <string name=\"you_have_not_favourites_yet\">अभी तक कोई पसंदीदा नहीं है</string>\n    <string name=\"remove\">हटाएं</string>\n    <string name=\"by_name\">नाम</string>\n    <string name=\"popular\">लोकप्रिय</string>\n    <string name=\"local_storage\">स्थानीय स्टॉरेज</string>\n    <string name=\"error_occurred\">कोई त्रुटि हुई</string>\n    <string name=\"network_error\">नेटवर्क समस्या</string>\n    <string name=\"favourites\">पसंदीदा</string>\n    <string name=\"detailed_list\">विस्तृत सूची</string>\n    <string name=\"settings\">सेटिंग्स</string>\n    <string name=\"list_mode\">सूची मोड</string>\n    <string name=\"chapter_d_of_d\">अध्याय %1$d, %2$d में से</string>\n    <string name=\"computing_\">गणना हो रही है…</string>\n    <string name=\"add_new_category\">नई श्रेणी</string>\n    <string name=\"clear_history\">इतिहास साफ करें</string>\n    <string name=\"share\">साझा करें</string>\n    <string name=\"create_shortcut\">शॉर्टकट बनाएं</string>\n    <string name=\"share_s\">%s साझा करें</string>\n    <string name=\"search\">खोजें</string>\n    <string name=\"search_manga\">मंगा खोजें</string>\n    <string name=\"manga_downloading_\">डाउनलोड हो रहा है…</string>\n    <string name=\"downloads\">डाउनलोड</string>\n    <string name=\"by_rating\">रेटिंग</string>\n    <string name=\"clear\">साफ करे</string>\n    <string name=\"page_saved\">सहेजा गया</string>\n    <string name=\"share_image\">छवि साझा करें</string>\n    <string name=\"delete\">मिटाएं</string>\n    <string name=\"clear_pages_cache\">पृष्ठ कैशे साफ करें</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">मानक</string>\n    <string name=\"webtoon\">वेबटून</string>\n    <string name=\"remote_sources\">मांगा स्रोत</string>\n    <string name=\"download_complete\">डाउनलोड किया गया</string>\n    <string name=\"processing_\">प्रसंस्करण…</string>\n    <string name=\"history\">इतिहास</string>\n    <string name=\"grid\">ग्रिड</string>\n    <string name=\"loading_\">लोड हो रहा है…</string>\n    <string name=\"text_file_not_supported\">ZIP या CBZ मैं से एक फाइल चुनें।</string>\n    <string name=\"updated\">अद्यतित</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" को स्थानीय स्टोरेज से मिटा दिया गया</string>\n    <string name=\"save_page\">पृष्ठ सहेजें</string>\n    <string name=\"_import\">आयात</string>\n    <string name=\"operation_not_supported\">यह कार्य समर्थित नहीं है</string>\n    <string name=\"sort_order\">छंटाई क्रम</string>\n    <string name=\"list\">सूची</string>\n    <string name=\"filter\">फिल्टर</string>\n    <string name=\"theme\">थीम</string>\n    <string name=\"follow_system\">सिस्टम की पालना</string>\n    <string name=\"pages\">पन्ने</string>\n    <string name=\"no_description\">कोई विवरण नहीं है</string>\n    <string name=\"updates\">अद्यतन</string>\n    <string name=\"manga_shelf\">दराज</string>\n    <string name=\"text_history_holder_secondary\">«अन्वेषण करें» अनुभाग में जानें कि क्या पढ़ना है</string>\n    <string name=\"all_favourites\">सभी पसंदीदा</string>\n    <string name=\"light_indicator\">LED सूचक</string>\n    <string name=\"favourites_categories\">पसंदीदा श्रेणियां</string>\n    <string name=\"clear_thumbs_cache\">थंबनेल कैच को साफ करे</string>\n    <string name=\"switch_pages\">पन्नो को बदले</string>\n    <string name=\"rotate_screen\">स्क्रीन घुमायें</string>\n    <string name=\"vibration\">कंपन</string>\n    <string name=\"remove_category\">हटाएं</string>\n    <string name=\"read_mode\">पढ़ने की विधि</string>\n    <string name=\"internal_storage\">आंतरिक स्टोरेज</string>\n    <string name=\"read_later\">बाद में पढ़े</string>\n    <string name=\"cannot_find_available_storage\">स्टोरेज उपलब्ध नहीं हैं</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%2$d में से %1$d</string>\n    <string name=\"text_feed_holder\">आप जो पढ़ रहे हैं उसके नए अध्याय यहां दिखाए गए हैं</string>\n    <string name=\"favourites_category_empty\">रिक्त श्रेणी</string>\n    <string name=\"manga_save_location\">डाउनलोड फोल्डर</string>\n    <string name=\"updates_feed_cleared\">साफ किया गया</string>\n    <string name=\"update\">अद्यतन</string>\n    <string name=\"feed_will_update_soon\">फीड अद्यतन शीघ्र ही आरंभ होगा</string>\n    <string name=\"app_update_available\">इस ऐप का नया संस्करण उपलब्ध हैं</string>\n    <string name=\"new_version_s\">नया संस्करण: %s</string>\n    <string name=\"text_delete_local_manga\">डिवाइस से \\\"%s\\\" को स्थायी रूप से मिटाएं?</string>\n    <string name=\"text_history_holder_primary\">आप जो पढ़ेंगे वह यहां प्रदर्शित किया जाएगा</string>\n    <string name=\"delete_manga\">मंगा मिटाएं</string>\n    <string name=\"notification_sound\">सूचना की ध्वनि</string>\n    <string name=\"search_history_cleared\">साफ हो गया</string>\n    <string name=\"open_in_browser\">वेब ब्राउज़र में खोलें</string>\n    <string name=\"notifications\">सूचनाएं</string>\n    <string name=\"not_available\">उपल्ब्ध नहीं हैं</string>\n    <string name=\"track_sources\">अद्यतन देखें</string>\n    <string name=\"clear_search_history\">खोज इतिहास साफ करें</string>\n    <string name=\"download\">डाउनलोड</string>\n    <string name=\"size_s\">आकार: %s</string>\n    <string name=\"new_chapters\">नये अध्याय</string>\n    <string name=\"clear_updates_feed\">अद्यतन फीड साफ करें</string>\n    <string name=\"notifications_settings\">सुचना के सेटिंग</string>\n    <string name=\"domain\">डोमेन</string>\n    <string name=\"reader_settings\">पाठक सेटिंग्स</string>\n    <string name=\"text_search_holder_secondary\">क्वेरी को पुन: तैयार करने का प्रयास करें।</string>\n    <string name=\"error\">त्रुटि</string>\n    <string name=\"grid_size\">ग्रिड का आकार</string>\n    <string name=\"_continue\">जारी रखें</string>\n    <string name=\"recent_manga\">हालिया</string>\n    <string name=\"search_on_s\">%s पर खोजें</string>\n    <string name=\"search_results\">खोज परिणाम</string>\n    <string name=\"pages_animation\">पृष्ठ सजीवता</string>\n    <string name=\"other_storage\">अन्य स्टोरेज</string>\n    <string name=\"external_storage\">बाहरी स्टोरेज</string>\n    <string name=\"text_local_holder_secondary\">किसी ऑनलाइन कैटलॉग से कुछ सहेजें या किसी फाइल से आयात करें।</string>\n    <string name=\"text_local_holder_primary\">पहले कुछ सहेजें</string>\n    <string name=\"text_empty_holder_primary\">यहां कुछ खाली सा है…</string>\n    <string name=\"done\">संपन्न</string>\n    <string name=\"dont_check\">जांच मत करें</string>\n    <string name=\"enter_password\">पासवर्ड दर्ज करें</string>\n    <string name=\"advanced\">उन्नत</string>\n    <string name=\"catalog\">कैटलॉग</string>\n    <string name=\"manage_sources\">स्रोत प्रबंधित करें</string>\n    <string name=\"screenshots_policy\">स्क्रीनशॉट नीति</string>\n    <string name=\"screenshots_allow\">अनुमति दें</string>\n    <string name=\"suggestions\">सुझाव</string>\n    <string name=\"suggestions_enable\">सुझाव सक्षम करें</string>\n    <string name=\"suggestions_summary\">अपनी प्राथमिकताओं के आधार पर मंगा का सुझाव दें</string>\n    <string name=\"suggestions_info\">सभी डेटा का विश्लेषण केवल इस डिवाइस पर स्थानीय रूप से किया जाता है और कभी भी कहीं नहीं भेजा जाता है।</string>\n    <string name=\"reset_filter\">फिल्टर रीसेट करें</string>\n    <string name=\"onboard_text\">उन भाषाओं का चयन करें जिन्हें आप मंगा पढ़ना चाहते हैं। आप इसे बाद में सेटिंग में बदल सकते हैं।</string>\n    <string name=\"chapters_empty\">इस मंगा में कोई अध्याय नहीं</string>\n    <string name=\"suggestions_excluded_genres\">शैलियों को छोड़ें</string>\n    <string name=\"removal_completed\">निष्कासन पूर्ण हो गया</string>\n    <string name=\"download_slowdown_summary\">आपके IP पते को ब्लॉक होने से बचाने में मदद करता है</string>\n    <string name=\"local_manga_processing\">सहेजे गए मंगा का प्रसंस्करण</string>\n    <string name=\"chapters_will_removed_background\">अध्याय पृष्ठभूमि में हटा दिए जाएंगे</string>\n    <string name=\"comics_archive\">कॉमिक्स संग्रह</string>\n    <string name=\"webtoon_zoom\">वेबटून जूम</string>\n    <string name=\"repeat_password\">पासवर्ड दोहराएं</string>\n    <string name=\"passwords_mismatch\">बेमेल पासवर्ड</string>\n    <string name=\"app_version\">संस्करण %s</string>\n    <string name=\"check_for_updates\">अद्यतनों के लिए जांचें</string>\n    <string name=\"no_update_available\">कोई अद्यतन उपलब्ध नहीं</string>\n    <string name=\"scale_mode\">स्केल मोड</string>\n    <string name=\"black_dark_theme\">काली</string>\n    <string name=\"black_dark_theme_summary\">AMOLED स्क्रीन पर कम बिजली का उपयोग होता है</string>\n    <string name=\"create_backup\">डेटा बैकअप बनाएं</string>\n    <string name=\"restore_backup\">बैकअप से पुनर्स्थापित करें</string>\n    <string name=\"data_restored\">पुनर्स्थापित किया गया</string>\n    <string name=\"data_restored_with_errors\">डेटा पुनर्स्थापित कर दिया गया था, लेकिन त्रुटियां हैं</string>\n    <string name=\"just_now\">बस अभी</string>\n    <string name=\"yesterday\">कल</string>\n    <string name=\"long_ago\">बहुत पहले</string>\n    <string name=\"group\">समूह</string>\n    <string name=\"today\">आज</string>\n    <string name=\"tap_to_try_again\">दोबारा प्रयास करने के लिए टैप करें</string>\n    <string name=\"reader_mode_hint\">इस मंगा के लिए चुना गया विन्यास याद रखा जाएगा</string>\n    <string name=\"silent\">खामोश</string>\n    <string name=\"captcha_required\">CAPTCHA आवश्यक है</string>\n    <string name=\"captcha_solve\">हल करें</string>\n    <string name=\"cookies_cleared\">सभी कुकीज़ हटा दी गईं</string>\n    <string name=\"backup_saved\">बैकअप सहेजा गया</string>\n    <string name=\"welcome\">स्वागत है</string>\n    <string name=\"tracker_warning\">कुछ डिवाइसों में अलग-अलग सिस्टम व्यवहार होता है, जो पृष्ठभूमि कार्यों को बाधित कर सकता है।</string>\n    <string name=\"read_more\">अधिक पढ़ें</string>\n    <string name=\"chapter_is_missing\">अध्याय गायब है</string>\n    <string name=\"about_app_translation_summary\">इस ऐप का अनुवाद करें</string>\n    <string name=\"auth_complete\">अधिकृत</string>\n    <string name=\"auth_not_supported_by\">%s पर लॉगिन करना समर्थित नहीं है</string>\n    <string name=\"text_clear_cookies_prompt\">आप सभी स्रोतों से लॉग आउट हो जायेंगे</string>\n    <string name=\"state_finished\">समाप्त</string>\n    <string name=\"state_ongoing\">चल रही है</string>\n    <string name=\"system_default\">तयशुदा</string>\n    <string name=\"exclude_nsfw_from_history\">इतिहास से NSFW मंगा को बाहर करें</string>\n    <string name=\"show_pages_numbers\">क्रमांकित पन्ने</string>\n    <string name=\"screenshots_block_nsfw\">NSFW पर रोक लगाएं</string>\n    <string name=\"screenshots_block_all\">हमेशा ब्लॉक करें</string>\n    <string name=\"text_suggestion_holder\">मंगा पढ़ना शुरू करें और आपको व्यक्तिगत सुझाव मिलेंगे</string>\n    <string name=\"exclude_nsfw_from_suggestions\">NSFW मंगा का सुझाव न दें</string>\n    <string name=\"disabled\">अक्षम</string>\n    <string name=\"never\">कभी नहीं</string>\n    <string name=\"only_using_wifi\">केवल Wi-Fi पर</string>\n    <string name=\"always\">हमेशा</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">विभिन्न भाषाएं</string>\n    <string name=\"search_chapters\">अध्याय खोजें</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">दिखावट</string>\n    <string name=\"preload_pages\">पन्ने पहले से लोड करें</string>\n    <string name=\"edit\">संपादित करें</string>\n    <string name=\"download_slowdown\">धीमी गति से डाउनलोड करें</string>\n    <string name=\"edit_category\">श्रेणी संपादित करें</string>\n    <string name=\"tracking\">ट्रैकिंग</string>\n    <string name=\"empty_favourite_categories\">कोई पसंदीदा श्रेणियां नहीं</string>\n    <string name=\"logout\">लॉग आउट</string>\n    <string name=\"bookmark_add\">बुकमार्क जोड़ें</string>\n    <string name=\"bookmark_remove\">बुकमार्क हटाएं</string>\n    <string name=\"bookmarks\">बुकमार्क</string>\n    <string name=\"bookmark_removed\">बुकमार्क हटा दिया गया</string>\n    <string name=\"bookmark_added\">बुकमार्क जोड़ा गया</string>\n    <string name=\"undo\">पूर्ववत करें</string>\n    <string name=\"removed_from_history\">इतिहास से हटा दिया गया</string>\n    <string name=\"detect_reader_mode\">पाठक मोड का स्वत: पता लगाएं</string>\n    <string name=\"detect_reader_mode_summary\">स्वचालित रूप से पता लगाएं कि मंगा वेबटून है या नहीं</string>\n    <string name=\"disable_battery_optimization\">बैटरी अनुकूलन अक्षम करें</string>\n    <string name=\"send\">भेजें</string>\n    <string name=\"disable_all\">सब अक्षम करें</string>\n    <string name=\"use_fingerprint\">यदि उपलब्ध हो तो बायोमेट्रिक का उपयोग करें</string>\n    <string name=\"appwidget_shelf_description\">आपके पसंदीदा में से मांगा</string>\n    <string name=\"appwidget_recent_description\">आपके हालिया पढ़ें मंगा</string>\n    <string name=\"report\">रिपोर्ट</string>\n    <string name=\"status_planned\">योजना बनाई</string>\n    <string name=\"status_reading\">पढ़ रहा हूँ</string>\n    <string name=\"status_re_reading\">दोबारा पढ़ना</string>\n    <string name=\"status_completed\">पूरा किया हुआ</string>\n    <string name=\"status_on_hold\">होल्ड पर</string>\n    <string name=\"status_dropped\">गिरा दिया गया</string>\n    <string name=\"show_reading_indicators\">पठन प्रगति संकेतक दिखाएं</string>\n    <string name=\"data_deletion\">डेटा विलोपन</string>\n    <string name=\"exclude_nsfw_from_history_summary\">NSFW के रूप में चिह्नित मंगा को इतिहास में कभी नहीं जोड़ा जाएगा और आपकी प्रगति सहेजी नहीं जाएगी</string>\n    <string name=\"clear_cookies_summary\">कुछ समस्या होने पर मदद मिल सकती है। सभी प्राधिकरण अमान्य कर दिए जाएंगे</string>\n    <string name=\"invalid_domain_message\">अमान्य डोमेन</string>\n    <string name=\"select_range\">दायरा चुनें</string>\n    <string name=\"clear_all_history\">सारा इतिहास साफ करें</string>\n    <string name=\"last_2_hours\">पिछले 2 घंटे</string>\n    <string name=\"history_cleared\">इतिहास साफ हो गया</string>\n    <string name=\"manage\">प्रबंधित करें</string>\n    <string name=\"explore\">अन्वेषण करें</string>\n    <string name=\"confirm_exit\">बाहर निकलने के लिए फिर से वापस दबाएं</string>\n    <string name=\"exit_confirmation\">बाहर निकलने की पुष्टि</string>\n    <string name=\"saved_manga\">सहेजा गया मंगा</string>\n    <string name=\"pages_cache\">पन्नो का कैशे</string>\n    <string name=\"other_cache\">अन्य कैशे</string>\n    <string name=\"available\">उपलब्ध</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"removed_from_favourites\">पसंदीदा से हटाया गया</string>\n    <string name=\"options\">विकल्प</string>\n    <string name=\"not_found_404\">सामग्री नहीं मिली या हटाई गई</string>\n    <string name=\"incognito_mode\">गुप्त मोड</string>\n    <string name=\"reader_info_pattern\">अध्. %1$d/%2$d पृ. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">पाठक में सूचना पट्टी दिखाएं</string>\n    <string name=\"folder_with_images\">छवियों वाला फोल्डर</string>\n    <string name=\"import_completed\">आयात पूरा हुआ</string>\n    <string name=\"history_shortcuts_summary\">एप्लिकेशन आइकन पर लंबे समय तक दबाकर हालिया मंगा को उपलब्ध कराएं</string>\n    <string name=\"reader_control_ltr\">सुविधापूर्ण पाठक नियंत्रण</string>\n    <string name=\"color_correction\">रंग सुधार</string>\n    <string name=\"brightness\">चमक</string>\n    <string name=\"storage_usage\">स्टोरेज उपयोग</string>\n    <string name=\"contrast\">वैषम्य</string>\n    <string name=\"reset\">रीसेट करें</string>\n    <string name=\"text_unsaved_changes_prompt\">असहेजे परिवर्तन सहेजें या त्यागें?</string>\n    <string name=\"error_no_space_left\">डिवाइस पर जगह समाप्त</string>\n    <string name=\"reader_slider\">पृष्ठ स्विचिंग स्लाइडर दिखाएं</string>\n    <string name=\"network_unavailable\">नेटवर्क उपलब्ध नहीं है</string>\n    <string name=\"server_error\">सर्वर साइड त्रुटि (%1$d)। कृपया बाद में पुन: प्रयास करें</string>\n    <string name=\"clear_new_chapters_counters\">नए अध्यायों के बारे में भी स्पष्ट जानकारी</string>\n    <string name=\"compact\">सघन</string>\n    <string name=\"mark_as_current\">वर्तमान के रूप में चिह्नित करें</string>\n    <string name=\"language\">भाषा</string>\n    <string name=\"enable_logging\">लॉगिंग सक्षम करें</string>\n    <string name=\"show_suspicious_content\">संदिग्ध सामग्री दिखाएं</string>\n    <string name=\"theme_name_dynamic\">गतिशील</string>\n    <string name=\"color_theme\">रंग योजना</string>\n    <string name=\"show_in_grid_view\">ग्रिड दृश्य में दिखाएं</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"scrobbling_empty_hint\">पढ़ने की प्रगति को ट्रैक करने के लिए, मंगा विवरण स्क्रीन पर मेनू → ट्रैक का चयन करें।</string>\n    <string name=\"services\">सेवाएं</string>\n    <string name=\"settings_apply_restart_required\">कृपया इन परिवर्तनों को लागू करने के लिए एप्लिकेशन को पुनः आरंभ करें</string>\n    <string name=\"comics_archive_import_description\">आप एक या अधिक .cbz या .zip फाइलों का चयन कर सकते हैं, प्रत्येक फाइल को एक अलग मंगा के रूप में पहचाना जाएगा।</string>\n    <string name=\"user_agent\">UserAgent शीर्षलेख</string>\n    <string name=\"speed\">गति</string>\n    <string name=\"show_on_shelf\">दराज पर दिखाएं</string>\n    <string name=\"sync_auth_hint\">आप किसी मौजूदा खाते में साइन इन कर सकते हैं या एक नया खाता बना सकते हैं</string>\n    <string name=\"mirror_switching_summary\">यदि मिरर उपलब्ध हैं तो त्रुटियों पर मंगा स्रोतों के लिए स्वचालित रूप से डोमेन स्विच करें</string>\n    <string name=\"find_similar\">समान खोजें</string>\n    <string name=\"pause\">विराम</string>\n    <string name=\"resume\">पुनः आरम्भ</string>\n    <string name=\"paused\">विरामित</string>\n    <string name=\"cancel_all\">सभी रद्द करें</string>\n    <string name=\"mirror_switching\">स्वचालित रूप से मिरर चुनें</string>\n    <string name=\"downloads_wifi_only_summary\">मोबाइल नेटवर्क पर स्विच करते समय डाउनलोड करना बंद कर दें</string>\n    <string name=\"suggestion_manga\">सुझाव: %s</string>\n    <string name=\"suggestions_notifications_summary\">कभी-कभी सुझाए गए मंगा के साथ सूचनाएं दिखाएं</string>\n    <string name=\"more\">अधिक</string>\n    <string name=\"enable\">सक्षम</string>\n    <string name=\"no_thanks\">जी नहीं, धन्यवाद</string>\n    <string name=\"cancel_all_downloads_confirm\">सभी सक्रिय डाउनलोड रद्द कर दिए जाएंगे, आंशिक रूप से डाउनलोड किया गया डेटा खो जाएगा</string>\n    <string name=\"remove_completed_downloads_confirm\">आपका डाउनलोड इतिहास स्थायी रूप से हटा दिया जाएगा। कोई भी डाउनलोड की गई फ़ाइल प्रभावित नहीं होगी।</string>\n    <string name=\"text_downloads_list_holder\">आपके पास कोई डाउनलोड नहीं है</string>\n    <string name=\"downloads_resumed\">डाउनलोड फिर से शुरू कर दिए गए हैं</string>\n    <string name=\"downloads_paused\">डाउनलोड रोक दिए गए हैं</string>\n    <string name=\"downloads_removed\">डाउनलोड हटा दिए गए हैं</string>\n    <string name=\"suggestions_enable_prompt\">क्या आप वैयक्तिकृत मंगा सुझाव प्राप्त करना चाहते हैं?</string>\n    <string name=\"web_view_unavailable\">WebView उपलब्ध नहीं है: जांचें कि WebView प्रदाता स्थापित है या नहीं</string>\n    <string name=\"clear_network_cache\">नेटवर्क कैशे साफ करें</string>\n    <string name=\"type\">प्रकार</string>\n    <string name=\"address\">पता</string>\n    <string name=\"port\">पोर्ट</string>\n    <string name=\"proxy\">प्रॉक्सी</string>\n    <string name=\"invalid_value_message\">अमान्य मान</string>\n    <string name=\"password\">पासवर्ड</string>\n    <string name=\"invert_colors\">रंगों को पलटें</string>\n    <string name=\"invalid_port_number\">अमान्य पोर्ट नंबर</string>\n    <string name=\"network\">नेटवर्क</string>\n    <string name=\"show_pages_numbers_summary\">निचले कोने में पृष्ठ संख्याएं दिखाएं</string>\n    <string name=\"restore_summary\">पहले बनाए गए बैकअप को पुनर्स्थापित करें</string>\n    <string name=\"webtoon_zoom_summary\">वेबटून मोड में जूम इन जेस्चर की अनुमति दें</string>\n    <string name=\"reader_info_bar_summary\">स्क्रीन के शीर्ष पर वर्तमान समय और पढ़ने की प्रगति दिखाएं</string>\n    <string name=\"volume_\">वॉल्यूम %d</string>\n    <string name=\"volume_unknown\">अज्ञात वॉल्यूम</string>\n    <string name=\"downloads_settings_info\">यदि आपको सर्वर-साइड ब्लॉकिंग की समस्या हो रही है तो आप स्रोत सेटिंग्स में प्रत्येक मंगा स्रोत के लिए व्यक्तिगत रूप से डाउनलोड मंदी को सक्षम कर सकते हैं</string>\n    <string name=\"remove_from_history\">इतिहास से हटाएं</string>\n    <string name=\"skip\">छोड़ें</string>\n    <string name=\"incognito_mode_hint\">आपकी पढ़ने की प्रगति सहेजी नहीं जाएगी</string>\n    <string name=\"content_rating\">सामग्री मूल्यांकन</string>\n    <string name=\"genres_exclude\">शैलियों को छोड़ें</string>\n    <string name=\"rating_safe\">सुरक्षित</string>\n    <string name=\"rating_adult\">वयस्क</string>\n    <string name=\"rating_suggestive\">सुझावात्मक</string>\n    <string name=\"last_read\">अंतिम पढ़ा</string>\n    <string name=\"lock_screen_rotation\">लॉक स्क्रीन रोटेशन</string>\n    <string name=\"vertical\">लंबवत</string>\n    <string name=\"download_started\">डाउनलोड प्रारंभ हुआ</string>\n    <string name=\"manga_list\">मंगा सूची</string>\n    <string name=\"disable_nsfw\">NSFW अक्षम करें</string>\n    <string name=\"images_proxy_title\">छवियां अनुकूलन प्रॉक्सी</string>\n    <string name=\"data_and_privacy\">डेटा और गोपनीयता</string>\n    <string name=\"email_password_enter_hint\">जारी रखने के लिए अपना ईमेल और पासवर्ड डालें</string>\n    <string name=\"clear_source_cookies_summary\">केवल निर्दिष्ट डोमेन के लिए कुकीज़ साफ करें। अधिकांश मामलों में प्राधिकरण अमान्य हो जाएगा</string>\n    <string name=\"download_option_next_unread_n_chapters\">अगला अपठित %s</string>\n    <string name=\"no_access_to_file\">आपके पास इस फाइल या निर्देशिका तक कोई पहुंच नहीं है</string>\n    <string name=\"voice_search\">ध्वनि खोज</string>\n    <string name=\"related_manga\">संबंधित मंगा</string>\n    <string name=\"description\">विवरण</string>\n    <string name=\"this_month\">इस महीने</string>\n    <string name=\"background\">पृष्ठभूमि</string>\n    <string name=\"local_manga_directories\">स्थानीय मंगा निर्देशिकाएं</string>\n    <string name=\"data_not_restored_text\">सुनिश्चित करें कि आपने सही बैकअप फाइल का चयन किया है</string>\n    <string name=\"data_not_restored\">डेटा पुनर्स्थापित नहीं किया गया</string>\n    <string name=\"suggestions_wifi_only_summary\">मीटर्ड नेटवर्क कनेक्शन का उपयोग करके सुझावों को अद्यतन न करें</string>\n    <string name=\"tracker_wifi_only_summary\">मीटर्ड नेटवर्क कनेक्शन का उपयोग करके नए अध्यायों की जांच न करें</string>\n    <string name=\"search_hint\">मंगा शीर्षक, शैली या स्रोत का नाम दर्ज करें</string>\n    <string name=\"progress\">प्रगति</string>\n    <string name=\"order_added\">जोड़ा गया</string>\n    <string name=\"show\">दिखाएं</string>\n    <string name=\"languages\">भाषाएं</string>\n    <string name=\"unknown\">अज्ञात</string>\n    <string name=\"in_progress\">प्रगति पर है</string>\n    <string name=\"error_corrupted_file\">अमान्य डेटा लौटाया गया है या फाइल दूषित है</string>\n    <string name=\"items_limit_exceeded\">कोई और वस्तुएं नहीं जोड़ा जा सकता</string>\n    <string name=\"on_device\">डिवाइस पर</string>\n    <string name=\"main_screen_sections\">मुख्य स्क्रीन अनुभाग</string>\n    <string name=\"directories\">निर्देशिकाएं</string>\n    <string name=\"to_top\">शीर्ष पर</string>\n    <string name=\"moved_to_top\">शीर्ष पर ले जाया गया</string>\n    <string name=\"zoom_out\">जूम आउट</string>\n    <string name=\"zoom_in\">जूम इन</string>\n    <string name=\"reader_zoom_buttons\">जूम बटन दिखाएं</string>\n    <string name=\"reader_zoom_buttons_summary\">निचले दाएं कोने में जूम नियंत्रण बटन दिखाना है या नहीं</string>\n    <string name=\"keep_screen_on\">स्क्रीन चालू रखें</string>\n    <string name=\"keep_screen_on_summary\">जब आप मंगा पढ़ रहे हों तो स्क्रीन बंद न करें</string>\n    <string name=\"enhanced_colors_summary\">बैंडिंग को कम करता है, लेकिन प्रदर्शन को प्रभावित कर सकता है</string>\n    <string name=\"enhanced_colors\">32-बिट रंग मोड</string>\n    <string name=\"suggest_new_sources\">ऐप अद्यतन के बाद नए स्रोत सुझाएं</string>\n    <string name=\"suggest_new_sources_summary\">एप्लिकेशन को अपडेट करने के बाद नए जोड़े गए स्रोतों को सक्षम करने का संकेत दें</string>\n    <string name=\"online_variant\">ऑनलाइन संस्करण</string>\n    <string name=\"periodic_backups\">आवधिक बैकअप</string>\n    <string name=\"backup_frequency\">बैकअप निर्माण आवृत्ति</string>\n    <string name=\"frequency_every_day\">प्रतिदिन</string>\n    <string name=\"frequency_once_per_week\">हर हफ्ते एक बार</string>\n    <string name=\"frequency_twice_per_month\">प्रति माह दो बार</string>\n    <string name=\"frequency_once_per_month\">प्रति महीना एक बार</string>\n    <string name=\"frequency_every_2_days\">हर 2 दिन में</string>\n    <string name=\"periodic_backups_enable\">आवधिक बैकअप सक्षम करें</string>\n    <string name=\"backups_output_directory\">बैकअप आउटपुट डायरेक्टरी</string>\n    <string name=\"last_successful_backup\">अंतिम सफल बैकअप: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"content_type_manga\">मंगा</string>\n    <string name=\"content_type_hentai\">हेंताई</string>\n    <string name=\"content_type_comics\">कॉमिक्स</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_other\">अन्य</string>\n    <string name=\"source_enabled\">स्रोत सक्षम</string>\n    <string name=\"sources_catalog\">स्रोत कैटलॉग</string>\n    <string name=\"no_manga_sources_catalog_text\">इस अनुभाग में कोई स्रोत उपलब्ध नहीं है, या यह सब पहले ही जोड़ा जा चुका होगा।\n\\nहमारे साथ बने रहें</string>\n    <string name=\"no_manga_sources_found\">आपकी क्वेरी से कोई उपलब्ध मंगा स्रोत नहीं मिला</string>\n    <string name=\"manual\">हस्तचालित</string>\n    <string name=\"available_d\">उपलब्ध: %1$d</string>\n    <string name=\"disable_nsfw_summary\">यदि संभव हो तो NSFW स्रोतों को अक्षम करें और वयस्क मंगा को सूची से छिपाएं</string>\n    <string name=\"state_paused\">विरामित</string>\n    <string name=\"reader_optimize\">मेमोरी खपत कम करें (बीटा)</string>\n    <string name=\"state\">अवस्था</string>\n    <string name=\"error_multiple_genres_not_supported\">अनेक शैलियों द्वारा फिल्टर करना इस मंगा स्रोत द्वारा समर्थित नहीं है</string>\n    <string name=\"error_search_not_supported\">खोज इस मंगा स्रोत द्वारा समर्थित नहीं है</string>\n    <string name=\"genres_search_hint\">शैली का नाम लिखना प्रारंभ करें</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">यदि आपको इसमें कोई समस्या है तो डाउनलोड शुरू करने में मदद मिल सकती है</string>\n    <string name=\"restore\">पुनर्स्थापित करें</string>\n    <string name=\"backup_date_\">बैकअप दिनांक: %s</string>\n    <string name=\"state_upcoming\">आगामी</string>\n    <string name=\"by_name_reverse\">नाम उलट दिया गया</string>\n    <string name=\"mark_as_completed\">पूर्ण चिह्नित करें</string>\n    <string name=\"mark_as_completed_prompt\">चयनित मंगा को पूरी तरह से पढ़ा गया के रूप में चिह्नित करें?\n\\n\n\\nचेतावनी: वर्तमान पठन प्रगति नष्ट हो जाएगी।</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"show_menu\">मेनू दिखाएं</string>\n    <string name=\"long_tap_action\">लंबे टैप पर कार्रवाई</string>\n    <string name=\"tap_action\">टैप कार्रवाई</string>\n    <string name=\"none\">कोई नहीं</string>\n    <string name=\"config_reset_confirm\">सेटिंग्स को तयशुदा मानों पर रीसेट करें? इस कार्रवाई को वापस नहीं किया जा सकता।</string>\n    <string name=\"use_two_pages_landscape\">परिदृश्य उन्मुखीकरण पर दो पृष्ठ अभिन्यास का उपयोग करें (बीटा)</string>\n    <string name=\"got_it\">समझ गया</string>\n    <string name=\"default_tab\">तयशुदा टैब</string>\n    <string name=\"download_option_all_chapters\">अनुवाद सहित सभी अध्याय %s</string>\n    <string name=\"download_option_whole_manga\">संपूर्ण मंगा</string>\n    <string name=\"download_option_first_n_chapters\">प्रथम %s</string>\n    <string name=\"download_option_all_unread\">सभी अपठित अध्याय</string>\n    <string name=\"download_option_all_unread_b\">सभी अपठित अध्याय (%s)</string>\n    <string name=\"download_option_manual_selection\">अध्यायों का चयन हस्तचालित रूप से करें</string>\n    <string name=\"color_light\">हल्का</string>\n    <string name=\"color_dark\">गहरा</string>\n    <string name=\"color_white\">सफेद</string>\n    <string name=\"color_black\">काला</string>\n    <string name=\"manage_categories\">श्रेणी व्यवस्थित करें</string>\n    <string name=\"downloaded\">डाउनलोड किया गया</string>\n    <string name=\"too_many_requests_message\">बहुत सारे अनुरोध। बाद में पुन: प्रयास</string>\n    <string name=\"related_manga_summary\">संबंधित मंगा की एक सूची दिखाएं। कुछ मामलों में यह गलत या गायब हो सकता है</string>\n    <string name=\"pick_custom_directory\">तदनुकूल डायरेक्टरी चुनें</string>\n    <string name=\"default_webtoon_zoom_out\">तयशुदा वेबटून जूम आउट</string>\n    <string name=\"captcha_required_summary\">%s को ठीक से काम करने के लिए कैप्चा को हल करने की आवश्यकता है</string>\n    <string name=\"fullscreen_mode\">पूर्णस्क्रीन मोड</string>\n    <string name=\"reader_fullscreen_summary\">सिस्टम स्थिति और नेविगेशन बार छिपाएं</string>\n    <string name=\"username\">उपयोक्तानाम</string>\n    <string name=\"authorization_optional\">प्राधिकरण (वैकल्पिक)</string>\n    <string name=\"category_hidden_done\">यह श्रेणी मुख्य स्क्रीन से छिपी हुई थी और मेनू → श्रेणियों को प्रबंधित करें के माध्यम से पहुंच योग्य है</string>\n    <string name=\"globally\">वैश्विक स्तर पर</string>\n    <string name=\"grayscale\">ग्रेस्केल</string>\n    <string name=\"apply\">लागू करें</string>\n    <string name=\"ignore_ssl_errors\">SSL त्रुटियों को नजरअंदाज करें</string>\n    <string name=\"downloads_wifi_only\">केवल Wi-Fi के ज़रिए डाउनलोड करें</string>\n    <string name=\"show_notification_new_chapters_off\">आपको सूचनाएं प्राप्त नहीं होंगी लेकिन नए अध्याय सूचियों में चिन्हांकित किए जाएंगे</string>\n    <string name=\"notifications_enable\">सूचनाएं सक्षम करें</string>\n    <string name=\"name\">नाम</string>\n    <string name=\"bookmarks_removed\">बुकमार्क हटा दिए गए</string>\n    <string name=\"no_manga_sources\">कोई मंगा स्रोत नहीं</string>\n    <string name=\"no_manga_sources_text\">मंगा को ऑनलाइन पढ़ने के लिए मंगा स्रोतों को सक्षम करें</string>\n    <string name=\"random\">यादृच्छिक</string>\n    <string name=\"reorder\">पुनः क्रमित करें</string>\n    <string name=\"empty\">खाली</string>\n    <string name=\"import_will_start_soon\">आयात जल्द शुरू होगा</string>\n    <string name=\"feed\">फीड</string>\n    <string name=\"history_shortcuts\">हाल के मंगा शॉर्टकट दिखाएं</string>\n    <string name=\"discard\">त्यागें</string>\n    <string name=\"sources_reorder_tip\">किसी वस्तु को पुन: व्यवस्थित करने के लिए उस पर टैप करके रखें</string>\n    <string name=\"wrong_password\">गलत पासवर्ड</string>\n    <string name=\"protect_application\">ऐप को सुरक्षित रखें</string>\n    <string name=\"protect_application_summary\">Kotatsu शुरू करते समय पासवर्ड मांगें</string>\n    <string name=\"right_to_left\">दाएं-से-बाएं</string>\n    <string name=\"create_category\">नई श्रेणी</string>\n    <string name=\"zoom_mode_fit_center\">केंद्र के अनुरूप</string>\n    <string name=\"zoom_mode_fit_height\">ऊंचाई के अनुरूप</string>\n    <string name=\"zoom_mode_fit_width\">चौड़ाई के अनुरूप</string>\n    <string name=\"zoom_mode_keep_start\">प्रारंभ में रखें</string>\n    <string name=\"clear_cookies\">कुकीज़ साफ करें</string>\n    <string name=\"clear_feed\">फीड साफ करें</string>\n    <string name=\"text_clear_updates_feed_prompt\">सभी अद्यतन इतिहास स्थायी रूप से साफ करें?</string>\n    <string name=\"check_for_new_chapters\">नए अध्यायों की जांच करें</string>\n    <string name=\"reverse\">उलटा</string>\n    <string name=\"sign_in\">साइन इन</string>\n    <string name=\"canceled\">रद्द किया गया</string>\n    <string name=\"account_already_exists\">खाता पहले से मौजूद है</string>\n    <string name=\"back\">पीछे</string>\n    <string name=\"sync\">समन्वयन</string>\n    <string name=\"sync_title\">अपना डेटा समन्वयित करें</string>\n    <string name=\"email_enter_hint\">जारी रखने के लिए अपना ईमेल दर्ज करें</string>\n    <string name=\"hide\">छुपाएं</string>\n    <string name=\"new_sources_text\">नए मंगा स्रोत उपलब्ध हैं</string>\n    <string name=\"check_new_chapters_title\">नए अध्यायों की जांच करें और इसके बारे में सूचित करें</string>\n    <string name=\"remove_completed\">पूर्ण हटा दें</string>\n    <string name=\"toggle_ui\">UI दिखाएं/छिपाएं</string>\n    <string name=\"next_chapter\">अगला अध्याय</string>\n    <string name=\"reader_actions\">पाठक कार्रवाई</string>\n    <string name=\"switch_pages_volume_buttons\">वॉल्यूम बटन सक्षम करें</string>\n    <string name=\"next_page\">अगला पृष्ठ</string>\n    <string name=\"reading_time_estimation\">पढ़ने का अनुमानित समय दिखाएं</string>\n    <string name=\"reading_time_estimation_summary\">समय अनुमान मान गलत हो सकता है</string>\n    <string name=\"location\">स्थान</string>\n    <string name=\"queued\">कतारबद्ध</string>\n    <string name=\"about_app_translation\">अनुवाद</string>\n    <string name=\"enabled\">सक्षम</string>\n    <string name=\"auth_required\">इस सामग्री को देखने के लिए साइन इन करें</string>\n    <string name=\"default_s\">तयशुदा: %s</string>\n    <string name=\"next\">अगला</string>\n    <string name=\"genres\">शैलियां</string>\n    <string name=\"logged_in_as\">%s के रूप में लॉगिन किया गया</string>\n    <string name=\"protect_application_subtitle\">ऐप शुरू करने के लिए पासवर्ड दर्ज करें</string>\n    <string name=\"suggestions_updating\">सुझाव अपडेट हो रहे हैं</string>\n    <string name=\"confirm\">पुष्टि करें</string>\n    <string name=\"suggestions_excluded_genres_summary\">वे शैलियां निर्दिष्ट करें जिन्हें आप सुझावों में नहीं देखना चाहते</string>\n    <string name=\"password_length_hint\">पासवर्ड 4 वर्णों या अधिक का होना चाहिए</string>\n    <string name=\"text_delete_local_manga_batch\">डिवाइस से चयनित वस्तुएं स्थायी रूप से मिटाएं?</string>\n    <string name=\"text_clear_search_history_prompt\">हाल की सभी खोज क्वेरी को स्थायी रूप से हटा दें?</string>\n    <string name=\"about\">ऐप के बारे में</string>\n    <string name=\"backup_restore\">बैकअप और पुनर्स्थापना</string>\n    <string name=\"preparing_\">तैयार कर रहे हैं…</string>\n    <string name=\"file_not_found\">फाइल नहीं मिली</string>\n    <string name=\"data_restored_success\">सारा डेटा पुनर्स्थापित कर दिया गया</string>\n    <string name=\"backup_information\">आप अपने इतिहास और पसंदीदा का बैकअप बना सकते हैं और उसे पुनर्स्थापित कर सकते हैं</string>\n    <string name=\"show_notification_new_chapters_on\">आप जो मंगा पढ़ रहे हैं उसके अद्यतन के बारे में आपको सूचनाएं प्राप्त होंगी</string>\n    <string name=\"dns_over_https\">HTTPS पर DNS</string>\n    <string name=\"default_mode\">तयशुदा मोड</string>\n    <string name=\"disable_battery_optimization_summary\">बैकग्राउंड अद्यतन जांच में मदद करता है</string>\n    <string name=\"crash_text\">कुछ गलत हो गया। कृपया इसे ठीक करने में हमारी सहायता के लिए डेवलपर्स को एक बग रिपोर्ट सबमिट करें।</string>\n    <string name=\"show_reading_indicators_summary\">इतिहास और पसंदीदा में पढ़ा गया प्रतिशत दिखाएं</string>\n    <string name=\"show_all\">सब दिखाएं</string>\n    <string name=\"downloads_cancelled\">डाउनलोड रद्द कर दिए गए हैं</string>\n    <string name=\"no_bookmarks_yet\">अभी तक कोई बुकमार्क नहीं</string>\n    <string name=\"no_bookmarks_summary\">आप मंगा पढ़ते समय बुकमार्क बना सकते हैं</string>\n    <string name=\"sync_settings\">समन्वयन सेटिंग्स</string>\n    <string name=\"exit_confirmation_summary\">ऐप से बाहर निकलने के लिए \\\"पीछे\\\" को दो बार दबाएं</string>\n    <string name=\"server_address\">सर्वर पता</string>\n    <string name=\"sync_host_description\">आप स्वयं-होस्ट किये गये समन्वयन सर्वर या तयशुदा सर्वर का उपयोग कर सकते हैं। यदि आप निश्चित नहीं हैं कि आप क्या कर रहे हैं तो इसे न बदलें।</string>\n    <string name=\"no_chapters\">कोई अध्याय नहीं</string>\n    <string name=\"automatic_scroll\">स्वचालित स्क्रॉल</string>\n    <string name=\"importing_manga\">मंगा आयात किया जा रहा है</string>\n    <string name=\"import_completed_hint\">स्थान बचाने के लिए आप मूल फाइल को स्टोरेज से हटा सकते हैं</string>\n    <string name=\"network_unavailable_hint\">मंगा को ऑनलाइन पढ़ने के लिए Wi-Fi या मोबाइल नेटवर्क चालू करें</string>\n    <string name=\"source_disabled\">स्रोत अक्षम किया गया</string>\n    <string name=\"prefetch_content\">सामग्री प्रीलोड हो रही है</string>\n    <string name=\"share_logs\">लॉग साझा करें</string>\n    <string name=\"enable_logging_summary\">डिबग उद्देश्यों के लिए कुछ क्रियाएं रिकॉर्ड करें। यदि आप निश्चित नहीं हैं कि आप क्या कर रहे हैं तो इसे चालू न करें</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"allow_unstable_updates\">अस्थिर अद्यतन की अनुमति दें</string>\n    <string name=\"nothing_here\">यहां कुछ नहीं है</string>\n    <string name=\"allow_unstable_updates_summary\">अस्थिर बिल्ड के बारे में सूचनाएं प्राप्त करें</string>\n    <string name=\"categories_delete_confirm\">क्या आप वाकई चयनित पसंदीदा श्रेणियां मिटाना चाहते हैं? \\nइसमें मौजूद सारा मंगा नष्ट हो जाएगा और इसे पूर्ववत नहीं किया जा सकता।</string>\n    <string name=\"manga_error_description_pattern\">त्रुटि विवरण:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1। यह सुनिश्चित करने के लिए कि यह अपने स्रोत पर उपलब्ध है &lt;a href=%2$s&gt;मंगा को वेब ब्राउज़र में खोलने का प्रयास करें&lt;/a&gt;&lt;br&gt;2। सुनिश्चित करें कि आप &lt;a href=kotatsu://about&gt;Kotatsu के नवीनतम संस्करण&lt;/a&gt;&lt;br&gt;3 का उपयोग कर रहे हैं। यदि यह उपलब्ध है, तो डेवलपर्स को एक त्रुटि रिपोर्ट भेजें।</string>\n    <string name=\"folder_with_images_import_description\">आप अभिलेखों या छवियों वाली एक निर्देशिका का चयन कर सकते हैं। प्रत्येक संग्रह (या उपनिर्देशिका) को एक अध्याय के रूप में पहचाना जाएगा।</string>\n    <string name=\"images_procy_description\">यदि संभव हो तो ट्रैफिक उपयोग को कम करने और छवि लोडिंग को तेज़ करने के लिए wsrv.nl सेवा का उपयोग करें</string>\n    <string name=\"state_abandoned\">गिरा दिया गया</string>\n    <string name=\"list_options\">विकल्पों की सूची बनाएं</string>\n    <string name=\"by_relevance\">प्रासंगिकता</string>\n    <string name=\"categories\">श्रेणियां</string>\n    <string name=\"reader_optimize_summary\">कम मेमोरी का उपयोग करने के लिए छुपे पन्नो की गुणवत्ता कम करें</string>\n    <string name=\"error_multiple_states_not_supported\">एकाधिक राज्यों द्वारा फिल्टर करना इस मंगा स्रोत द्वारा समर्थित नहीं है</string>\n    <string name=\"color_correction_apply_text\">ये सेटिंग्स विश्व स्तर पर या केवल वर्तमान मंगा पर लागू की जा सकती हैं। यदि विश्व स्तर पर लागू किया जाता है, तो व्यक्तिगत सेटिंग्स को अध्यारोहण नहीं किया जाएगा।</string>\n    <string name=\"this_manga\">यह मंगा</string>\n    <string name=\"error_filter_locale_genre_not_supported\">शैलियों और स्थान दोनों के आधार पर फिल्टर करना इस स्रोत द्वारा समर्थित नहीं है</string>\n    <string name=\"welcome_text\">कृपया चुनें कि आप कौन से सामग्री स्रोत सक्षम करना चाहते हैं। इसे बाद में सेटिंग्स में भी विन्यस्त किया जा सकता है</string>\n    <string name=\"sync_auth\">खाता समन्वयन करने के लिए लॉगिन करें</string>\n    <string name=\"error_filter_states_genre_not_supported\">शैलियों और राज्यों दोनों द्वारा फिल्टर करना इस स्रोत द्वारा समर्थित नहीं है</string>\n    <string name=\"prev_chapter\">पिछला अध्याय</string>\n    <string name=\"default_page_save_dir\">तयशुदा पृष्ठ सहेजने की डायरेक्टरी</string>\n    <string name=\"reader_actions_summary\">टैप करने योग्य स्क्रीन क्षेत्रों के लिए कार्रवाइयां विन्यस्त करें</string>\n    <string name=\"reader_control_ltr_summary\">दाएं किनारे पर टैप करने या दाएं कुंजी दबाने से हमेशा अगले पृष्ठ पर स्विच हो जाता है।</string>\n    <string name=\"prev_page\">पिछला पृष्ठ</string>\n    <string name=\"switch_pages_volume_buttons_summary\">पन्ने बदलने के लिए वॉल्यूम बटन का उपयोग करें</string>\n    <string name=\"suggestions_unavailable_text\">सुझाव सुविधा अक्षम है</string>\n    <string name=\"check_for_new_chapters_disabled\">नए अध्यायों की जांच अक्षम है</string>\n    <string name=\"show_labels_in_navbar\">नेविगेशन बार में लेबल दिखाएं</string>\n    <string name=\"pages_saving\">पन्ने सहेजा जा रहा है</string>\n    <string name=\"ask_for_dest_dir_every_time\">हर बार गंतव्य स्थान के लिए पूछें</string>\n    <string name=\"preferred_download_format\">वरीय डाउनलोड प्रारूप</string>\n    <string name=\"multiple_cbz_files\">अनेक CBZ फाइलें</string>\n    <string name=\"automatic\">स्वचालित</string>\n    <string name=\"single_cbz_file\">एकल CBZ फाइल</string>\n    <string name=\"reading_stats\">पठन आंकड़े</string>\n    <string name=\"stats_cleared\">आंकड़े साफ हो गए</string>\n    <string name=\"week\">हफ्ता</string>\n    <string name=\"all_time\">पूरा समय</string>\n    <string name=\"other_manga\">अन्य मंगा</string>\n    <string name=\"less_than_minute\">एक मिनट से कम</string>\n    <string name=\"statistics\">आंकड़े</string>\n    <string name=\"clear_stats_confirm\">क्या आप सचमुच सभी पठन आंकड़े साफ करना चाहते हैं? इस क्रिया को पूर्ववत नहीं किया जा सकता है।</string>\n    <string name=\"three_months\">तीन महीने</string>\n    <string name=\"clear_stats\">आंकड़े साफ करें</string>\n    <string name=\"month\">महीना</string>\n    <string name=\"day\">दिन</string>\n    <string name=\"empty_stats_text\">चयनित अवधि के लिए कोई आंकड़े नहीं हैं</string>\n    <string name=\"pages_read_s\">पठित पृष्ठ: %s</string>\n    <string name=\"manga_migration\">मंगा प्रवासन</string>\n    <string name=\"migration_completed\">प्रवासन पूरा हुआ</string>\n    <string name=\"migrate\">प्रवासन</string>\n    <string name=\"alternatives\">वैकल्पिक</string>\n    <string name=\"migrate_confirmation\">आपके इतिहास और पसंदीदा में \\\"%2$s\\\" से मंगा \\\"%1$s\\\" को \\\"%4$s\\\" से \\\"%3$s\\\" से बदल दिया जाएगा (यदि मौजूद है)</string>\n    <string name=\"delete_read_chapters\">पढ़े हुए अध्याय मिटाएं</string>\n    <string name=\"no_chapters_deleted\">कोई अध्याय नहीं मिटाया गया है</string>\n    <string name=\"chapters_deleted_pattern\">%1$s को हटाया गया, %2$s को साफ किया गया</string>\n    <string name=\"delete_read_chapters_summary\">स्थान खाली करने के लिए स्थानीय स्टोरेज से उन अध्यायों को मिटा दें जिन्हें आप पढ़ चुके हैं</string>\n    <string name=\"delete_read_chapters_prompt\">यह आपके स्थानीय स्टोरेज से पढ़े गए के रूप में चिह्नित सभी अध्यायों को स्थायी रूप से मिटा देगा। आप इसे बाद में पुनः डाउनलोड कर सकते हैं, लेकिन आयातित अध्याय हमेशा के लिए खो सकते हैं</string>\n    <string name=\"delete_read_chapters_auto\">पढ़े गए अध्याय स्वचालित रूप से मिटाएं</string>\n    <string name=\"runs_on_app_start\">एप्लिकेशन प्रारंभ होने पर चलता है</string>\n    <string name=\"chapters_grid_view\">ग्रिड दृश्य</string>\n    <string name=\"split_by_translations\">अनुवाद द्वारा विभाजित</string>\n    <string name=\"split_by_translations_summary\">विभिन्न अनुवादों वाले अध्यायों को एक सूची के बजाय अलग-अलग दिखाएं</string>\n    <string name=\"order_oldest\">सबसे पुराना</string>\n    <string name=\"long_ago_read\">बहुत पहले पढ़ा था</string>\n    <string name=\"unread\">अपठित</string>\n    <string name=\"unsupported_source\">यह मंगा स्रोत समर्थित नहीं है</string>\n    <string name=\"enable_source\">स्रोत सक्षम करें</string>\n    <string name=\"show_pages_thumbs_summary\">विवरण स्क्रीन पर \\\"पृष्ठ\\\" टैब सक्षम करें</string>\n    <string name=\"show_pages_thumbs\">पृष्ठ थंबनेल दिखाएं</string>\n    <string name=\"error_no_data_received\">सर्वर से कोई डेटा प्राप्त नहीं हुआ</string>\n    <string name=\"unsupported_backup_message\">कृपया उचित Kotatsu बैकअप फाइल चुनें</string>\n    <string name=\"hours_short\">%d घं</string>\n    <string name=\"minutes_short\">%d मि</string>\n    <string name=\"hours_minutes_short\">%1$d घं %2$d मि</string>\n    <string name=\"fix\">ठीक करें</string>\n    <string name=\"missing_storage_permission\">बाहरी स्टोरेज पर मंगा तक पहुंचने की कोई अनुमति नहीं है</string>\n    <string name=\"last_used\">अंतिम प्रयोग</string>\n    <string name=\"show_updated\">अद्यतित दिखाएं</string>\n    <string name=\"webtoon_gaps\">वेबटून मोड में अंतराल</string>\n    <string name=\"webtoon_gaps_summary\">वेबटून मोड में पृष्ठों के बीच लंबवत अंतराल दिखाएं</string>\n    <string name=\"less_frequently\">कम बार</string>\n    <string name=\"more_frequently\">अधिक बार</string>\n    <string name=\"frequency_of_check\">जांच की आवृत्ति</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">नेविगेशन UI पिन करें</string>\n    <string name=\"pin_navigation_ui_summary\">स्क्रॉल पर नेविगेशन बार और खोज दृश्य को न छिपाएं</string>\n    <string name=\"search_suggestions\">सुझाव खोजें</string>\n    <string name=\"recent_queries\">हाल के प्रश्न</string>\n    <string name=\"suggested_queries\">सुझाए गए प्रश्न</string>\n    <string name=\"authors\">रचयिता</string>\n    <string name=\"blocked_by_server_message\">आपको सर्वर द्वारा अवरुद्ध कर दिया गया है। किसी भिन्न नेटवर्क कनेक्शन (VPN, प्रॉक्सी, आदि) का उपयोग करने का प्रयास करें</string>\n    <string name=\"disable\">अक्षम करें</string>\n    <string name=\"sources_disabled\">स्रोत अक्षम</string>\n    <string name=\"disable_connectivity_check\">कनेक्टिविटी जांच अक्षम करें</string>\n    <string name=\"ignore_ssl_errors_summary\">यदि नेटवर्क संसाधनों तक पहुँचने के दौरान आपको SSL से संबंधित समस्याओं का सामना करना पड़ता है तो आप SSL प्रमाणपत्र सत्यापन को अक्षम कर सकते हैं। इससे आपकी सुरक्षा प्रभावित हो सकती है। इस सेटिंग को बदलने के बाद एप्लिकेशन को पुनरारंभ करना आवश्यक है।</string>\n    <string name=\"disable_connectivity_check_summary\">यदि आपको कनेक्टिविटी से जुड़ी कोई समस्या है तो कनेक्टिविटी जांच को छोड़ दें (उदाहरण के लिए नेटवर्क कनेक्ट होने के बावजूद ऑफ़लाइन मोड में जाना)</string>\n    <string name=\"disable_nsfw_notifications\">NSFW सूचनाएं अक्षम करें</string>\n    <string name=\"disable_nsfw_notifications_summary\">NSFW मंगा अपडेट के बारे में सूचनाएं न दिखाएं</string>\n    <string name=\"tracker_debug_info\">नए अध्याय लॉग की जांच की जा रही है</string>\n    <string name=\"tracker_debug_info_summary\">नए अध्यायों के लिए पृष्ठभूमि जांच के बारे में जानकारी डीबग करें</string>\n    <string name=\"_new\">नया</string>\n    <string name=\"all_languages\">सभी भाषाएं</string>\n    <string name=\"screenshots_block_incognito\">गुप्त मोड में ब्लॉक करें</string>\n    <string name=\"image_server\">पसंदीदा छवि सर्वर</string>\n    <string name=\"crop_pages\">पृष्ठ काटें</string>\n    <string name=\"pin\">पिन करें</string>\n    <string name=\"unpin\">अनपिन करें</string>\n    <string name=\"sources_unpinned\">स्रोत अनपिन किए गए</string>\n    <string name=\"sources_pinned\">स्रोत पिन किए गए</string>\n    <string name=\"source_pinned\">स्रोत पिन किया गया</string>\n    <string name=\"source_unpinned\">स्रोत अनपिन किया गया</string>\n    <string name=\"recent_sources\">हालिया स्रोत</string>\n    <string name=\"percent_read\">प्रतिशत पढ़ा</string>\n    <string name=\"percent_left\">प्रतिशत शेष</string>\n    <string name=\"chapters_read\">अध्याय पढ़ा</string>\n    <string name=\"external_source\">बाहरी/प्लगइन</string>\n    <string name=\"chapters_left\">अध्याय शेष</string>\n    <string name=\"plugin_incompatible\">असंगत प्लगइन या आंतरिक त्रुटि। सुनिश्चित करें कि आप प्लगइन और कोटात्सु के नवीनतम संस्करण का उपयोग कर रहे हैं</string>\n    <string name=\"text_empty_holder_secondary_filtered\">आपके द्वारा चयनित फिल्टर से मेल खाने वाला कोई मंगा नहीं है</string>\n    <string name=\"connection_ok\">कनेक्शन ठीक है</string>\n    <string name=\"show_quick_filters\">शीघ्र फिल्टर दिखाएं</string>\n    <string name=\"invalid_proxy_configuration\">अमान्य प्रॉक्सी विन्यास</string>\n    <string name=\"show_quick_filters_summary\">कुछ मापदंडों के आधार पर मंगा सूचियों को फिल्टर करने की क्षमता प्रदान करता है</string>\n    <string name=\"pages_saved\">सहेजे गए पृष्ठ</string>\n    <string name=\"invalid_server_address_message\">अमान्य सर्वर पता</string>\n    <string name=\"retry\">पुनः प्रयत्न करें</string>\n    <string name=\"nsfw_16\">१६+</string>\n    <string name=\"too_many_requests_message_retry\">बहुत अधिक अनुरोध. %s बाद में फिर से प्रयास करें</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-hr/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d stavka</item>\n        <item quantity=\"few\">%1$d stavke</item>\n        <item quantity=\"other\">%1$d stavki</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d novo poglavlje</item>\n        <item quantity=\"few\">%1$d nova poglavlja</item>\n        <item quantity=\"other\">%1$d novih poglavlja</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d poglavlje</item>\n        <item quantity=\"few\">%1$d poglavlja</item>\n        <item quantity=\"other\">%1$d poglavlja</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">Prije %1$d minute</item>\n        <item quantity=\"few\">Prije %1$d minute</item>\n        <item quantity=\"other\">Prije %1$d minuta</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">Prije %1$d sat</item>\n        <item quantity=\"few\">Prije %1$d sata</item>\n        <item quantity=\"other\">Prije %1$d sati</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">Prije %1$d dan</item>\n        <item quantity=\"few\">Prije %1$d dana</item>\n        <item quantity=\"other\">Prije %1$d dana</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">Prije %1$d mjesec</item>\n        <item quantity=\"few\">Prije %1$d mjeseca</item>\n        <item quantity=\"other\">Prije %1$d mjeseci</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d sat</item>\n        <item quantity=\"few\">%1$d sata</item>\n        <item quantity=\"other\">%1$d sati</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuta</item>\n        <item quantity=\"few\">%1$d minute</item>\n        <item quantity=\"other\">%1$d minuta</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-hr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"missing_storage_permission\">Nema dopuštenja za pristup mangi na vanjskoj pohrani</string>\n    <string name=\"show_pages_thumbs_summary\">Omogućite karticu \\\"Stranice\\\" na zaslonu s detaljima</string>\n    <string name=\"error_no_data_received\">Nikakvi podaci nisu primljeni s poslužitelja</string>\n    <string name=\"unsupported_backup_message\">Odaberite odgovarajuću datoteku sigurnosne kopije Kotatsu</string>\n    <string name=\"hours_short\">%d h</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"hours_minutes_short\">%1$d h %2$d m</string>\n    <string name=\"fix\">Popravi</string>\n    <string name=\"show_updated\">Prikaži ažurirano</string>\n    <string name=\"webtoon_gaps_summary\">Prikaži okomite razmake između stranica u načinu webtoon</string>\n    <string name=\"more_frequently\">Češće</string>\n    <string name=\"pin_navigation_ui_summary\">Nemoj skrivati navigacijsku traku i prikaz pretrage prilikom listanja</string>\n    <string name=\"suggested_queries\">Predloženi upiti</string>\n    <string name=\"last_used\">Zadnje korišteno</string>\n    <string name=\"webtoon_gaps\">Razmaci u webtoon modusu</string>\n    <string name=\"blocked_by_server_message\">Poslužitelj vas je blokirao. Pokušajte koristiti drugu mrežnu vezu (VPN, proxy itd.)</string>\n    <string name=\"less_frequently\">Rjeđe</string>\n    <string name=\"frequency_of_check\">Učestalost provjere</string>\n    <string name=\"recent_queries\">Nedavni upiti</string>\n    <string name=\"disable\">Onemogući</string>\n    <string name=\"pin_navigation_ui\">Zakači navigacijsko sučelje</string>\n    <string name=\"search_suggestions\">Prijedlozi za pretraživanje</string>\n    <string name=\"authors\">Autori</string>\n    <string name=\"sources_disabled\">Izvori onemogućeni</string>\n    <string name=\"local_storage\">Lokalna pohrana</string>\n    <string name=\"favourites\">Favoriti</string>\n    <string name=\"history\">Povijest</string>\n    <string name=\"error_occurred\">Dogodila se greška</string>\n    <string name=\"details\">Detalji</string>\n    <string name=\"chapters\">Poglavlja</string>\n    <string name=\"list\">Popis</string>\n    <string name=\"detailed_list\">Detaljan popis</string>\n    <string name=\"grid\">Mreža</string>\n    <string name=\"settings\">Postavke</string>\n    <string name=\"remote_sources\">Manga izvori</string>\n    <string name=\"loading_\">Učitavanje…</string>\n    <string name=\"computing_\">Računanje…</string>\n    <string name=\"chapter_d_of_d\">Poglavlje %1$d od %2$d</string>\n    <string name=\"close\">Zatvori</string>\n    <string name=\"try_again\">Pokušaj ponovo</string>\n    <string name=\"clear_history\">Obriši povijest</string>\n    <string name=\"nothing_found\">Ništa nije pronađeno</string>\n    <string name=\"read\">Čitaj</string>\n    <string name=\"you_have_not_favourites_yet\">Još nema favorita</string>\n    <string name=\"add_to_favourites\">Dodaj u favorite</string>\n    <string name=\"add_new_category\">Nova kategorija</string>\n    <string name=\"add\">Dodaj</string>\n    <string name=\"save\">Sačuvaj</string>\n    <string name=\"share\">Dijeli</string>\n    <string name=\"create_shortcut\">Stvori prečac</string>\n    <string name=\"share_s\">Dijeli %s</string>\n    <string name=\"search\">Traži</string>\n    <string name=\"search_manga\">Traži mangu</string>\n    <string name=\"manga_downloading_\">Preuzimanje…</string>\n    <string name=\"network_error\">Pogreška mreže</string>\n    <string name=\"list_mode\">Modus popisa</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"light\">Svijetla</string>\n    <string name=\"dark\">Tamna</string>\n    <string name=\"clear\">Očisti</string>\n    <string name=\"remove\">Ukloni</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" izbrisano iz lokalne pohrane</string>\n    <string name=\"page_saved\">Stranica je spremljena</string>\n    <string name=\"share_image\">Dijeli sliku</string>\n    <string name=\"_import\">Uvoz</string>\n    <string name=\"delete\">Izbriši</string>\n    <string name=\"operation_not_supported\">Ova operacija nije podržana</string>\n    <string name=\"text_file_not_supported\">Odaberite ZIP ili CBZ datoteku.</string>\n    <string name=\"no_description\">Bez opisa</string>\n    <string name=\"clear_pages_cache\">Očisti predmemoriju stranice</string>\n    <string name=\"read_mode\">Modus čitanja</string>\n    <string name=\"grid_size\">Veličina mreže</string>\n    <string name=\"search_on_s\">Traži na %s</string>\n    <string name=\"delete_manga\">Izbriši mangu</string>\n    <string name=\"text_delete_local_manga\">Trajno izbrisati \\\"%s\\\" s uređaja?</string>\n    <string name=\"reader_settings\">Postavke čitača</string>\n    <string name=\"switch_pages\">Promijenite stranice</string>\n    <string name=\"_continue\">Nastaviti</string>\n    <string name=\"error\">Greška</string>\n    <string name=\"clear_thumbs_cache\">Očisti predmemoriju sličica</string>\n    <string name=\"search_history_cleared\">Očišćeno</string>\n    <string name=\"internal_storage\">Interna pohrana</string>\n    <string name=\"external_storage\">Vanjska pohrana</string>\n    <string name=\"domain\">Domena</string>\n    <string name=\"app_update_available\">Dostupna je nova verzija aplikacije</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Omogućeno je %1$d od %2$d</string>\n    <string name=\"new_chapters\">Nova poglavlja</string>\n    <string name=\"download\">Preuzimi</string>\n    <string name=\"notifications_settings\">Postavke obavijesti</string>\n    <string name=\"notification_sound\">Zvuk obavijesti</string>\n    <string name=\"light_indicator\">LED indikator</string>\n    <string name=\"vibration\">Vibracija</string>\n    <string name=\"favourites_categories\">Omiljene kategorije</string>\n    <string name=\"text_search_holder_secondary\">Pokušajte preformulirati upit.</string>\n    <string name=\"text_history_holder_primary\">Ono što pročitate bit će prikazano ovdje</string>\n    <string name=\"text_history_holder_secondary\">Pronađite što čitati u odjeljku «Istražite»</string>\n    <string name=\"text_local_holder_primary\">Prvo spremite nešto</string>\n    <string name=\"text_local_holder_secondary\">Spremite nešto iz online kataloga ili uvezite iz datoteke.</string>\n    <string name=\"manga_shelf\">Polica</string>\n    <string name=\"recent_manga\">Nedavno</string>\n    <string name=\"pages_animation\">Animacija stranice</string>\n    <string name=\"manga_save_location\">Mapa preuzimanja</string>\n    <string name=\"not_available\">Nije dostupno</string>\n    <string name=\"cannot_find_available_storage\">Nema dostupnog prostora za pohranu</string>\n    <string name=\"other_storage\">Ostala pohrana</string>\n    <string name=\"all_favourites\">Svi favoriti</string>\n    <string name=\"favourites_category_empty\">Prazna kategorija</string>\n    <string name=\"text_feed_holder\">Ovdje su prikazana nova poglavlja onoga što čitate</string>\n    <string name=\"search_results\">Rezultati pretraživanja</string>\n    <string name=\"new_version_s\">Nova verzija: %s</string>\n    <string name=\"dont_check\">Ne provjeravaj</string>\n    <string name=\"enter_password\">Upišite lozinku</string>\n    <string name=\"wrong_password\">Pogrešna lozinka</string>\n    <string name=\"protect_application\">Zaštitite aplikaciju</string>\n    <string name=\"protect_application_summary\">Traži lozinku prilikom pokretanja Kotatsua</string>\n    <string name=\"repeat_password\">Ponovite lozinku</string>\n    <string name=\"passwords_mismatch\">Nepodudarne lozinke</string>\n    <string name=\"about\">O aplikaciji</string>\n    <string name=\"app_version\">Verzija %s</string>\n    <string name=\"check_for_updates\">Provjerite ima li ažuriranja</string>\n    <string name=\"no_update_available\">Nema dostupnih ažuriranja</string>\n    <string name=\"right_to_left\">S desna na lijevo</string>\n    <string name=\"create_category\">Nova kategorija</string>\n    <string name=\"scale_mode\">Modus skaliranja</string>\n    <string name=\"zoom_mode_fit_width\">Prilagodi širini</string>\n    <string name=\"zoom_mode_fit_height\">Prilagodi visini</string>\n    <string name=\"zoom_mode_fit_center\">Prilagodi sredini</string>\n    <string name=\"zoom_mode_keep_start\">Zadrži na početku</string>\n    <string name=\"black_dark_theme\">Crno</string>\n    <string name=\"black_dark_theme_summary\">Koristi manje energije na AMOLED zaslonima</string>\n    <string name=\"backup_restore\">Sigurnosno kopiranje i vraćanje</string>\n    <string name=\"create_backup\">Stvorite sigurnosnu kopiju podataka</string>\n    <string name=\"group\">Grupa</string>\n    <string name=\"today\">Danas</string>\n    <string name=\"tap_to_try_again\">Dodirnite za ponovni pokušaj</string>\n    <string name=\"reader_mode_hint\">Odabrana konfiguracija će se zapamtiti za ovaj manga</string>\n    <string name=\"silent\">Tiho</string>\n    <string name=\"captcha_required\">Potrebna CAPTCHA</string>\n    <string name=\"captcha_solve\">Riješi</string>\n    <string name=\"clear_cookies\">Obriši kolačiće</string>\n    <string name=\"cookies_cleared\">Svi kolačići su uklonjeni</string>\n    <string name=\"clear_feed\">Očisti novosti</string>\n    <string name=\"text_clear_updates_feed_prompt\">Trajno izbrisati svu povijest ažuriranja?</string>\n    <string name=\"check_for_new_chapters\">Provjerite ima li novih poglavlja</string>\n    <string name=\"reverse\">Unazad</string>\n    <string name=\"chapters_grid_view\">Mrežni prikaz</string>\n    <string name=\"sign_in\">Prijaviti se</string>\n    <string name=\"auth_required\">Prijavite se da vidite ovaj sadržaj</string>\n    <string name=\"default_s\">Zadano: %s</string>\n    <string name=\"next\">Sljedeće</string>\n    <string name=\"protect_application_subtitle\">Unesite lozinku za pokretanje aplikacije</string>\n    <string name=\"confirm\">Potvrdi</string>\n    <string name=\"password_length_hint\">Lozinka mora imati 4 znaka ili više</string>\n    <string name=\"tracker_warning\">Neki uređaji imaju drugačije ponašanje sustava, što može prekinuti pozadinske zadatke.</string>\n    <string name=\"read_more\">Čitaj više</string>\n    <string name=\"queued\">U redu čekanja</string>\n    <string name=\"chapter_is_missing\">Poglavlje nedostaje</string>\n    <string name=\"about_app_translation_summary\">Prevedi ovu aplikaciju</string>\n    <string name=\"about_app_translation\">Prijevod</string>\n    <string name=\"auth_complete\">Ovlašteni</string>\n    <string name=\"auth_not_supported_by\">Prijava na %s nije podržana</string>\n    <string name=\"text_clear_cookies_prompt\">Bit ćete odjavljeni sa svih izvora</string>\n    <string name=\"genres\">Žanrovi</string>\n    <string name=\"state_finished\">Završeno</string>\n    <string name=\"state_ongoing\">U tijeku</string>\n    <string name=\"system_default\">Zadano</string>\n    <string name=\"exclude_nsfw_from_history\">Isključi NSFW mangu iz povijesti</string>\n    <string name=\"show_pages_numbers\">Numerirane stranice</string>\n    <string name=\"screenshots_policy\">Pravila snimanja zaslona</string>\n    <string name=\"suggestions_info\">Svi se podaci analiziraju samo lokalno na ovom uređaju i nikada se ne šalju nigdje.</string>\n    <string name=\"text_suggestion_holder\">Počnite čitati mangu i dobit ćete personalizirane prijedloge</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Nemojte predlagati NSFW mangu</string>\n    <string name=\"reset_filter\">Resetiraj filter</string>\n    <string name=\"onboard_text\">Odaberite jezike na kojima želite čitati mangu. Kasnije ga možete promijeniti u postavkama.</string>\n    <string name=\"never\">Nikada</string>\n    <string name=\"only_using_wifi\">Samo na Wi-Fi</string>\n    <string name=\"always\">Uvijek</string>\n    <string name=\"logged_in_as\">Prijavljeni kao %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Razni jezici</string>\n    <string name=\"search_chapters\">Pronađi poglavlje</string>\n    <string name=\"chapters_will_removed_background\">Poglavlja će biti uklonjena u pozadini</string>\n    <string name=\"canceled\">Otkazano</string>\n    <string name=\"account_already_exists\">Račun već postoji</string>\n    <string name=\"back\">Nazad</string>\n    <string name=\"sync\">Sinkronizacija</string>\n    <string name=\"sync_title\">Sinkronizirajte vaše podatke</string>\n    <string name=\"email_enter_hint\">Unesite vašu e-poštu za nastavak</string>\n    <string name=\"hide\">Sakrij</string>\n    <string name=\"new_sources_text\">Dostupni su novi izvori mange</string>\n    <string name=\"check_new_chapters_title\">Provjeriti ima li novih poglavlja i obavijestiti o tome</string>\n    <string name=\"show_notification_new_chapters_on\">Primit ćete obavijesti o ažuriranjima mange koju čitate</string>\n    <string name=\"show_notification_new_chapters_off\">Nećete primati obavijesti, ali će nova poglavlja biti istaknuta na popisima</string>\n    <string name=\"notifications_enable\">Omogući obavijesti</string>\n    <string name=\"name\">Ime</string>\n    <string name=\"edit\">Uredi</string>\n    <string name=\"edit_category\">Uredi kategoriju</string>\n    <string name=\"tracking\">Pratim</string>\n    <string name=\"bookmark_add\">Dodaj zabilješku</string>\n    <string name=\"bookmark_remove\">Ukloni zabilješku</string>\n    <string name=\"bookmark_added\">Zabilješka dodana</string>\n    <string name=\"removed_from_history\">Uklonjeno iz povijesti</string>\n    <string name=\"detect_reader_mode\">Automatski otkrij modus čitanja</string>\n    <string name=\"disable_battery_optimization_summary\">Pomaže pri provjerama ažuriranja u pozadini</string>\n    <string name=\"crash_text\">Nešto je pošlo po zlu. Pošaljite izvješće o pogrešci razvojnim programerima kako biste nam pomogli da je popravimo.</string>\n    <string name=\"send\">Pošalji</string>\n    <string name=\"status_planned\">Planirano</string>\n    <string name=\"status_reading\">Čitam</string>\n    <string name=\"status_re_reading\">Ponovo čitam</string>\n    <string name=\"status_completed\">Dovršeno</string>\n    <string name=\"status_on_hold\">Na čekanju</string>\n    <string name=\"report\">Prijavi</string>\n    <string name=\"show_reading_indicators\">Prikaži indikatore napretka tokom čitanja</string>\n    <string name=\"data_deletion\">Brisanje podataka</string>\n    <string name=\"show_reading_indicators_summary\">Prikaži postotak pročitanih u povijesti i favoritima</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga označena kao NSFW nikada neće biti dodana u povijest i vaš napredak neće biti spremljen</string>\n    <string name=\"clear_cookies_summary\">Može pomoći u slučaju nekih problema. Sva će ovlaštenja biti poništena</string>\n    <string name=\"show_all\">Prikaži sve</string>\n    <string name=\"invalid_domain_message\">Nevažeća domena</string>\n    <string name=\"select_range\">Odaberite raspon</string>\n    <string name=\"clear_all_history\">Izbriši svu povijest</string>\n    <string name=\"last_2_hours\">Zadnja 2 sata</string>\n    <string name=\"history_cleared\">Povijest izbrisana</string>\n    <string name=\"manage\">Upravljaj</string>\n    <string name=\"no_bookmarks_yet\">Još nema zabilješki</string>\n    <string name=\"bookmarks_removed\">Zabilješka izbrisana</string>\n    <string name=\"no_manga_sources\">Nema izvora mange</string>\n    <string name=\"no_manga_sources_text\">Omogućite izvore mange za čitanje mange na mreži</string>\n    <string name=\"random\">Nasumično</string>\n    <string name=\"categories_delete_confirm\">Jeste li sigurni da želite izbrisati odabrane omiljene kategorije? \\nSve mange u njoj bit će izgubljene i to se ne može poništiti.</string>\n    <string name=\"reader_info_pattern\">Poglavlje %1$d/%2$d Stranica %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Prikaži informacijsku traku u čitaču</string>\n    <string name=\"comics_archive\">Arhiva stripova</string>\n    <string name=\"folder_with_images\">Mapa sa slikama</string>\n    <string name=\"importing_manga\">Uvoz mange</string>\n    <string name=\"import_completed\">Uvoz dovršen</string>\n    <string name=\"import_completed_hint\">Možete izbrisati izvornu datoteku iz pohrane radi uštede prostora</string>\n    <string name=\"import_will_start_soon\">Uvoz će uskoro početi</string>\n    <string name=\"feed\">Novosti</string>\n    <string name=\"manga_error_description_pattern\">Detalji pogreške:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Pokušajte &lt;a href=%2$s&gt;otvoriti mangu u web-pregledniku&lt;/a&gt; kako biste bili sigurni da je dostupna na izvoru&lt;br&gt;2. Provjerite koristite li &lt;a href=kotatsu://about&gt;najnoviju verziju Kotatsu&lt;/a&gt;&lt;br&gt;3. Ako je dostupno, pošaljite izvješće o pogrešci programerima.</string>\n    <string name=\"history_shortcuts\">Prikaži nedavne manga prečice</string>\n    <string name=\"history_shortcuts_summary\">Učinite nedavnu mangu dostupnom dugim pritiskom na ikonu aplikacije</string>\n    <string name=\"reader_control_ltr_summary\">Ne prilagođavaj smjer listanja stranica u modus čitača, npr. pritiskom desne tipke uvijek prelazi na sljedeću stranicu. Ova opcija utječe samo na hardverske uređaje za unos</string>\n    <string name=\"reader_control_ltr\">Ergonomska kontrola čitača</string>\n    <string name=\"color_correction\">Korekcija boja</string>\n    <string name=\"brightness\">Svjetlina</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"reset\">Resetiraj</string>\n    <string name=\"text_unsaved_changes_prompt\">Spremi ili odbaci nespremljene promjene?</string>\n    <string name=\"discard\">Odbaci</string>\n    <string name=\"error_no_space_left\">Nema više prostora na uređaju</string>\n    <string name=\"reader_slider\">Prikaži klizač za promjenu stranice</string>\n    <string name=\"webtoon_zoom\">Webtoon zumiranje</string>\n    <string name=\"network_unavailable\">Mreža nije dostupna</string>\n    <string name=\"network_unavailable_hint\">Uključite Wi-Fi ili mobilnu mrežu da biste čitali mangu online</string>\n    <string name=\"server_error\">Pogreška na strani poslužitelja (%1$d). Molimo pokušajte ponovo kasnije</string>\n    <string name=\"clear_new_chapters_counters\">Također očistite informacije o novim poglavljima</string>\n    <string name=\"compact\">Kompaktan</string>\n    <string name=\"source_disabled\">Izvor onemogućen</string>\n    <string name=\"prefetch_content\">Predučitavanje sadržaja</string>\n    <string name=\"mark_as_current\">Označi kao trenutno</string>\n    <string name=\"language\">Jezik</string>\n    <string name=\"share_logs\">Dijeli dnevnike</string>\n    <string name=\"enable_logging\">Omogući bilježenje</string>\n    <string name=\"enable_logging_summary\">Snimite neke radnje u svrhu otklanjanja pogrešaka. Nemojte ga uključivati ako niste sigurni što radite</string>\n    <string name=\"show_suspicious_content\">Prikaži sumnjiv sadržaj</string>\n    <string name=\"theme_name_dynamic\">Dinamična</string>\n    <string name=\"color_theme\">Shema boja</string>\n    <string name=\"show_in_grid_view\">Prikaži u mrežnom prikazu</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"services\">Usluge</string>\n    <string name=\"allow_unstable_updates\">Dopusti nestabilna ažuriranja</string>\n    <string name=\"allow_unstable_updates_summary\">Primajte obavijesti o nestabilnim verzijama</string>\n    <string name=\"download_started\">Preuzimanje je počelo</string>\n    <string name=\"got_it\">Shvaćam</string>\n    <string name=\"speed\">Brzina</string>\n    <string name=\"server_address\">Adresa poslužitelja</string>\n    <string name=\"sync_host_description\">Možete koristiti poslužitelj za sinkronizaciju s vlastitim hostom ili zadanim. Nemojte ovo mijenjati ako niste sigurni što radite.</string>\n    <string name=\"ignore_ssl_errors\">Ignorirajte SSL greške</string>\n    <string name=\"mirror_switching\">Odaberite posluživač automatski</string>\n    <string name=\"mirror_switching_summary\">Automatski mijenjaj domene za izvore mange u slučaju grešaka ako su poslužitelji dostupni</string>\n    <string name=\"pause\">Pauza</string>\n    <string name=\"resume\">Nastavi</string>\n    <string name=\"paused\">Pauzirano</string>\n    <string name=\"remove_completed\">Ukloni dovršene</string>\n    <string name=\"cancel_all\">Otkaži sve</string>\n    <string name=\"downloads_wifi_only\">Preuzimanje samo putem Wi-Fi mreže</string>\n    <string name=\"downloads_wifi_only_summary\">Zaustavite preuzimanje kada se prebacite na mobilnu mrežu</string>\n    <string name=\"suggestion_manga\">Prijedlog: %s</string>\n    <string name=\"suggestions_notifications_summary\">Ponekad prikaži obavijesti s predloženom mangom</string>\n    <string name=\"more\">Više</string>\n    <string name=\"enable\">Omogući</string>\n    <string name=\"no_thanks\">Ne hvala</string>\n    <string name=\"cancel_all_downloads_confirm\">Sva aktivna preuzimanja bit će otkazana, djelomično preuzeti podaci bit će izgubljeni</string>\n    <string name=\"remove_completed_downloads_confirm\">Vaša povijest preuzimanja će se trajno izbrisati. To neće utjecati na preuzete datoteke</string>\n    <string name=\"downloads_resumed\">Preuzimanja su nastavljena</string>\n    <string name=\"downloads_paused\">Preuzimanja su pauzirana</string>\n    <string name=\"downloads_removed\">Preuzimanja su uklonjena</string>\n    <string name=\"suggestions_enable_prompt\">Želite li primati personalizirane prijedloge za mange?</string>\n    <string name=\"web_view_unavailable\">WebView nije dostupan: provjerite je li instaliran WebView provider</string>\n    <string name=\"type\">Tip</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"invalid_value_message\">Nevažeća vrijednost</string>\n    <string name=\"email_password_enter_hint\">Unesite vašu e-poštu i lozinku za nastavak</string>\n    <string name=\"invert_colors\">Invertiraj boje</string>\n    <string name=\"username\">Korisničko ime</string>\n    <string name=\"password\">Lozinka</string>\n    <string name=\"authorization_optional\">Autorizacija (nije obavezno)</string>\n    <string name=\"invalid_port_number\">Nevažeći broj port-a</string>\n    <string name=\"network\">Mreža</string>\n    <string name=\"data_and_privacy\">Podaci i privatnost</string>\n    <string name=\"restore_summary\">Vratite prethodno stvorenu sigurnosnu kopiju</string>\n    <string name=\"reader_info_bar_summary\">Prikažite trenutno vrijeme i napredak čitanja na vrhu zaslona</string>\n    <string name=\"show_pages_numbers_summary\">Prikaži brojeve stranica u donjem kutu</string>\n    <string name=\"clear_source_cookies_summary\">Brisanje kolačića samo za određenu domenu. U većini slučajeva poništit će autorizaciju</string>\n    <string name=\"download_option_whole_manga\">Cijela manga</string>\n    <string name=\"download_option_first_n_chapters\">Prvi %s</string>\n    <string name=\"description\">Opis</string>\n    <string name=\"tracker_wifi_only_summary\">Ne provjeravajte nova poglavlja pomoću mrežnih veza s ograničenim protokom</string>\n    <string name=\"search_hint\">Unesite naslov mange, žanr ili naziv izvora</string>\n    <string name=\"progress\">Napredak</string>\n    <string name=\"order_added\">Dodano</string>\n    <string name=\"show\">Prikaži</string>\n    <string name=\"captcha_required_summary\">%s zahtijeva da se captcha riješi kako bi ispravno radio</string>\n    <string name=\"to_top\">Na vrh</string>\n    <string name=\"zoom_out\">Umanji</string>\n    <string name=\"zoom_in\">Povećaj</string>\n    <string name=\"reader_zoom_buttons\">Prikaži gumbe za zumiranje</string>\n    <string name=\"reader_zoom_buttons_summary\">Treba li prikazati gumbe za kontrolu zumiranja u donjem desnom kutu</string>\n    <string name=\"keep_screen_on\">Držite zaslon uključenim</string>\n    <string name=\"keep_screen_on_summary\">Ne isključujte ekran dok čitate mangu</string>\n    <string name=\"state_abandoned\">Izbačeno</string>\n    <string name=\"enhanced_colors_summary\">Smanjuje trake, ali može utjecati na performanse</string>\n    <string name=\"enhanced_colors\">32-bitni modus boje</string>\n    <string name=\"suggest_new_sources\">Predloži nove izvore nakon ažuriranja aplikacije</string>\n    <string name=\"suggest_new_sources_summary\">Upit za omogućavanje novododanih izvora nakon ažuriranja aplikacije</string>\n    <string name=\"list_options\">Popis opcija</string>\n    <string name=\"by_relevance\">Relevantnost</string>\n    <string name=\"categories\">Kategorije</string>\n    <string name=\"online_variant\">Online varijanta</string>\n    <string name=\"periodic_backups\">Periodične sigurnosne kopije</string>\n    <string name=\"backup_frequency\">Učestalost stvaranja sigurnosne kopije</string>\n    <string name=\"frequency_every_day\">Svaki dan</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Stripovi</string>\n    <string name=\"content_type_other\">Ostalo</string>\n    <string name=\"sources_catalog\">Katalog izvora</string>\n    <string name=\"source_enabled\">Izvor omogućen</string>\n    <string name=\"no_manga_sources_catalog_text\">U ovom odjeljku nema dostupnih izvora ili su svi možda već dodani.\n\\nBudite u toku</string>\n    <string name=\"no_manga_sources_found\">Vašim upitom nisu pronađeni dostupni izvori mangi</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"disable_nsfw_summary\">Onemogućite NSFW izvore i sakrijte mangu za odrasle s popisa ako je moguće</string>\n    <string name=\"state_paused\">Pauzirano</string>\n    <string name=\"reader_optimize\">Smanjite potrošnju memorije (beta)</string>\n    <string name=\"reader_optimize_summary\">Smanjite kvalitetu stranica izvan zaslona kako biste koristili manje memorije</string>\n    <string name=\"state\">Stanje</string>\n    <string name=\"error_multiple_genres_not_supported\">Ovaj izvor mange ne podržava filtriranje prema više žanrova</string>\n    <string name=\"apply\">Primjeni</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Ovaj izvor ne podržava filtriranje po žanrovima i lokalitetima</string>\n    <string name=\"error_filter_states_genre_not_supported\">Ovaj izvor ne podržava filtriranje po žanrovima i stanjima</string>\n    <string name=\"genres_search_hint\">Počnite upisivati naziv žanra</string>\n    <string name=\"backup_date_\">Datum sigurnosne kopije: %s</string>\n    <string name=\"state_upcoming\">Nadolazeće</string>\n    <string name=\"by_name_reverse\">Ime obrnuto</string>\n    <string name=\"content_rating\">Ocjena sadržaja</string>\n    <string name=\"rating_safe\">Sigurno</string>\n    <string name=\"rating_suggestive\">Sugestivno</string>\n    <string name=\"rating_adult\">Za odrasle</string>\n    <string name=\"default_tab\">Zadana kartica</string>\n    <string name=\"mark_as_completed\">Označi kao dovršeno</string>\n    <string name=\"mark_as_completed_prompt\">Označiti odabranu mangu kao potpuno pročitanu?\n\\n\n\\nUpozorenje: trenutni napredak čitanja bit će izgubljen.</string>\n    <string name=\"category_hidden_done\">Ova je kategorija bila skrivena s glavnog zaslona i dostupna je kroz Meni → Upravljanje kategorijama</string>\n    <string name=\"volume_\">Svezak %d</string>\n    <string name=\"volume_unknown\">Nepoznati svezak</string>\n    <string name=\"incognito_mode_hint\">Vaš napredak u čitanju se neće spremiti</string>\n    <string name=\"vertical\">Okomito</string>\n    <string name=\"last_read\">Zadnje pročitano</string>\n    <string name=\"show_menu\">Prikaži meni</string>\n    <string name=\"toggle_ui\">Prikaži/sakrij korisničko sučelje</string>\n    <string name=\"prev_chapter\">Prethodno poglavlje</string>\n    <string name=\"next_chapter\">Sljedeće poglavlje</string>\n    <string name=\"next_page\">Sljedeća stranica</string>\n    <string name=\"default_webtoon_zoom_out\">Zadani webtoon smanji</string>\n    <string name=\"fullscreen_mode\">Cjeloekranski modus</string>\n    <string name=\"reader_fullscreen_summary\">Sakrij stanje sustava i navigacijske trake</string>\n    <string name=\"reading_time_estimation\">Prikaži procijenjeno vrijeme čitanja</string>\n    <string name=\"reading_time_estimation_summary\">Vrijednost procjene vremena može biti netočna</string>\n    <string name=\"suggestions_unavailable_text\">Značajka prijedloga je onemogućena</string>\n    <string name=\"check_for_new_chapters_disabled\">Provjera novih poglavlja je onemogućena</string>\n    <string name=\"show_labels_in_navbar\">Prikaži oznake u navigacijskoj traci</string>\n    <string name=\"pages_saving\">Spremanje stranica</string>\n    <string name=\"ask_for_dest_dir_every_time\">Svaki put pitajte za odredišni direktorij</string>\n    <string name=\"default_page_save_dir\">Zadani direktorij za spremanje stranice</string>\n    <string name=\"remove_from_history\">Ukloni iz povijesti</string>\n    <string name=\"location\">Lokacija</string>\n    <string name=\"preferred_download_format\">Preferirani format preuzimanja</string>\n    <string name=\"automatic\">Automatski</string>\n    <string name=\"single_cbz_file\">Jedna CBZ datoteka</string>\n    <string name=\"multiple_cbz_files\">Više CBZ datoteka</string>\n    <string name=\"reading_stats\">Statistika čitanja</string>\n    <string name=\"other_manga\">Druge mange</string>\n    <string name=\"less_than_minute\">Manje od minute</string>\n    <string name=\"statistics\">Statistika</string>\n    <string name=\"stats_cleared\">Statistika obrisana</string>\n    <string name=\"clear_stats\">Obriši statistiku</string>\n    <string name=\"delete_read_chapters_summary\">Izbrišite poglavlja koja ste već pročitali iz lokalne pohrane kako biste oslobodili prostor</string>\n    <string name=\"delete_read_chapters_prompt\">Ovo će trajno izbrisati sva poglavlja označena kao pročitana iz vaše lokalne pohrane. Kasnije ga možete ponovo preuzeti, ali uvezena poglavlja mogu biti zauvijek izgubljena</string>\n    <string name=\"delete_read_chapters_auto\">Automatski izbrišite pročitana poglavlja</string>\n    <string name=\"runs_on_app_start\">Pokreće se kada se aplikacija pokrene</string>\n    <string name=\"split_by_translations\">Podjeli po prijevodima</string>\n    <string name=\"split_by_translations_summary\">Pokažite poglavlja s različitim prijevodima odvojeno, umjesto na jednom popisu</string>\n    <string name=\"order_oldest\">Najstariji</string>\n    <string name=\"long_ago_read\">Davno pročitano</string>\n    <string name=\"unread\">Nepročitano</string>\n    <string name=\"enable_source\">Omogući izvor</string>\n    <string name=\"unsupported_source\">Ovaj izvor mange nije podržan</string>\n    <string name=\"show_pages_thumbs\">Prikaži sličice stranica</string>\n    <string name=\"disable_connectivity_check\">Onemogući provjeru povezivanja</string>\n    <string name=\"ignore_ssl_errors_summary\">Možete onemogućiti provjeru SSL certifikata u slučaju da se suočite s problemima povezanim sa SSL-om prilikom pristupa mrežnim resursima. To može utjecati na vašu sigurnost. Nakon promjene ove postavke potrebno je ponovno pokretanje aplikacije.</string>\n    <string name=\"disable_connectivity_check_summary\">Preskočite provjeru povezivanja u slučaju da imate problema s njom (npr. odlazak u izvanmrežni način rada iako je mreža povezana)</string>\n    <string name=\"disable_nsfw_notifications\">Onemogući NSFW obavijesti</string>\n    <string name=\"disable_nsfw_notifications_summary\">Ne prikazuj obavijesti o ažuriranjima NSFW mange</string>\n    <string name=\"tracker_debug_info\">Provjera zapisnika novih poglavlja</string>\n    <string name=\"tracker_debug_info_summary\">Informacije o otklanjanju pogrešaka o pozadinskim provjerama za nova poglavlja</string>\n    <string name=\"_new\">Novo</string>\n    <string name=\"all_languages\">Svi jezici</string>\n    <string name=\"screenshots_block_incognito\">Blokiraj u anonimnom načinu rada</string>\n    <string name=\"processing_\">Obrada…</string>\n    <string name=\"download_complete\">Preuzeto</string>\n    <string name=\"by_name\">Ime</string>\n    <string name=\"newest\">Najnovije</string>\n    <string name=\"sort_order\">Redoslijed sortiranja</string>\n    <string name=\"filter\">Filter</string>\n    <string name=\"follow_system\">Slijedi sustav</string>\n    <string name=\"pages\">Stranice</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"remove_category\">Ukloni</string>\n    <string name=\"done\">Gotovo</string>\n    <string name=\"read_later\">Pročitaj kasnije</string>\n    <string name=\"size_s\">Veličina: %s</string>\n    <string name=\"feed_will_update_soon\">Ažuriranje sažetka sadržaja počet će uskoro</string>\n    <string name=\"track_sources\">Potražite ažuriranja</string>\n    <string name=\"clear_search_history\">Obriši povijest pretraživanja</string>\n    <string name=\"open_in_browser\">Otvorite u web pregledniku</string>\n    <string name=\"text_empty_holder_primary\">Ovdje je nekako prazno…</string>\n    <string name=\"updates\">Ažuriranja</string>\n    <string name=\"downloads\">Preuzimanja</string>\n    <string name=\"popular\">Popularno</string>\n    <string name=\"updated\">Ažurirano</string>\n    <string name=\"by_rating\">Ocjena</string>\n    <string name=\"save_page\">Spremi stranicu</string>\n    <string name=\"notifications\">Obavijesti</string>\n    <string name=\"update\">Ažuriraj</string>\n    <string name=\"history_is_empty\">Još nema povijesti</string>\n    <string name=\"clear_updates_feed\">Očisti sažetak ažuriranja</string>\n    <string name=\"updates_feed_cleared\">Očišćeno</string>\n    <string name=\"rotate_screen\">Zakreni zaslon</string>\n    <string name=\"preparing_\">Priprema…</string>\n    <string name=\"restore_backup\">Vrati iz sigurnosne kopije</string>\n    <string name=\"file_not_found\">Datoteka nije pronađena</string>\n    <string name=\"data_restored_success\">Svi podaci su vraćeni</string>\n    <string name=\"data_restored_with_errors\">Podaci su vraćeni, ali ima grešaka</string>\n    <string name=\"backup_information\">Možete stvoriti sigurnosnu kopiju svoje povijesti i favorita i obnoviti je</string>\n    <string name=\"yesterday\">Jučer</string>\n    <string name=\"long_ago\">Davno</string>\n    <string name=\"data_restored\">Vraćeno</string>\n    <string name=\"just_now\">Upravo sada</string>\n    <string name=\"screenshots_allow\">Dopusti</string>\n    <string name=\"screenshots_block_all\">Uvijek blokiraj</string>\n    <string name=\"suggestions\">Prijedlozi</string>\n    <string name=\"chapters_empty\">Nema poglavlja u ovoj mangi</string>\n    <string name=\"appearance\">Izgled</string>\n    <string name=\"suggestions_updating\">Ažuriranje prijedloga</string>\n    <string name=\"suggestions_excluded_genres_summary\">Navedite žanrove koje ne želite vidjeti u prijedlozima</string>\n    <string name=\"download_slowdown_summary\">Pomaže u izbjegavanju blokiranja vaše IP adrese</string>\n    <string name=\"local_manga_processing\">Obrada spremljene mange</string>\n    <string name=\"bookmarks\">Zabilješke</string>\n    <string name=\"bookmark_removed\">Zabilješka uklonjena</string>\n    <string name=\"dns_over_https\">DNS preko HTTPS-a</string>\n    <string name=\"disable_battery_optimization\">Onemogući optimizaciju baterije</string>\n    <string name=\"status_dropped\">Izbačeno</string>\n    <string name=\"appwidget_shelf_description\">Manga iz vaših favorita</string>\n    <string name=\"no_bookmarks_summary\">Možete stvoriti zabilješku dok čitate mangu</string>\n    <string name=\"empty\">Prazno</string>\n    <string name=\"exit_confirmation_summary\">Dvaput pritisnite Natrag za izlaz iz aplikacije</string>\n    <string name=\"exit_confirmation\">Potvrda izlaza</string>\n    <string name=\"storage_usage\">Korištenje pohrane</string>\n    <string name=\"text_clear_search_history_prompt\">Želite li trajno ukloniti sve nedavne upite za pretraživanje?</string>\n    <string name=\"welcome\">Dobrodošli</string>\n    <string name=\"backup_saved\">Sigurnosna kopija spremljena</string>\n    <string name=\"enabled\">Omogućeno</string>\n    <string name=\"preload_pages\">Prethodno učitavanje stranica</string>\n    <string name=\"empty_favourite_categories\">Nema omiljenih kategorija</string>\n    <string name=\"undo\">Poništi</string>\n    <string name=\"disable_all\">Onemogući sve</string>\n    <string name=\"use_fingerprint\">Koristite biometriju ako je dostupna</string>\n    <string name=\"appwidget_recent_description\">Vaša nedavno pročitana manga</string>\n    <string name=\"explore\">Istraži</string>\n    <string name=\"confirm_exit\">Ponovno pritisnite Natrag za izlaz</string>\n    <string name=\"saved_manga\">Spremljena manga</string>\n    <string name=\"pages_cache\">Predmemorija stranica</string>\n    <string name=\"other_cache\">Drugi cache</string>\n    <string name=\"available\">Dostupno</string>\n    <string name=\"disabled\">Onemogućeno</string>\n    <string name=\"default_mode\">Zadani modus</string>\n    <string name=\"detect_reader_mode_summary\">Automatski otkrij je li manga webtoon</string>\n    <string name=\"removed_from_favourites\">Uklonjeno iz favorita</string>\n    <string name=\"not_found_404\">Sadržaj nije pronađen ili je uklonjen</string>\n    <string name=\"no_chapters\">Nema poglavlja</string>\n    <string name=\"options\">Opcije</string>\n    <string name=\"download_slowdown\">Usporavanje preuzimanja</string>\n    <string name=\"logout\">Odjavite se</string>\n    <string name=\"reorder\">Rasporedi</string>\n    <string name=\"incognito_mode\">Anonimni modus</string>\n    <string name=\"suggestions_excluded_genres\">Ispostavite žanrove</string>\n    <string name=\"text_delete_local_manga_batch\">Trajno izbrisati odabrane stavke s uređaja?</string>\n    <string name=\"removal_completed\">Uklanjanje dovršeno</string>\n    <string name=\"automatic_scroll\">Automatsko listanje</string>\n    <string name=\"screenshots_block_nsfw\">Blokiraj na NSFW-u</string>\n    <string name=\"suggestions_enable\">Omogući prijedloge</string>\n    <string name=\"suggestions_summary\">Predložiti mangu na temelju vaših preferencija</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"find_similar\">Pronađite slično</string>\n    <string name=\"scrobbling_empty_hint\">Za praćenje napretka čitanja odaberite Meni → Prati na zaslonu s detaljima mange.</string>\n    <string name=\"sources_reorder_tip\">Dodirnite i držite stavku da biste im promijenili redoslijed</string>\n    <string name=\"settings_apply_restart_required\">Ponovno pokrenite aplikaciju kako biste primijenili ove promjene</string>\n    <string name=\"folder_with_images_import_description\">Možete odabrati direktorij s arhivama ili slikama. Svaka arhiva (ili poddirektorij) bit će prepoznata kao poglavlje.</string>\n    <string name=\"sync_settings\">Postavke sinkronizacije</string>\n    <string name=\"nothing_here\">Ovdje nema ničega</string>\n    <string name=\"user_agent\">Zaglavlje UserAgent-a</string>\n    <string name=\"comics_archive_import_description\">Možete odabrati jednu ili više .cbz ili .zip datoteka, svaka će datoteka biti prepoznata kao zasebna manga.</string>\n    <string name=\"show_on_shelf\">Prikaži na polici</string>\n    <string name=\"sync_auth_hint\">Možete se prijaviti na postojeći račun ili stvoriti novi</string>\n    <string name=\"text_downloads_list_holder\">Nemate preuzimanja</string>\n    <string name=\"downloads_cancelled\">Preuzimanja su otkazana</string>\n    <string name=\"clear_network_cache\">Očisti mrežnu predmemoriju</string>\n    <string name=\"address\">Adresa</string>\n    <string name=\"images_proxy_title\">Proxy za optimizaciju slika</string>\n    <string name=\"webtoon_zoom_summary\">Dopusti gestu zumiranja u modusu webtoona</string>\n    <string name=\"download_option_all_chapters\">Sva poglavlja s prijevodom %s</string>\n    <string name=\"this_month\">Ovaj mjesec</string>\n    <string name=\"color_light\">Svjetla</string>\n    <string name=\"color_white\">Bijela</string>\n    <string name=\"background\">Pozadina</string>\n    <string name=\"data_not_restored_text\">Provjerite jeste li odabrali ispravnu datoteku sigurnosne kopije</string>\n    <string name=\"manage_categories\">Upravljanje kategorijama</string>\n    <string name=\"suggestions_wifi_only_summary\">Nemojte ažurirati prijedloge pomoću mrežnih veza s ograničenim protokom</string>\n    <string name=\"languages\">Jezici</string>\n    <string name=\"directories\">Direktorij</string>\n    <string name=\"main_screen_sections\">Odjeljci glavnog zaslona</string>\n    <string name=\"items_limit_exceeded\">Nije moguće dodati više stavki</string>\n    <string name=\"moved_to_top\">Premješteno na vrh</string>\n    <string name=\"frequency_every_2_days\">Svaka 2 dana</string>\n    <string name=\"manual\">Ručno</string>\n    <string name=\"error_search_not_supported\">Ovaj izvor mange ne podržava pretraživanje</string>\n    <string name=\"skip\">Preskoči</string>\n    <string name=\"grayscale\">Sivi tonovi</string>\n    <string name=\"globally\">Globalno</string>\n    <string name=\"this_manga\">Ova manga</string>\n    <string name=\"color_correction_apply_text\">Ove postavke mogu se primijeniti globalno ili samo na trenutnu mangu. Ako se primjenjuju globalno, pojedinačne postavke neće biti zamjenjene.</string>\n    <string name=\"welcome_text\">Odaberite koje izvore sadržaja želite omogućiti. To se također može konfigurirati kasnije u postavkama</string>\n    <string name=\"sync_auth\">Prijavite se za sinkronizaciju računa</string>\n    <string name=\"prev_page\">Prethodna stranica</string>\n    <string name=\"reader_actions\">Radnje čitatelja</string>\n    <string name=\"reader_actions_summary\">Konfigurirajte radnje za dodirna područja zaslona</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Koristite tipke za glasnoću za prebacivanje stranica</string>\n    <string name=\"tap_action\">Radnja pri dodiru</string>\n    <string name=\"long_tap_action\">Radnja pri dugom dodiru</string>\n    <string name=\"use_two_pages_landscape\">Koristi raspored dviju stranice u poleženom položaju (beta)</string>\n    <string name=\"download_option_next_unread_n_chapters\">Sljedeći nepročitani %s</string>\n    <string name=\"download_option_all_unread\">Sva nepročitana poglavlja</string>\n    <string name=\"download_option_all_unread_b\">Sva nepročitana poglavlja (%s)</string>\n    <string name=\"download_option_manual_selection\">Odaberite poglavlja ručno</string>\n    <string name=\"pick_custom_directory\">Odaberite prilagođeni direktorij</string>\n    <string name=\"no_access_to_file\">Nemate pristup ovoj datoteci ili direktoriju</string>\n    <string name=\"local_manga_directories\">Lokalni direktoriji mange</string>\n    <string name=\"voice_search\">Glasovno pretraživanje</string>\n    <string name=\"related_manga\">Povezana manga</string>\n    <string name=\"color_dark\">Tamna</string>\n    <string name=\"color_black\">Crna</string>\n    <string name=\"data_not_restored\">Podaci nisu vraćeni</string>\n    <string name=\"on_device\">Na uređaju</string>\n    <string name=\"frequency_once_per_week\">Jednom tjedno</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"manage_sources\">Upravljanje izvorima</string>\n    <string name=\"error_multiple_states_not_supported\">Ovaj izvor mange ne podržava filtriranje prema višestrukim stanjima</string>\n    <string name=\"downloads_settings_info\">Možete omogućiti usporavanje preuzimanja za svaki izvor mange zasebno u postavkama izvora ako imate problema s blokiranjem na strani poslužitelja</string>\n    <string name=\"restore\">Vrati</string>\n    <string name=\"switch_pages_volume_buttons\">Omogući tipke za glasnoću</string>\n    <string name=\"none\">Nijedan</string>\n    <string name=\"config_reset_confirm\">Vratiti postavke na zadane vrijednosti? Ova se radnja ne može poništiti.</string>\n    <string name=\"clear_stats_confirm\">Želite li stvarno izbrisati sve statistike čitanja? Ova se radnja ne može poništiti.</string>\n    <string name=\"month\">Mjesec</string>\n    <string name=\"pages_read_s\">Pročitane stranice: %s</string>\n    <string name=\"no_chapters_deleted\">Nijedno poglavlje nije izbrisano</string>\n    <string name=\"downloaded\">Preuzeto</string>\n    <string name=\"images_procy_description\">Koristite uslugu wsrv.nl kako biste smanjili promet i ubrzali učitavanje slika ako je moguće</string>\n    <string name=\"unknown\">Nepoznato</string>\n    <string name=\"too_many_requests_message\">Previše zahtjeva. Pokušajte ponovno kasnije</string>\n    <string name=\"manga_list\">Popis mangi</string>\n    <string name=\"error_corrupted_file\">Vraćeni su nevažeći podaci ili je datoteka oštećena</string>\n    <string name=\"frequency_twice_per_month\">Dva puta mjesečno</string>\n    <string name=\"frequency_once_per_month\">Jednom mjesečno</string>\n    <string name=\"available_d\">Dostupno: %1$d</string>\n    <string name=\"genres_exclude\">Isključi žanrove</string>\n    <string name=\"week\">Tjedan</string>\n    <string name=\"day\">Dan</string>\n    <string name=\"alternatives\">Alternative</string>\n    <string name=\"periodic_backups_enable\">Omogućite povremene sigurnosne kopije</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Moglo bi vam pomoći pri započinjanju preuzimanja ako imate problema s njim</string>\n    <string name=\"all_time\">Cijelo vrijeme</string>\n    <string name=\"manga_migration\">Migracija mange</string>\n    <string name=\"migration_completed\">Migracija dovršena</string>\n    <string name=\"in_progress\">U toku</string>\n    <string name=\"disable_nsfw\">Onemogući NSFW</string>\n    <string name=\"related_manga_summary\">Prikaži popis povezanih mangi. U nekim slučajevima može biti netočan ili nedostajati</string>\n    <string name=\"advanced\">Napredno</string>\n    <string name=\"backups_output_directory\">Izlazni direktorij sigurnosne kopije</string>\n    <string name=\"three_months\">Tri mjeseca</string>\n    <string name=\"empty_stats_text\">Nema statistike za odabrano razdoblje</string>\n    <string name=\"last_successful_backup\">Zadnja uspješna sigurnosna kopija: %s</string>\n    <string name=\"migrate\">Migrirati</string>\n    <string name=\"chapters_deleted_pattern\">Uklonjeno %1$s, izbrisano %2$s</string>\n    <string name=\"lock_screen_rotation\">Zaključavanje zakretanja zaslona</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" iz \\\"%2$s\\\" bit će zamijenjena s \\\"%3$s\\\" iz \\\"%4$s\\\" u vašoj povijesti i favoritima (ako postoje)</string>\n    <string name=\"delete_read_chapters\">Brisanje pročitanih poglavlja</string>\n    <string name=\"crop_pages\">Izreži stranice</string>\n    <string name=\"sources_unpinned\">Izvori otkvačeni</string>\n    <string name=\"pin\">Prikvači</string>\n    <string name=\"unpin\">Otkvači</string>\n    <string name=\"source_pinned\">Izvor prikvačen</string>\n    <string name=\"source_unpinned\">Izvor otkvačen</string>\n    <string name=\"recent_sources\">Nedavni izvori</string>\n    <string name=\"external_source\">Eksterni/dodatak</string>\n    <string name=\"image_server\">Preferirani server slika</string>\n    <string name=\"sources_pinned\">Izvori prikvačeni</string>\n    <string name=\"percent_read\">Postotak pročitano</string>\n    <string name=\"percent_left\">Postotak preostalo</string>\n    <string name=\"chapters_read\">Pročitana poglavlja</string>\n    <string name=\"chapters_left\">Preostala poglavlja</string>\n    <string name=\"plugin_incompatible\">Nekompatibilni dodatak ili interna pogreška. Provjerite koristite li najnoviju verziju dodatka i Kotatsu</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Nema manga koji odgovaraju odabranim filtrima</string>\n    <string name=\"connection_ok\">Veza je u redu</string>\n    <string name=\"invalid_proxy_configuration\">Neispravna konfiguracija proksija</string>\n    <string name=\"show_quick_filters\">Prikaži brze filtre</string>\n    <string name=\"show_quick_filters_summary\">Pruža mogućnost filtriranja popisa manga prema određenim parametrima</string>\n    <string name=\"invalid_server_address_message\">Nevažeća adresa servera</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"retry\">Pokušaj ponovo</string>\n    <string name=\"too_many_requests_message_retry\">Previše zahtjeva. Pokušajte ponovno nakon %s</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"minutes_seconds_short\">%1$d min %2$d s</string>\n    <string name=\"skip_all\">Preskoči sve</string>\n    <string name=\"not_in_favorites\">Nije u favoritima</string>\n    <string name=\"unpopular\">Nepopularno</string>\n    <string name=\"stuck\">Zastoj</string>\n    <string name=\"low_rating\">Niska ocjena</string>\n    <string name=\"sort_order_asc\">Uzlazno</string>\n    <string name=\"sort_order_desc\">Silazno</string>\n    <string name=\"by_date\">Datum</string>\n    <string name=\"popularity\">Popularnost</string>\n    <string name=\"updated_long_ago\">Aktualizirano davno</string>\n    <string name=\"manga_replaced\">Manga „%1$s” (%2$s) zamijenjen sa „%3$s” (%4$s)</string>\n    <string name=\"downloads_background\">Preuzimanja u pozadini</string>\n    <string name=\"download_new_chapters\">Preuzmi nova poglavlja</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga s preuzetim poglavljima</string>\n    <string name=\"scrobbler_auth_required\">Prijavite na %s za nastavljanje</string>\n    <string name=\"scrobbler_auth_intro\">Prijavite se za postavljanje integracije s %s. To će vam omogućiti pratiti napredak i statnje čitanja mange</string>\n    <string name=\"content_type_novel\">Roman</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"recently_added\">Nedavno dodano</string>\n    <string name=\"added_long_ago\">Dodano davno</string>\n    <string name=\"filter_search_warning\">Ovaj izvor ne podržava pretraživanje s filtrima. Vaši filtri su izbrisani</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"pages_saved\">Stranice su spremljene</string>\n    <string name=\"unstable_feature\">Nestabilna funkcija</string>\n    <string name=\"no_fix_required\">%s ne zahtijeva popravljanje</string>\n    <string name=\"no_alternatives_found\">Za %s nisu pronađene alternative</string>\n    <string name=\"popular_in_hour\">Popularno u ovom satu</string>\n    <string name=\"popular_today\">Popularno danas</string>\n    <string name=\"popular_in_month\">Popularno ovaj mjesec</string>\n    <string name=\"popular_in_year\">Popularno ove godine</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"years\">Godine</string>\n    <string name=\"any\">Bilo koje</string>\n    <string name=\"content_type_one_shot\">Bez nastavka</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"demographics\">Demografija</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"original_language\">Izvorni jezik</string>\n    <string name=\"year\">Godina</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"error_not_image\">Neispravni format: očekuje se slika, ali je dobiveno %s</string>\n    <string name=\"start_download\">Pokreni preuzimanje</string>\n    <string name=\"save_manga_confirm\">Spremiti odabranu mangu? To može trošiti mrežni promet i memoriju na disku</string>\n    <string name=\"genre\">Žanr</string>\n    <string name=\"chapter_selection_hint\">Poglavlja se mogu preuzeti dugim klikom na stavku u popisu poglavlja.</string>\n    <string name=\"more_options\">Više opcija</string>\n    <string name=\"chapters_all\">Sva</string>\n    <string name=\"download_over_cellular\">Preuzimanje preko mobilne mreže</string>\n    <string name=\"download_cellular_confirm\">Dozvoliti preuzimanja preko mobilne mreže?</string>\n    <string name=\"ask_every_time\">Pitaj svaki put</string>\n    <string name=\"screen_orientation\">Položaj ekrana</string>\n    <string name=\"portrait\">Uspravno</string>\n    <string name=\"dont_allow\">Nemoj dozvoliti</string>\n    <string name=\"allow_always\">Dozvoli uvijek</string>\n    <string name=\"allow_once\">Dozvoli jednom</string>\n    <string name=\"landscape\">Položeno</string>\n    <string name=\"access_denied_403\">Pristup odbijen (403)</string>\n    <string name=\"max_backups_count\">Maksimalni broj sigurnosnih kopija</string>\n    <string name=\"delete_old_backups\">Izbriši stare sigurnosne kopije</string>\n    <string name=\"delete_old_backups_summary\">Automatski izbriši stare sigurnosne kopije datoteka radi uštede memorije</string>\n    <string name=\"error_image_format\">Nepodržani format slika: %s</string>\n    <string name=\"content_type_game_cg\">Igra (generirano od računala)</string>\n    <string name=\"user_manual\">Upute</string>\n    <string name=\"debug\">Otklanjanje grešaka</string>\n    <string name=\"plugin_incompatible_with_cause\">Greška dodatka: %s\\nProvjerite koristite li najnoviju verziju dodatka i aplikacije Kotatsu</string>\n    <string name=\"unstable_feature_summary\">Ova je funkcija eksperimentalna. Provjerite imate li sigurnosnu kopiju kako biste izbjegli gubljenje podataka</string>\n    <string name=\"popular_in_week\">Popularno ovaj tjedan</string>\n    <string name=\"content_type_artist_cg\">Umjetnik (generirano od računala)</string>\n    <string name=\"content_type_image_set\">Skup slika</string>\n    <string name=\"source_code\">Izvorni kod</string>\n    <string name=\"telegram_group\">Telegram grupa</string>\n    <string name=\"save_manga\">Spremi manga</string>\n    <string name=\"download_added\">Preuzimanje je dodano</string>\n    <string name=\"fixing_manga\">Popravljanje mange</string>\n    <string name=\"fixed\">Uspješno popravljeno</string>\n    <string name=\"manga_fix_prompt\">Ova funkcija pronalazi alternativne izvore za odabranu mangu. Zadatak će potrajati i odvijat će se u pozadini</string>\n    <string name=\"destination_directory\">Odredišni direktorij</string>\n    <string name=\"error_connection_reset\">Udaljeni host je resetirao vezu</string>\n    <string name=\"show_slider\">Prikaži klizač</string>\n    <string name=\"incognito\">Inkognito</string>\n    <string name=\"backup_tg_echo\">Probna poruka</string>\n    <string name=\"backup_tg_id_not_set\">Chat ID nije postavljen</string>\n    <string name=\"backup_tg_check\">Provjeri je li API radi</string>\n    <string name=\"open_telegram_bot\">Otvori Telegram bot</string>\n    <string name=\"captcha_required_message\">Ovaj izvor zahtijeva rješavanje captcha zadatka za nastavljanje</string>\n    <string name=\"email\">E-mail</string>\n    <string name=\"clear_database\">Izbriši bazu podataka</string>\n    <string name=\"clear_database_summary\">Izbriši informacije o mangama koje se ne koriste</string>\n    <string name=\"test_connection\">Provjeri vezu</string>\n    <string name=\"send_backups_telegram\">Pošalji sigurnosne kopije u Telegram</string>\n    <string name=\"telegram_chat_id_summary\">Upiši ID chata na koji treba slati sigurnosne kopije</string>\n    <string name=\"open_telegram_bot_summary\">Pritisni za otvaranje chata s Kotatsu Backup Bot</string>\n    <string name=\"translation\">Prijevod</string>\n    <string name=\"rating\">Ocjena</string>\n    <string name=\"source\">Izvor</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"telegram_chat_id\">Telegram chat ID</string>\n    <string name=\"backup_restored_background\">Sigurnosna kopija će se obnoviti u pozadini</string>\n    <string name=\"restoring_backup\">Obnavljanje sigurnosne kopije</string>\n    <string name=\"disable_captcha_notifications_summary\">Nećeš primati obavijesti o rješavanju CAPTCHA za ovaj izvor, ali to može prouzročiti prekid pozadinskih operacija (provjeravanje novih poglavlja, dobivanje preporuka itd.)</string>\n    <string name=\"reader_info_bar_transparent\">Transparentna traka s informacijama čitača</string>\n    <string name=\"enable_all_sources\">Aktiviraj sve manga izvore</string>\n    <string name=\"enable_all_sources_summary\">Svi manga izvori će biti trajno aktivirani</string>\n    <string name=\"all_sources_enabled\">Svi izvori su aktivirani</string>\n    <string name=\"handle_links\">Barataj poveznicama</string>\n    <string name=\"handle_links_summary\">Barataj manga poveznicama iz eksternih aplikacija (npr. web preglednik). Možda ćeš to također morati ručno aktivirati u postavkama sustava aplikacije</string>\n    <string name=\"simple\">Jednostavno</string>\n    <string name=\"reader_controls_in_bottom_bar\">Kontrole čitača u donjoj traci</string>\n    <string name=\"chapters_and_pages\">Poglavlja i stranice</string>\n    <string name=\"pages_slider\">Klizač za listanje stranica</string>\n    <string name=\"screen_rotation_locked\">Okretanje ekrana je zaključano</string>\n    <string name=\"screen_rotation_unlocked\">Okretanje ekrana je otključano</string>\n    <string name=\"disable_captcha_notifications\">Deaktiviraj Captcha obavijesti</string>\n    <string name=\"global_search\">Globalna pretraga</string>\n    <string name=\"search_everywhere\">Traži svuda</string>\n    <string name=\"badges_in_lists\">Značke u popisima</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"theme_name_expressive\">Ekspresivna (test)</string>\n    <string name=\"chapter_volume_number\">Svezak %1$s Poglavlje %2$s</string>\n    <string name=\"chapter_number\">Poglavlje %s</string>\n    <string name=\"unnamed_chapter\">Neimenovano poglavlje</string>\n    <string name=\"search_disabled_sources\">Pretraži deaktivirane izvore</string>\n    <string name=\"error_details\">Detalji greške</string>\n    <string name=\"error_disclaimer_manga\">Pokušaj otvoriti manga u web pregledniku kako bi provjerio/la je li dostupan na izvornom mjestu.</string>\n    <string name=\"error_disclaimer_app_outdated\">Izgleda da je tvoja Kotatsu verzija zastarjela. Instaliraj najnoviju verziju za dobivanje svih dostupnih ispravki.</string>\n    <string name=\"link_to_manga_in_app\">Poveznica na manga u Kotatsu</string>\n    <string name=\"link_to_manga_on_s\">Poveznica na manga na %s</string>\n    <string name=\"error_disclaimer_report\">Pošalji izvještaj o grešci programerima. To će nam pomoći da istražimo i ispravimo problem.</string>\n    <string name=\"clear_browser_data\">Izbriši podatke iz preglednika</string>\n    <string name=\"clear_browser_data_summary\">Izbriši podatke iz preglednika poput predmemorije i kolačića. Upozorenje: Autorizacija na izvorima mange može postati nevažeća</string>\n    <string name=\"no_write_permission_to_file\">Nema dozvolu za pisanje u datoteku</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Manga za odrasle se neće prikazivati u prijedlozima. Ova opcija možda neće točno funkcionirati s nekim izvorima</string>\n    <string name=\"include_disabled_sources\">Uključi deaktivirane izvore</string>\n    <string name=\"suggestions_disabled_sources_summary\">Prikaži prijedloge iz svih izvora mange, uključujući deaktivirane</string>\n    <string name=\"tags_warnings\">Istakni opasne žanrove</string>\n    <string name=\"tags_warnings_summary\">Istakni žanrove koji bi mogli biti neprikladni za većinu korisnika</string>\n    <string name=\"pull_to_prev_chapter\">Otpusti za otvaranje prethodnog poglavlja</string>\n    <string name=\"pull_to_next_chapter\">Otpusti za otvaranje sljedećeg poglavlja</string>\n    <string name=\"pull_top_no_prev\">Nema prethodnog poglavlja</string>\n    <string name=\"pull_bottom_no_next\">Nema sljedećeg poglavlja</string>\n    <string name=\"reader_navigation_inverted\">Preokreni navigacijske kontrole</string>\n    <string name=\"reader_navigation_inverted_summary\">Zamijeni gumb za glasnoću i fizičke navigacijske tipke (lijevo/gore/dolje/desno)</string>\n    <string name=\"enable_pull_gesture_title\">Aktiviraj geste povlačenja</string>\n    <string name=\"enable_pull_gesture_summary\">Aktiviraj geste povlačenja za mijenjanje poglavlja u webtoonu</string>\n    <string name=\"error_non_file_uri\">Odabrana staza se ne može koristiti jer ne označava datoteku ili direktorij</string>\n    <string name=\"manga_override_hint\">Ove promjene će utjecati na način prikaza mange u aplikaciji</string>\n    <string name=\"use_default_cover\">Koristi zadanu naslovnicu</string>\n    <string name=\"change_cover\">Promijeni naslovnicu</string>\n    <string name=\"pick_manga_page\">Odaberi stranicu mange</string>\n    <string name=\"pick_custom_file\">Odaberi prilagođenu datoteku</string>\n    <string name=\"page_switch_timer\">Stranica će se mijenjati svakih ~%d sekundi</string>\n    <string name=\"dont_ask_again\">Ne pitaj ponovo</string>\n    <string name=\"incognito_mode_hint_nsfw\">Ovaj manga može sadržati sadržaj za odrasle. Želiš li koristiti anonimni modus?</string>\n    <string name=\"incognito_for_nsfw\">Anonimni modus za mange s neprimjerenim sadržajem (NSFW)</string>\n    <string name=\"additional_action_required\">Potrebna je dodatna radnja</string>\n    <string name=\"hide_from_main_screen\">Sakrij s glavnog ekrana</string>\n    <string name=\"changelog\">Dnevnik promjena</string>\n    <string name=\"changelog_summary\">Povijest promjena za nedavno objavljene verzije</string>\n    <string name=\"collapse\">Sažmi</string>\n    <string name=\"expand\">Proširi</string>\n    <string name=\"adblock\">Blokiraj oglase u pregledniku</string>\n    <string name=\"adblock_summary\">Blokiraj oglase u ugrađenom pregledniku (beta)</string>\n    <string name=\"collapse_long_description\">Sažmi dugi opis</string>\n    <string name=\"creating_backup\">Izrada sigurnosne kopije</string>\n    <string name=\"share_backup\">Dijeli sigurnosnu kopiju</string>\n    <string name=\"reader_multitask\">Otvori čitač u zasebnom zadatku</string>\n    <string name=\"reader_multitask_summary\">Omogućuje istovremeno otvaranje više čitača s različitim mangama</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"book_effect\">Žućkasta pozadina (plavi filtar)</string>\n    <string name=\"local_storage_cleanup\">Čišćenje lokalne memorije</string>\n    <string name=\"packup_creation_failed\">Neuspjelo stvaranje sigurnosne kopije</string>\n    <string name=\"main_screen\">Glavni ekran</string>\n    <string name=\"main_screen_fab\">Prikaži plutajući gumb „Nastavi”</string>\n    <string name=\"main_screen_fab_summary\">Omogućuje nastavak čitanja jednim klikom. Ovaj se gumb neće prikazivati u anonimnom modusu ili kada je povijest prazna</string>\n    <string name=\"error_corrupted_zip\">Oštećena ZIP arhiva (%s)</string>\n    <string name=\"discord_token\">Discord token</string>\n    <string name=\"discord_token_description\">Unesi svoj Discord token ili klikni %s za dobivanje tokena putem preglednika</string>\n    <string name=\"discord_token_hint\">Ovdje umetni svoj Discord token</string>\n    <string name=\"discord_rpc_summary\">Prikaži svoje stanje čitanja na Discordu</string>\n    <string name=\"obtain\">Nabavi</string>\n    <string name=\"discord_rpc_description\">Čitanje manga na Kotatsu – aplikaciji za čitanje manga</string>\n    <string name=\"reading_s\">Čitaš %s</string>\n    <string name=\"read_on_s\">Čitaj na %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Ne koristi Rich Presence klijent (RPC) za sadržaj za odrasle</string>\n    <string name=\"invalid_token\">Nevažeći token: %s</string>\n    <string name=\"show_floating_control_button\">Prikaži plutajući gumb kontrola</string>\n    <string name=\"unavailable\">Nedostupno</string>\n    <string name=\"manga_restricted_description\">Ovaj manga nije dostupan za čitanje na ovom izvoru. Pokušaj ga potražiti u drugim izvorima ili otvoriti u pregledniku za više informacija</string>\n    <string name=\"no_chapters_in_manga\">Ovaj manga ne sadrži nijedno poglavlje</string>\n    <string name=\"chapters_load_failed\">Neuspjelo učitavanje popisa poglavlja</string>\n    <string name=\"telegram_integration\">Telegram integracija</string>\n    <string name=\"test_parser\">Testiraj izvor mange</string>\n    <string name=\"discord_rpc\">Discord Rich Presence</string>\n    <string name=\"discord_token_summary\">Unesi svoj Discord token za uključivanje Rich Presence podataka</string>\n    <string name=\"saved_filters\">Spremljeni filtri</string>\n    <string name=\"enter_name\">Upiši ime</string>\n    <string name=\"reader_chapter_toast_summary\">Prikaži skočnu poruku s naslovom poglavlja kada je promijenjen</string>\n    <string name=\"rename\">Preimenuj</string>\n    <string name=\"save_filter\">Spremi filtar</string>\n    <string name=\"reader_chapter_toast\">Prikaži skočnu poruku za promijenjeno poglavlje</string>\n    <string name=\"two_page_scroll_sensitivity\">Osjetljivost pomicanja dviju stranica</string>\n    <string name=\"frequency_every_6_hours\">Svakih 6 sati</string>\n    <string name=\"overwrite\">Prepiši</string>\n    <string name=\"filter_overwrite_confirm\">Filtar s imenom „%s” već postoji. Želiš li ga prepisati?</string>\n    <string name=\"storage_and_network\">Spremište i mreža</string>\n    <string name=\"create_or_restore_backup\">Stvori ili obnovi sigurnosnu kopiju</string>\n    <string name=\"data_removal\">Uklanjanje podataka</string>\n    <string name=\"privacy\">Privatnost</string>\n    <string name=\"source_broken_warning\">Ovaj izvor mange označen je kao pokvaren. Neke funkcije možda neće raditi</string>\n    <string name=\"download_default_directory\">Standardni direktorij za preuzimanje manga</string>\n    <string name=\"private_app_directory_warning\">Ako deinstaliraš aplikaciju, ovaj će se direktorij izbrisati sa svim podacima</string>\n    <string name=\"available_pattern\">%1$s dostupno</string>\n    <string name=\"pinned_sources_only\">Samo prikvačeni izvori</string>\n    <string name=\"hide_empty_sources\">Sakrij prazne izvore</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-hu/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d új fejezet</item>\n        <item quantity=\"other\">%1$d új fejezetek</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d fejezet</item>\n        <item quantity=\"other\">%1$d fejezetek</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d elem</item>\n        <item quantity=\"other\">%1$d elemek</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d perccel ezelőtt</item>\n        <item quantity=\"other\">%1$d perccel ezelőtt</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d órával ezelőtt</item>\n        <item quantity=\"other\">%1$d órával ezelőtt</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d nappal ezelőtt</item>\n        <item quantity=\"other\">%1$d nappal ezelőtt</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d hónappal ezelőtt</item>\n        <item quantity=\"other\">%1$d hónappal ezelőtt</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d óra</item>\n        <item quantity=\"other\">%1$d óra</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d perc</item>\n        <item quantity=\"other\">%1$d perc</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-hu/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"history\">Előzmények</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"detailed_list\">Részletes lista</string>\n    <string name=\"grid\">Rács</string>\n    <string name=\"list_mode\">Lista mód</string>\n    <string name=\"settings\">Beállítások</string>\n    <string name=\"remote_sources\">Manga források</string>\n    <string name=\"loading_\">Betöltés…</string>\n    <string name=\"computing_\">Számítás…</string>\n    <string name=\"chapter_d_of_d\">Fejezet %1$d / %2$d</string>\n    <string name=\"try_again\">Próbálja újra</string>\n    <string name=\"nothing_found\">Nem található semmi</string>\n    <string name=\"history_is_empty\">Nincs még előzmény</string>\n    <string name=\"read\">Olvas</string>\n    <string name=\"you_have_not_favourites_yet\">Nincsenek még kedvencek</string>\n    <string name=\"add\">Hozzáadás</string>\n    <string name=\"save\">Mentés</string>\n    <string name=\"share\">Megosztás</string>\n    <string name=\"create_shortcut\">Parancsikon létrehozása…</string>\n    <string name=\"share_s\">Megosztás %s</string>\n    <string name=\"search\">Keresés</string>\n    <string name=\"search_manga\">Manga keresése</string>\n    <string name=\"processing_\">Feldolgozás…</string>\n    <string name=\"download_complete\">Letöltött</string>\n    <string name=\"downloads\">Letöltések</string>\n    <string name=\"by_name\">Név</string>\n    <string name=\"updated\">Frissített</string>\n    <string name=\"newest\">Legújabb</string>\n    <string name=\"sort_order\">Rendezési sorrend</string>\n    <string name=\"by_rating\">Értékelés</string>\n    <string name=\"filter\">Szűrő</string>\n    <string name=\"light\">Fényes</string>\n    <string name=\"follow_system\">Rendszer alapján</string>\n    <string name=\"clear\">Törlés</string>\n    <string name=\"remove\">Eltávolítás</string>\n    <string name=\"save_page\">Oldal mentése</string>\n    <string name=\"share_image\">Kép megosztása</string>\n    <string name=\"_import\">Importálás</string>\n    <string name=\"operation_not_supported\">Ez a művelet nem engedélyezett</string>\n    <string name=\"text_file_not_supported\">Válassz ZIP vagy CBZ fájlt.</string>\n    <string name=\"no_description\">Nincs leírás</string>\n    <string name=\"local_storage\">Helyi tárhely</string>\n    <string name=\"favourites\">Kedvencek</string>\n    <string name=\"error_occurred\">Hiba történt</string>\n    <string name=\"network_error\">Hálózati hiba</string>\n    <string name=\"close\">Bezár</string>\n    <string name=\"details\">Részletek</string>\n    <string name=\"chapters\">Fejezetek</string>\n    <string name=\"clear_history\">Előzmények törlése</string>\n    <string name=\"add_to_favourites\">Kedvencekhez adás</string>\n    <string name=\"add_new_category\">Új kategória</string>\n    <string name=\"manga_downloading_\">Letöltés…</string>\n    <string name=\"popular\">Népszerű</string>\n    <string name=\"theme\">Téma</string>\n    <string name=\"dark\">Sötét</string>\n    <string name=\"pages\">Oldalak</string>\n    <string name=\"_s_deleted_from_local_storage\">%s törölve lett a helyi tárhelyből</string>\n    <string name=\"page_saved\">Mentve</string>\n    <string name=\"delete\">Törlés</string>\n    <string name=\"recent_manga\">Legutóbbi</string>\n    <string name=\"updates_feed_cleared\">Törölve</string>\n    <string name=\"not_available\">Nem elérhető</string>\n    <string name=\"done\">Kész</string>\n    <string name=\"all_favourites\">Összes kedvenc</string>\n    <string name=\"favourites_category_empty\">Üres kategória</string>\n    <string name=\"create_category\">Új kategória</string>\n    <string name=\"create_backup\">Adatmentés létrehozása</string>\n    <string name=\"restore_backup\">Helyreállítás biztonsági mentésből</string>\n    <string name=\"yesterday\">Tegnap</string>\n    <string name=\"group\">Csoport</string>\n    <string name=\"always\">Mindig</string>\n    <string name=\"preload_pages\">Oldalak előtöltése</string>\n    <string name=\"suggestions_excluded_genres_summary\">Határozz meg műfajokat, amiket nem akarsz látni a javaslatokban</string>\n    <string name=\"canceled\">Megszakítva</string>\n    <string name=\"email_enter_hint\">Add meg az email címed a folytatáshoz</string>\n    <string name=\"clear_pages_cache\">Oldal gyorsítótár törlése</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Olvasási mód</string>\n    <string name=\"grid_size\">Rács mérete</string>\n    <string name=\"search_on_s\">Keresés %s-en</string>\n    <string name=\"delete_manga\">Manga törlése</string>\n    <string name=\"reader_settings\">Olvasó beállítások</string>\n    <string name=\"switch_pages\">Oldalak váltása</string>\n    <string name=\"_continue\">Folytatás</string>\n    <string name=\"error\">Hiba</string>\n    <string name=\"clear_thumbs_cache\">Miniatűrök gyorsítótárának törlése</string>\n    <string name=\"clear_search_history\">Keresési előzmények törlése</string>\n    <string name=\"search_history_cleared\">Törölve</string>\n    <string name=\"internal_storage\">Belső tárhely</string>\n    <string name=\"external_storage\">Külső tárhely</string>\n    <string name=\"domain\">Tartomány</string>\n    <string name=\"app_update_available\">Elérhető az alkalmazás egy új verziója</string>\n    <string name=\"open_in_browser\">Megnyitás böngészőben</string>\n    <string name=\"notifications\">Értesítések</string>\n    <string name=\"new_chapters\">Új fejezetek</string>\n    <string name=\"download\">Letöltés</string>\n    <string name=\"notifications_settings\">Értesítési beállítások</string>\n    <string name=\"notification_sound\">Értesítési hang</string>\n    <string name=\"light_indicator\">LED jelzőfény</string>\n    <string name=\"vibration\">Rezgés</string>\n    <string name=\"favourites_categories\">Kedvenc kategóriák</string>\n    <string name=\"remove_category\">Eltávolítás</string>\n    <string name=\"text_empty_holder_primary\">Kicsit üres itt…</string>\n    <string name=\"text_search_holder_secondary\">Próbáld meg átalakítani a kérdést.</string>\n    <string name=\"text_local_holder_primary\">Először ments el valamit</string>\n    <string name=\"manga_shelf\">Polc</string>\n    <string name=\"pages_animation\">Oldal animáció</string>\n    <string name=\"manga_save_location\">Letöltések mappa</string>\n    <string name=\"cannot_find_available_storage\">Nincs rendelkezésre álló tárhely</string>\n    <string name=\"other_storage\">Egyéb tárhely</string>\n    <string name=\"read_later\">Olvasás később</string>\n    <string name=\"updates\">Frissítések</string>\n    <string name=\"search_results\">eresési eredmények</string>\n    <string name=\"new_version_s\">Új verzió: %s</string>\n    <string name=\"size_s\">Méret: %s</string>\n    <string name=\"clear_updates_feed\">Frissítési előzmények törlése</string>\n    <string name=\"rotate_screen\">Képernyő elforgatása</string>\n    <string name=\"update\">Frissítés</string>\n    <string name=\"track_sources\">Frissítések keresése</string>\n    <string name=\"dont_check\">Ne ellenőrizd</string>\n    <string name=\"enter_password\">Add meg a jelszót</string>\n    <string name=\"wrong_password\">Helytelen jelszó</string>\n    <string name=\"passwords_mismatch\">Nem egyező jelszavak</string>\n    <string name=\"about\">Névjegy</string>\n    <string name=\"app_version\">Verzió %s</string>\n    <string name=\"check_for_updates\">Frissítések keresése</string>\n    <string name=\"no_update_available\">Nincsenek rendelkezésre álló frissítések</string>\n    <string name=\"right_to_left\">Jobbról balra</string>\n    <string name=\"scale_mode\">Méretezési mód</string>\n    <string name=\"zoom_mode_fit_center\">Középre igazítás</string>\n    <string name=\"zoom_mode_fit_height\">Igazítás a magassághoz</string>\n    <string name=\"zoom_mode_fit_width\">Igazítás a szélességhez</string>\n    <string name=\"black_dark_theme\">Fekete</string>\n    <string name=\"black_dark_theme_summary\">Kevesebb energiát használ AMOLED képernyőkön</string>\n    <string name=\"data_restored\">Helyreállítva</string>\n    <string name=\"backup_restore\">Mentés és helyreállítás</string>\n    <string name=\"preparing_\">Előkészítés…</string>\n    <string name=\"file_not_found\">A fájl nem található</string>\n    <string name=\"data_restored_success\">Minden adat helyreállításra került</string>\n    <string name=\"data_restored_with_errors\">Az adatok helyreállításra kerültek, de hibák találhatók</string>\n    <string name=\"just_now\">Épp most</string>\n    <string name=\"long_ago\">Régen</string>\n    <string name=\"today\">Ma</string>\n    <string name=\"tap_to_try_again\">Koppintson újrapróbálkozáshoz</string>\n    <string name=\"disabled\">Letiltva</string>\n    <string name=\"reset_filter\">Szűrő visszaállítása</string>\n    <string name=\"onboard_text\">Válaszd ki azokat a nyelveket, amelyeken mangát szeretnél olvasni. Később megváltoztathatod a beállításokban.</string>\n    <string name=\"never\">Soha</string>\n    <string name=\"only_using_wifi\">Csak Wi-Fi-n</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Különböző nyelvek</string>\n    <string name=\"chapters_empty\">Nincsenek fejezetek ebben a mangában</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Megjelenés</string>\n    <string name=\"suggestions_updating\">Javaslatok frissülnek</string>\n    <string name=\"suggestions_excluded_genres\">Műfajok kizárása</string>\n    <string name=\"text_delete_local_manga_batch\">A kiválasztott elemek véglegesen törlődnek a készülékről?</string>\n    <string name=\"removal_completed\">Eltávolítás befejezve</string>\n    <string name=\"download_slowdown\">Letöltési késleltetés</string>\n    <string name=\"download_slowdown_summary\">Segít elkerülni az IP-címed blokkolását</string>\n    <string name=\"local_manga_processing\">Mentett manga feldolgozása</string>\n    <string name=\"chapters_will_removed_background\">A fejezetek a háttérben lesznek eltávolítva</string>\n    <string name=\"account_already_exists\">A fiók már létezik</string>\n    <string name=\"back\">Vissza</string>\n    <string name=\"sync\">Szinkronizálás</string>\n    <string name=\"sync_title\">Szinkronizáld az adataidat</string>\n    <string name=\"hide\">Elrejt</string>\n    <string name=\"new_sources_text\">Új manga források érhetőek el</string>\n    <string name=\"check_new_chapters_title\">Keresés új fejezetek után és értesülni róla</string>\n    <string name=\"logged_in_as\">Bejelentkezve mint %s</string>\n    <string name=\"bookmarks_removed\">Könyvjelzők törölve</string>\n    <string name=\"no_manga_sources\">Nincsenek manga források</string>\n    <string name=\"no_chapters\">Nincsenek fejezetek</string>\n    <string name=\"logout\">Kijelentkezés</string>\n    <string name=\"bookmark_add\">Könyvjelző hozzáadása</string>\n    <string name=\"bookmark_removed\">Könyvjelző eltávolítva</string>\n    <string name=\"disable_battery_optimization_summary\">Segít a háttérfrissítésekben</string>\n    <string name=\"crash_text\">Valami hiba történt. Kérlek, küldj hibajelentést a fejlesztőknek, hogy segíthessünk a hiba elhárításában.</string>\n    <string name=\"status_planned\">Tervezett</string>\n    <string name=\"status_reading\">Olvasás</string>\n    <string name=\"status_re_reading\">Újraolvasás</string>\n    <string name=\"status_completed\">Befejezve</string>\n    <string name=\"status_on_hold\">Függőben</string>\n    <string name=\"use_fingerprint\">Ujjlenyomat-azonosítást használata, ha elérhető</string>\n    <string name=\"invalid_domain_message\">Hibás domain</string>\n    <string name=\"select_range\">Válaszd ki a tartományt</string>\n    <string name=\"reader_info_bar\">Információs sáv megjelenítése az olvasóban</string>\n    <string name=\"folder_with_images\">Mappa képekkel</string>\n    <string name=\"importing_manga\">Manga importálása</string>\n    <string name=\"comics_archive\">Képregény archívum</string>\n    <string name=\"import_completed\">Importálás befejezve</string>\n    <string name=\"import_will_start_soon\">Az importálás hamarosan elkezdődik</string>\n    <string name=\"feed\">Hírfolyam</string>\n    <string name=\"import_completed_hint\">Törölheted az eredeti fájlt a tárolóból, hogy helyet spórolj</string>\n    <string name=\"reader_control_ltr\">Ergonomikus olvasóvezérlés</string>\n    <string name=\"color_correction\">Színkorrekció</string>\n    <string name=\"brightness\">Fényerő</string>\n    <string name=\"webtoon_zoom\">Webtoon zoomolás</string>\n    <string name=\"automatic\">Rendszer alapján</string>\n    <string name=\"text_delete_local_manga\">\\\"%s\\\" véglegesen törölve legyen az eszközről?</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d aktiválva a %2$d-ból</string>\n    <string name=\"text_history_holder_primary\">Amit épp olvasol, az itt jelenik meg</string>\n    <string name=\"text_history_holder_secondary\">Találd meg, mit olvashatsz a «Felfedezés» szekcióban</string>\n    <string name=\"text_local_holder_secondary\">Ments el valamit egy online katalógusból, vagy importálj egy fájlból.</string>\n    <string name=\"text_feed_holder\">Az általad olvasott mangák új fejezetei itt jelennek meg</string>\n    <string name=\"feed_will_update_soon\">A hírfolyam frissítése hamarosan elkezdődik</string>\n    <string name=\"protect_application\">Alkalmazás védése</string>\n    <string name=\"protect_application_summary\">Jelszó kérése a Kotatsu indításakor</string>\n    <string name=\"repeat_password\">Ismételd meg a jelszót</string>\n    <string name=\"backup_information\">Létrehozhatsz egy biztonsági másolatot az előzményeidről és kedvenceidről, majd visszaállíthatod</string>\n    <string name=\"reader_mode_hint\">A választott beállításokat megjegyezzük ehhez a mangához</string>\n    <string name=\"silent\">Néma</string>\n    <string name=\"captcha_required\">CAPTCHA szükséges</string>\n    <string name=\"clear_cookies\">Sütik törlése</string>\n    <string name=\"captcha_solve\">Megoldani</string>\n    <string name=\"cookies_cleared\">Az összes süti törölve lett</string>\n    <string name=\"text_clear_updates_feed_prompt\">A teljes frissítési előzmény törlésre kerüljön?</string>\n    <string name=\"clear_feed\">Hírfolyam törlése</string>\n    <string name=\"check_for_new_chapters\">Új fejezetek keresése</string>\n    <string name=\"reverse\">Megfordít</string>\n    <string name=\"sign_in\">Belépés</string>\n    <string name=\"auth_required\">Belépés szükséges a tartalom megtekintéséhez</string>\n    <string name=\"default_s\">Alap: %s</string>\n    <string name=\"next\">Tovább</string>\n    <string name=\"protect_application_subtitle\">Add meg azt a jelszót, amivel az alkalmazást indítani szeretnéd</string>\n    <string name=\"confirm\">Megerősít</string>\n    <string name=\"password_length_hint\">A jelszónak legalább 4 karakterből kell állnia</string>\n    <string name=\"text_clear_search_history_prompt\">Az összes legutóbbi keresési lekérdezés törlésre kerüljön?</string>\n    <string name=\"welcome\">Üdvözlünk</string>\n    <string name=\"backup_saved\">Mentés sikeresen elmentve</string>\n    <string name=\"tracker_warning\">Néhány készülék rendszere eltérően működik, ami megszakíthatja a háttérfeladatokat.</string>\n    <string name=\"read_more\">Tudj meg többet</string>\n    <string name=\"queued\">Sorba állítva</string>\n    <string name=\"chapter_is_missing\">A fejezet hiányzik</string>\n    <string name=\"about_app_translation_summary\">Fordítsd le ezt az alkalmazást</string>\n    <string name=\"notifications_enable\">Értesítések engedélyezése</string>\n    <string name=\"name\">Név</string>\n    <string name=\"edit\">Szerkesztés</string>\n    <string name=\"edit_category\">Kategória szerkesztése</string>\n    <string name=\"tracking\">Külső platformok (követés)</string>\n    <string name=\"empty_favourite_categories\">Nincsenek kedvenc kategóriák</string>\n    <string name=\"bookmark_remove\">Könyvjelző törlése</string>\n    <string name=\"bookmarks\">Könyvjelzők</string>\n    <string name=\"bookmark_added\">Könyvjelző hozzáadva</string>\n    <string name=\"undo\">Visszavonás</string>\n    <string name=\"removed_from_history\">Eltávolítva az előzményekből</string>\n    <string name=\"dns_over_https\">DNS HTTPS-en keresztül</string>\n    <string name=\"default_mode\">Alapértelmezett mód</string>\n    <string name=\"detect_reader_mode\">Automatikus olvasó mód felismerés</string>\n    <string name=\"detect_reader_mode_summary\">Automatikusan felismeri, hogy egy manga webtoon-e</string>\n    <string name=\"disable_battery_optimization\">Akkumulátor-optimalizálás letiltása</string>\n    <string name=\"send\">Küld</string>\n    <string name=\"status_dropped\">Elvetett</string>\n    <string name=\"disable_all\">Összes letiltása</string>\n    <string name=\"appwidget_shelf_description\">Manga a kedvenceid közül</string>\n    <string name=\"appwidget_recent_description\">A nemrég olvasott mangád</string>\n    <string name=\"report\">Jelent</string>\n    <string name=\"data_deletion\">Adattörlés</string>\n    <string name=\"show_all\">Összes mutatása</string>\n    <string name=\"clear_all_history\">Minden előzmény törlése</string>\n    <string name=\"last_2_hours\">Utolsó 2 óra</string>\n    <string name=\"history_cleared\">Előzmények törölve</string>\n    <string name=\"manage\">Kezelés</string>\n    <string name=\"no_bookmarks_yet\">Még nincsenek könyvjelzők</string>\n    <string name=\"no_bookmarks_summary\">Manga olvasás közben létrehozhatsz könyvjelzőt</string>\n    <string name=\"no_manga_sources_text\">Manga források engedélyezése az online mangák olvasásához</string>\n    <string name=\"random\">Véletlenszerű</string>\n    <string name=\"reorder\">Újrarendezés</string>\n    <string name=\"empty\">Üres</string>\n    <string name=\"explore\">Felfedezés</string>\n    <string name=\"confirm_exit\">Nyomd meg újra a Vissza gombot a kilépéshez</string>\n    <string name=\"exit_confirmation_summary\">Kattints kétszer a Vissza gombra az alkalmazásból való kilépéshez</string>\n    <string name=\"exit_confirmation\">Kilépés megerősítése</string>\n    <string name=\"saved_manga\">Mentett manga</string>\n    <string name=\"pages_cache\">Oldalak gyorsítótára</string>\n    <string name=\"other_cache\">Egyéb gyorsítótár</string>\n    <string name=\"storage_usage\">Tárhely használat</string>\n    <string name=\"available\">Elérhető</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"removed_from_favourites\">Eltávolítva a kedvencekből</string>\n    <string name=\"options\">Opciók</string>\n    <string name=\"not_found_404\">A tartalom nem elérhető vagy törölték</string>\n    <string name=\"incognito_mode\">Inkognitó mód</string>\n    <string name=\"automatic_scroll\">Automatikus lapozás</string>\n    <string name=\"reader_info_pattern\">Fej. %1$d/%2$d Old. %3$d/%4$d</string>\n    <string name=\"contrast\">Kontraszt</string>\n    <string name=\"reset\">Visszaállítás</string>\n    <string name=\"text_unsaved_changes_prompt\">A nem mentett változásokat menteni vagy elvetni?</string>\n    <string name=\"discard\">Elvet</string>\n    <string name=\"error_no_space_left\">Nincs több hely a készüléken</string>\n    <string name=\"reader_slider\">Mutassa a lapváltó csúszkát</string>\n    <string name=\"network_unavailable\">A hálózat nem elérhető</string>\n    <string name=\"compact\">Kompakt</string>\n    <string name=\"source_disabled\">Forrás letiltva</string>\n    <string name=\"prefetch_content\">Tartalom előtöltése</string>\n    <string name=\"mark_as_current\">Aktuálisként jelölés</string>\n    <string name=\"language\">Nyelv</string>\n    <string name=\"enable_logging\">Naplózás engedélyezése</string>\n    <string name=\"share_logs\">Naplók megosztása</string>\n    <string name=\"show_suspicious_content\">Gyanús tartalom megjelenítése</string>\n    <string name=\"theme_name_dynamic\">Dinamikus</string>\n    <string name=\"color_theme\">Színséma</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"nothing_here\">Itt nincs semmi</string>\n    <string name=\"settings_apply_restart_required\">Kérlek indítsd újra az alkalmazást a változtatások érvényesítéséhez</string>\n    <string name=\"ignore_ssl_errors\">SSL hibákat mellőzése</string>\n    <string name=\"color_dark\">Sötét</string>\n    <string name=\"background\">Háttér</string>\n    <string name=\"color_white\">Fehér</string>\n    <string name=\"color_black\">Fekete</string>\n    <string name=\"data_not_restored\">Az adatok visszaállítása nem sikerült</string>\n    <string name=\"data_not_restored_text\">Ellenőrizd, hogy a megfelelő biztonsági mentési fájlt választottad-e ki</string>\n    <string name=\"frequency_once_per_month\">Havonta egyszer</string>\n    <string name=\"frequency_every_day\">Minden nap</string>\n    <string name=\"genres_exclude\">Műfajok kizárása</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"last_read\">Legutóbb olvasott</string>\n    <string name=\"show_menu\">Menü megjelenítése</string>\n    <string name=\"show_labels_in_navbar\">Címkék megjelenítése a navigációs sávban</string>\n    <string name=\"pages_saving\">Oldalak mentése</string>\n    <string name=\"default_page_save_dir\">Alapértelmezett oldalmentési mappa</string>\n    <string name=\"reader_control_ltr_summary\">A jobb szélen való koppintás, vagy a jobb billentyű lenyomása mindig a következő oldalra vált.</string>\n    <string name=\"mirror_switching_summary\">Automatikusan váltson domaineket hiba esetén a manga forrásoknál, ha tükrök állnak rendelkezésre</string>\n    <string name=\"color_correction_apply_text\">Ezeket a beállításokat globálisan vagy csak az aktuális mangára lehet alkalmazni. Globálisan alkalmazva, az egyedi beállítások nem lesznek felülírva.</string>\n    <string name=\"downloads_settings_info\">Ha problémád van a szerver-oldali blokkolással, minden egyes manga forráshoz külön engedélyezheted a letöltés lassítását a forrásbeállításokban</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Ez a forrás nem támogatja a műfajok és a helyszín szerinti szűrést együttesen</string>\n    <string name=\"remove_from_history\">Eltávolítás az előzményekből</string>\n    <string name=\"ask_for_dest_dir_every_time\">Mindig kérdezze meg a célmappát</string>\n    <string name=\"preferred_download_format\">Preferált letöltési formátum</string>\n    <string name=\"single_cbz_file\">Egyetlen CBZ fájl</string>\n    <string name=\"multiple_cbz_files\">Több CBZ fájl</string>\n    <string name=\"zoom_mode_keep_start\">Kezdetben rendezni</string>\n    <string name=\"about_app_translation\">Fordítás</string>\n    <string name=\"auth_complete\">Hitelesítve</string>\n    <string name=\"auth_not_supported_by\">Bejelentkezés nem támogatott a(z) %s-en</string>\n    <string name=\"text_clear_cookies_prompt\">Ki leszel jelentkezve az összes forrásból</string>\n    <string name=\"genres\">Műfajok</string>\n    <string name=\"state_finished\">Befejezett</string>\n    <string name=\"state_ongoing\">Folyamatban</string>\n    <string name=\"system_default\">Alapértelmezett</string>\n    <string name=\"exclude_nsfw_from_history\">NSFW-mangát kihagyni a előzményekből</string>\n    <string name=\"show_pages_numbers\">Számozott oldalak</string>\n    <string name=\"screenshots_policy\">Képernyőkép szabályzat</string>\n    <string name=\"screenshots_allow\">Engedélyez</string>\n    <string name=\"screenshots_block_all\">Mindig blokkold</string>\n    <string name=\"suggestions\">Javaslatok</string>\n    <string name=\"screenshots_block_nsfw\">NSFW blokkolása</string>\n    <string name=\"suggestions_enable\">Javaslatok engedélyezése</string>\n    <string name=\"enabled\">Engedélyezve</string>\n    <string name=\"suggestions_summary\">Javasolj mangát az előnyeid alapján</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Ne javasolj NSFW mangát</string>\n    <string name=\"suggestions_info\">Minden adat csak helyben, ezen az eszközön van elemzés alatt, sosem kerül elküldésre semerre.</string>\n    <string name=\"text_suggestion_holder\">Kezdj el olvasni mangát, és személyre szabott javaslatokat fogsz kapni</string>\n    <string name=\"search_chapters\">Fejezet keresése</string>\n    <string name=\"show_notification_new_chapters_on\">Értesítéseket fogsz kapni a manga frissítésekről, amit olvasol</string>\n    <string name=\"show_notification_new_chapters_off\">Nem fogsz értesítéseket kapni, de az új fejezetek ki lesznek emelve a listákban</string>\n    <string name=\"show_reading_indicators\">A olvasási előrehaladás mutatójának megjelenítése</string>\n    <string name=\"show_reading_indicators_summary\">A haladás (százalékosan) megjelenítése az előzményekben és kedvencekben</string>\n    <string name=\"exclude_nsfw_from_history_summary\">A NSFW jelzésű mangák soha nem kerülnek hozzáadásra az előzményekhez, és a haladásod nem lesz mentve</string>\n    <string name=\"clear_cookies_summary\">Segíthet néhány probléma esetén. Minden hitelesítés érvényét veszti</string>\n    <string name=\"categories_delete_confirm\">Biztosan törölni szeretnéd a kiválasztott kedvenc kategóriákat? \\nMinden mangája elveszik, és ez nem visszavonható.</string>\n    <string name=\"history_shortcuts\">Aktuális manga hivatkozásai megjelenítése</string>\n    <string name=\"history_shortcuts_summary\">Tedd elérhetővé a legújabb mangákat az alkalmazás ikonjának hosszú megnyomásával</string>\n    <string name=\"network_unavailable_hint\">Kapcsold be a Wi-Fi-t vagy a mobilhálózatot, hogy online olvashass mangát</string>\n    <string name=\"server_error\">Szerver hiba (%1$d). Próbálkozz később</string>\n    <string name=\"clear_new_chapters_counters\">Valamint új fejezetekről szóló információk törlése</string>\n    <string name=\"show_in_grid_view\">Rács nézetben megjelenít</string>\n    <string name=\"manga_error_description_pattern\">Hiba részletei:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Próbáld meg &lt;a href=%2$s&gt;megnyitni a mangát egy webböngészőben&lt;/a&gt;, hogy megbizonyosodj róla, hogy elérhető-e az eredeti forrásáról.&lt;br&gt;2. Győződj meg arról, hogy a &lt;a href=kotatsu://about&gt;Kotatsu legfrissebb verzióját&lt;/a&gt; használod.&lt;br&gt;3. Ha elérhető, küldj egy hibajelentést a fejlesztőknek.</string>\n    <string name=\"scrobbling_empty_hint\">Az olvasási előrehaladás követéséhez válaszd a Menü → Követés lehetőséget a manga részletek képernyőn.</string>\n    <string name=\"enable_logging_summary\">Rögzít néhány műveletet hibakeresési célokra. Ne kapcsold be, ha nem vagy biztos benne, hogy mit csinálsz</string>\n    <string name=\"services\">Szolgáltatások</string>\n    <string name=\"allow_unstable_updates\">Instabil frissítések engedélyezése</string>\n    <string name=\"allow_unstable_updates_summary\">Értesítések fogadása instabil verziókról</string>\n    <string name=\"download_started\">Letöltés elkezdve</string>\n    <string name=\"got_it\">Értettem</string>\n    <string name=\"sources_reorder_tip\">Koppints és tartsd nyomva egy elemet a sorrend megváltoztatásához</string>\n    <string name=\"user_agent\">UserAgent fejléc</string>\n    <string name=\"comics_archive_import_description\">Választhatsz egy vagy több .cbz vagy .zip fájlt, minden egyes fájlt külön manga-ként fog felismerni.</string>\n    <string name=\"speed\">Sebesség</string>\n    <string name=\"sync_auth_hint\">Bejelentkezhetsz egy meglévő fiókba vagy létrehozhatsz egy újat</string>\n    <string name=\"find_similar\">Hasonló keresése</string>\n    <string name=\"sync_settings\">Szinkronizációs beállítások</string>\n    <string name=\"server_address\">Szerver cím</string>\n    <string name=\"folder_with_images_import_description\">Választhatsz egy mappát archívumokkal vagy képekkel. Minden egyes archívum (vagy almappa) egy-egy fejezetként lesz felismerve.</string>\n    <string name=\"show_on_shelf\">Mutatás a Polcon</string>\n    <string name=\"sync_host_description\">Használhatsz saját szinkronizációs szervert vagy az alapértelmezettet. Ne változtasd meg ezt, ha nem vagy biztos abban, mit csinálsz.</string>\n    <string name=\"mirror_switching\">Automatikusan válassz tükröt</string>\n    <string name=\"pause\">Szünet</string>\n    <string name=\"resume\">Folytatás</string>\n    <string name=\"paused\">Szüneteltetve</string>\n    <string name=\"remove_completed\">Eltávolítás befejezve</string>\n    <string name=\"cancel_all\">Összes megszakítása</string>\n    <string name=\"downloads_wifi_only\">Csak Wi-Fi-n keresztül töltsd le</string>\n    <string name=\"downloads_wifi_only_summary\">Mobilhálózatra váltáskor állítsa le a letöltést</string>\n    <string name=\"suggestion_manga\">Javaslat: %s</string>\n    <string name=\"suggestions_notifications_summary\">Néha mutass értesítéseket manga javaslatokkal</string>\n    <string name=\"more\">Több</string>\n    <string name=\"enable\">Engedélyez</string>\n    <string name=\"no_thanks\">Nem, köszönöm</string>\n    <string name=\"cancel_all_downloads_confirm\">Minden aktív letöltés megszakad, a részben letöltött adatok elvesznek</string>\n    <string name=\"remove_completed_downloads_confirm\">A letöltési előzményeid véglegesen törlődnek</string>\n    <string name=\"text_downloads_list_holder\">Nincsenek letöltéseid</string>\n    <string name=\"downloads_resumed\">A letöltések folytatódtak</string>\n    <string name=\"downloads_paused\">A letöltések szünetelnek</string>\n    <string name=\"downloads_removed\">A letöltések eltávolításra kerültek</string>\n    <string name=\"downloads_cancelled\">A letöltések törölve lettek</string>\n    <string name=\"suggestions_enable_prompt\">Szeretnél személyre szabott manga ajánlásokat kapni?</string>\n    <string name=\"web_view_unavailable\">WebView nem elérhető: ellenőrizd, hogy a WebView szolgáltató telepítve van-e</string>\n    <string name=\"clear_network_cache\">Hálózati gyorsítótár törlése</string>\n    <string name=\"type\">Típus</string>\n    <string name=\"address\">Cím</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"invalid_value_message\">Érvénytelen érték</string>\n    <string name=\"email_password_enter_hint\">Add meg az e-mail címed és a jelszavad a folytatáshoz</string>\n    <string name=\"downloaded\">Letöltött</string>\n    <string name=\"images_proxy_title\">Proxy a képek optimalizálásához</string>\n    <string name=\"images_procy_description\">Használd a wsrv.nl szolgáltatást a forgalom csökkentéséhez és a képek betöltésének felgyorsításához, ha lehetséges</string>\n    <string name=\"invert_colors\">Színek invertálása</string>\n    <string name=\"username\">Felhasználónév</string>\n    <string name=\"password\">Jelszó</string>\n    <string name=\"authorization_optional\">Hitelesítés (opcionális)</string>\n    <string name=\"invalid_port_number\">Érvénytelen portszám</string>\n    <string name=\"network\">Hálózat</string>\n    <string name=\"data_and_privacy\">Adatok és adatvédelem</string>\n    <string name=\"restore_summary\">Korábban létrehozott biztonsági mentés visszaállítása</string>\n    <string name=\"webtoon_zoom_summary\">Engedélyezze a nagyító gesztust webtoon módban</string>\n    <string name=\"reader_info_bar_summary\">Aktuális idő és az olvasási folyamat megjelenítése a képernyő tetején</string>\n    <string name=\"show_pages_numbers_summary\">Oldalszámok megjelenítése az alsó sarokban</string>\n    <string name=\"clear_source_cookies_summary\">Csak a kijelölt domain sütijeinek törlése. A legtöbb esetben érvényteleníti a hitelesítést</string>\n    <string name=\"download_option_all_chapters\">Minden fejezet fordítással %s</string>\n    <string name=\"download_option_whole_manga\">Az egész manga</string>\n    <string name=\"download_option_first_n_chapters\">Első %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Következő olvasatlan %s</string>\n    <string name=\"download_option_all_unread\">Minden olvasatlan fejezet</string>\n    <string name=\"download_option_all_unread_b\">Minden olvasatlan fejezet (%s)</string>\n    <string name=\"download_option_manual_selection\">Fejezetek kézi kiválasztása</string>\n    <string name=\"pick_custom_directory\">Egyéni mappa kiválasztása</string>\n    <string name=\"no_access_to_file\">Nincs hozzáférésed ehhez a fájlhoz vagy mappához</string>\n    <string name=\"local_manga_directories\">Helyi manga mappák</string>\n    <string name=\"description\">Leírás</string>\n    <string name=\"this_month\">E hónap</string>\n    <string name=\"voice_search\">Hangkeresés</string>\n    <string name=\"related_manga\">Hasonló manga</string>\n    <string name=\"color_light\">Világos</string>\n    <string name=\"manage_categories\">Kategóriák kezelése</string>\n    <string name=\"progress\">Haladás</string>\n    <string name=\"order_added\">Hozzáadva</string>\n    <string name=\"show\">Megjelenít</string>\n    <string name=\"captcha_required_summary\">%s megfelelő működéséhez szükséges egy captcha megoldása</string>\n    <string name=\"languages\">Nyelvek</string>\n    <string name=\"unknown\">Ismeretlen</string>\n    <string name=\"in_progress\">Folyamatban</string>\n    <string name=\"disable_nsfw\">NSFW letiltása</string>\n    <string name=\"too_many_requests_message\">Túl sok kérés. Próbáld újra később</string>\n    <string name=\"advanced\">Haladó</string>\n    <string name=\"manga_list\">Manga lista</string>\n    <string name=\"on_device\">Az eszközön</string>\n    <string name=\"main_screen_sections\">Fő képernyő szekciók</string>\n    <string name=\"items_limit_exceeded\">Nem lehet több elemet hozzáadni</string>\n    <string name=\"directories\">Mappák</string>\n    <string name=\"suggestions_wifi_only_summary\">Ne frissítsd a javaslatokat mérőhálózati kapcsolatokon keresztül</string>\n    <string name=\"tracker_wifi_only_summary\">Ne keress új fejezeteket mérőhálózati kapcsolatokon keresztü</string>\n    <string name=\"search_hint\">Írd be a manga címét, műfaját vagy forrásának nevét</string>\n    <string name=\"related_manga_summary\">Mutasd a hasonló mangák listáját. Néhány esetben ez pontatlan vagy hiányos lehet</string>\n    <string name=\"error_corrupted_file\">Érvénytelen adatok érkeztek vissza, vagy a fájl sérült</string>\n    <string name=\"to_top\">Tetejére</string>\n    <string name=\"moved_to_top\">Átlépve a tetejére</string>\n    <string name=\"zoom_out\">Kicsinyítés</string>\n    <string name=\"zoom_in\">Nagyítás</string>\n    <string name=\"reader_zoom_buttons\">Nagyítási gombok megjelenítése</string>\n    <string name=\"reader_zoom_buttons_summary\">Megjelenjenek-e a nagyításvezérlő gombok a jobb alsó sarokban</string>\n    <string name=\"keep_screen_on\">Képernyő bekapcsolva tartása</string>\n    <string name=\"state_abandoned\">Elvetve</string>\n    <string name=\"enhanced_colors_summary\">Csökkenti a szalagolást (kemény színátmenetek), de befolyásolhatja a teljesítményt</string>\n    <string name=\"enhanced_colors\">32 bites színmód</string>\n    <string name=\"suggest_new_sources\">Javasolj új forrásokat az alkalmazás frissítése után</string>\n    <string name=\"suggest_new_sources_summary\">Kérés az alkalmazás frissítése után hozzáadott új források engedélyezésére</string>\n    <string name=\"list_options\">Lista beállítások</string>\n    <string name=\"by_relevance\">Relevancia</string>\n    <string name=\"categories\">Kategóriák</string>\n    <string name=\"online_variant\">Online változat</string>\n    <string name=\"keep_screen_on_summary\">Ne kapcsold ki a képernyőt, amíg mangát olvasol</string>\n    <string name=\"periodic_backups\">Rendszeres biztonsági mentések</string>\n    <string name=\"backup_frequency\">Biztonsági mentés készítésének gyakorisága</string>\n    <string name=\"frequency_every_2_days\">Minden 2 naponta</string>\n    <string name=\"frequency_once_per_week\">Heti egyszer</string>\n    <string name=\"frequency_twice_per_month\">Havonta kétszer</string>\n    <string name=\"periodic_backups_enable\">Időszakos biztonsági mentések engedélyezése</string>\n    <string name=\"backups_output_directory\">Biztonsági mentések kimeneti mappája</string>\n    <string name=\"last_successful_backup\">Utolsó sikeres mentés: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">Képernyő forgatásának zárolása</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Képregények</string>\n    <string name=\"content_type_other\">Egyéb</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"sources_catalog\">Források katalógusa</string>\n    <string name=\"source_enabled\">Forrás engedélyezve</string>\n    <string name=\"catalog\">Katalógus</string>\n    <string name=\"manage_sources\">Források kezelése</string>\n    <string name=\"manual\">Manuális</string>\n    <string name=\"available_d\">Elérhető: %1$d</string>\n    <string name=\"disable_nsfw_summary\">NSFW források letiltása és a felnőtt manga tartalmak elrejtése a listából, ha lehetséges</string>\n    <string name=\"state_paused\">Szüneteltetve</string>\n    <string name=\"reader_optimize\">Memória-felhasználás csökkentése (béta)</string>\n    <string name=\"reader_optimize_summary\">Csökkentsd a nem látható oldalak minőségét, hogy kevesebb memóriát használj</string>\n    <string name=\"state\">Állapot</string>\n    <string name=\"no_manga_sources_found\">Nincsenek elérhető manga források a lekérdezésed alapján</string>\n    <string name=\"no_manga_sources_catalog_text\">Ebben a szekcióban nincsenek elérhető források, vagy minden forrást már hozzáadtak.\n\\nMaradj velünk az új forrásokért</string>\n    <string name=\"skip\">Kihagyás</string>\n    <string name=\"grayscale\">Szürkeárnyalatos</string>\n    <string name=\"globally\">Globális</string>\n    <string name=\"this_manga\">Ez a manga</string>\n    <string name=\"apply\">Alkalmaz</string>\n    <string name=\"genres_search_hint\">Kezd el beírni a műfaj nevét</string>\n    <string name=\"sync_auth\">Jelentkezz be a fiók szinkronizálásához</string>\n    <string name=\"restore\">Helyreállítás</string>\n    <string name=\"backup_date_\">Biztonsági mentés dátuma: %s</string>\n    <string name=\"state_upcoming\">Közelgő</string>\n    <string name=\"by_name_reverse\">Név fordítva</string>\n    <string name=\"content_rating\">Tartalom értékelése</string>\n    <string name=\"rating_safe\">Biztonságos</string>\n    <string name=\"rating_adult\">Felnőtt</string>\n    <string name=\"default_tab\">Alap fül</string>\n    <string name=\"mark_as_completed\">Megjelölés befejezettként</string>\n    <string name=\"error_multiple_genres_not_supported\">Ez a manga forrás nem támogatja a többszörös műfaj szerinti szűrést</string>\n    <string name=\"error_multiple_states_not_supported\">z a manga forrás nem támogatja a többszörös állapot szerinti szűrést</string>\n    <string name=\"error_search_not_supported\">Ez a manga forrás nem támogatja a keresést</string>\n    <string name=\"error_filter_states_genre_not_supported\">Ez a forrás nem támogatja a műfajok és az állapotok szerinti szűrést együttesen</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Segíthet a letöltés elindításában, ha problémád van vele</string>\n    <string name=\"welcome_text\">Kérlek válaszd ki, mely tartalomforrásokat szeretnéd engedélyezni. Ezek később is konfigurálhatók a beállításokban</string>\n    <string name=\"rating_suggestive\">Suggeráló</string>\n    <string name=\"mark_as_completed_prompt\">A kiválasztott mangát teljesen olvasottként jelölni?\n\\n\n\\nFigyelem: a jelenlegi olvasási haladás elveszik.</string>\n    <string name=\"category_hidden_done\">Ez a kategória el lett rejtve a főképernyőről, és a Menü → Kategóriák kezelése menüponton keresztül érhető el</string>\n    <string name=\"volume_\">Kötet %d</string>\n    <string name=\"volume_unknown\">Ismeretlen kötet</string>\n    <string name=\"incognito_mode_hint\">Az olvasási folyamatod nem lesz elmentve</string>\n    <string name=\"vertical\">Függőleges</string>\n    <string name=\"toggle_ui\">Felhasználói felület megjelenítése/elrejtése</string>\n    <string name=\"prev_chapter\">Előző fejezet</string>\n    <string name=\"next_chapter\">Következő fejezet</string>\n    <string name=\"prev_page\">Előző oldal</string>\n    <string name=\"next_page\">Következő oldal</string>\n    <string name=\"reader_actions\">Olvasói műveletek</string>\n    <string name=\"switch_pages_volume_buttons\">Hangerő gombok engedélyezése</string>\n    <string name=\"tap_action\">Koppintás művelet</string>\n    <string name=\"long_tap_action\">Hosszú érintési művelet</string>\n    <string name=\"none\">Semelyik</string>\n    <string name=\"reader_actions_summary\">Koppintható képernyőterületek műveleteinek konfigurálása</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Az oldalak váltásához használja a hangerőgombokat</string>\n    <string name=\"default_webtoon_zoom_out\">Alapértelmezett webtoon kicsinyítés</string>\n    <string name=\"fullscreen_mode\">Teljes képernyős mód</string>\n    <string name=\"config_reset_confirm\">Alapértelmezett értékekre visszaállítása? Ez a művelet nem visszavonható.</string>\n    <string name=\"use_two_pages_landscape\">Kétoldalas elrendezés használata vízszintes tájolásban (béta)</string>\n    <string name=\"reader_fullscreen_summary\">Rendszerstátusz és navigációs sáv elrejtése</string>\n    <string name=\"suggestions_unavailable_text\">A javaslat funkció le van tiltva</string>\n    <string name=\"check_for_new_chapters_disabled\">Az új fejezetek ellenőrzése le van tiltva</string>\n    <string name=\"reading_time_estimation\">Becsült olvasási idő megjelenítése</string>\n    <string name=\"reading_time_estimation_summary\">Az időbecslés értéke lehet, hogy pontatlan</string>\n    <string name=\"location\">Helyzet</string>\n    <string name=\"unsupported_backup_message\">Kérlek válassz ki egy megfelelő Kotatsu visszaállítási fájlt</string>\n    <string name=\"delete_read_chapters_prompt\">Ez véglegesen törölni fogja a telefonodról, azokat a fejezeteket amelyeket megjelöltél. Újra letöltheted őket később, de az importált fejezetek örökre elveszhetnek.</string>\n    <string name=\"delete_read_chapters_auto\">Törölje automatikusan az elolvasott fejezeteket</string>\n    <string name=\"error_no_data_received\">Semmilyen adat nem érkezett a szervertől</string>\n    <string name=\"hours_short\">%d ó</string>\n    <string name=\"minutes_short\">%d p</string>\n    <string name=\"hours_minutes_short\">%1$d ó %2$d p</string>\n    <string name=\"chapters_grid_view\">Rács nézet</string>\n    <string name=\"all_time\">Minden idő</string>\n    <string name=\"day\">Nap</string>\n    <string name=\"three_months\">Három hónap</string>\n    <string name=\"alternatives\">Alternatívák</string>\n    <string name=\"migrate\">Áthelyez</string>\n    <string name=\"manga_migration\">Manga áthelyezése</string>\n    <string name=\"delete_read_chapters\">Olvasott fejezetek törlése</string>\n    <string name=\"migration_completed\">Áthelyezés befejeződött</string>\n    <string name=\"no_chapters_deleted\">Nem lett fejezet törölve</string>\n    <string name=\"chapters_deleted_pattern\">Eltávolítva %1$s, eltakarítva %2$s</string>\n    <string name=\"delete_read_chapters_summary\">Töröl fejezeteket amiket már elolvastál a telefonodról hogy szabadíts fel helyet</string>\n    <string name=\"reading_stats\">Statisztika olvasása</string>\n    <string name=\"other_manga\">Egyéb manga</string>\n    <string name=\"less_than_minute\">Kevesebb mint egy perc</string>\n    <string name=\"statistics\">Statisztika</string>\n    <string name=\"clear_stats\">Statisztika törlése</string>\n    <string name=\"clear_stats_confirm\">Ténylegesen törölni akarod az összes elolvasott statisztikát? Ez a művelet nem vonható vissza.</string>\n    <string name=\"stats_cleared\">Statisztika törölve</string>\n    <string name=\"month\">Hónap</string>\n    <string name=\"week\">Hét</string>\n    <string name=\"empty_stats_text\">Nincsen statisztika a kijelölt idő periódushoz</string>\n    <string name=\"pages_read_s\">Oldalok elolvasva:%s</string>\n    <string name=\"show_pages_thumbs\">Oldalak iknojának mutatása</string>\n    <string name=\"show_pages_thumbs_summary\">\\\"Oldalak\\\" tab engedélyezése a részletek képernyőn</string>\n    <string name=\"split_by_translations\">Felosztása fordítások szerint</string>\n    <string name=\"split_by_translations_summary\">Fejezetek mutatása külön különböző fordítóktól, inkább mint egy listában</string>\n    <string name=\"order_oldest\">Legrégebbi</string>\n    <string name=\"long_ago_read\">Rég olvasva</string>\n    <string name=\"unread\">Olvasatlan</string>\n    <string name=\"runs_on_app_start\">Futtatás amikor az alkalmazás elindul</string>\n    <string name=\"enable_source\">Forrás engedélyezése</string>\n    <string name=\"unsupported_source\">Ez a manga forrás nem támogatott</string>\n    <string name=\"all_languages\">Összes nyelv</string>\n    <string name=\"screenshots_block_incognito\">Blokkold inkognitó módban</string>\n    <string name=\"crop_pages\">Lap kivágása</string>\n    <string name=\"image_server\">Előnyben részesített kiszolgáló</string>\n    <string name=\"last_used\">Legutóbb használt</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"more_frequently\">Gyakrabban</string>\n    <string name=\"frequency_of_check\">Az ellenőrzés gyakorisága</string>\n    <string name=\"pin_navigation_ui_summary\">Ne rejtse el a navigációs sávot és a keresési nézetet görgetéskor</string>\n    <string name=\"pages_saved\">Mentett oldalak</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Nincsenek az általad kiválasztott szűrőknek megfelelő mangák</string>\n    <string name=\"too_many_requests_message_retry\">Túl sok lekérés. Próbálja újra %s után</string>\n    <string name=\"seconds_short\">%d mp</string>\n    <string name=\"minutes_seconds_short\">%1$d p %2$d mp</string>\n    <string name=\"missing_storage_permission\">Nincs engedély a külső tárhelyen lévő mangákhoz való hozzáférésre</string>\n    <string name=\"show_updated\">Frissített megjelenítése</string>\n    <string name=\"invalid_server_address_message\">Érvénytelen kiszolgáló cím</string>\n    <string name=\"migrate_confirmation\">A „%1$s” mangát a „%2$s”-ből a „%3$s” mangát a „%4$s”-ből a „%2$s” mangával fogja helyettesíteni az előzményekben és a kedvencekben (ha van ilyen)</string>\n    <string name=\"webtoon_gaps_summary\">Függőleges hézagok megjelenítése az oldalak között webtoon módban</string>\n    <string name=\"webtoon_gaps\">Hézagok a webtoon módban</string>\n    <string name=\"retry\">Újra</string>\n    <string name=\"fix\">Javítás</string>\n    <string name=\"pin_navigation_ui\">Pin navigációs UI</string>\n    <string name=\"search_suggestions\">Keresési javaslatok</string>\n    <string name=\"recent_queries\">Legutóbbi lekérdezések</string>\n    <string name=\"less_frequently\">Ritkábban</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-in/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d item</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d Sub-bab Baru</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d bab</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d menit yang lalu</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d jam yang lalu</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d hari yang lalu</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d bulan yang lalu</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d jam</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d menit</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-in/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"local_storage\">Penyimpanan lokal</string>\n    <string name=\"favourites\">Disukai</string>\n    <string name=\"history\">Riwayat</string>\n    <string name=\"error_occurred\">Terjadi kesalahan</string>\n    <string name=\"network_error\">Kesalahan jaringan</string>\n    <string name=\"details\">Detail</string>\n    <string name=\"grid\">Kisi</string>\n    <string name=\"list_mode\">Mode daftar</string>\n    <string name=\"settings\">Pengaturan</string>\n    <string name=\"remote_sources\">Sumber manga</string>\n    <string name=\"loading_\">Memuat…</string>\n    <string name=\"computing_\">Menghitung…</string>\n    <string name=\"chapter_d_of_d\">Bab %1$d dari %2$d</string>\n    <string name=\"close\">Tutup</string>\n    <string name=\"try_again\">Coba lagi</string>\n    <string name=\"nothing_found\">Tidak ditemukan</string>\n    <string name=\"history_is_empty\">Riwayat kosong</string>\n    <string name=\"read\">Baca</string>\n    <string name=\"you_have_not_favourites_yet\">Belum ada yang disukai</string>\n    <string name=\"add_to_favourites\">Favoritkan ini</string>\n    <string name=\"add_new_category\">Kategori baru</string>\n    <string name=\"add\">Tambah</string>\n    <string name=\"save\">Simpan</string>\n    <string name=\"share\">Bagikan</string>\n    <string name=\"create_shortcut\">Buat pintasan</string>\n    <string name=\"share_s\">Bagikan %s</string>\n    <string name=\"search\">Cari</string>\n    <string name=\"search_manga\">Cari manga</string>\n    <string name=\"manga_downloading_\">Mengunduh…</string>\n    <string name=\"processing_\">Memproses…</string>\n    <string name=\"download_complete\">Diunduh</string>\n    <string name=\"downloads\">Unduhan</string>\n    <string name=\"by_name\">Nama</string>\n    <string name=\"popular\">Populer</string>\n    <string name=\"updated\">Diperbarui</string>\n    <string name=\"newest\">Terbaru</string>\n    <string name=\"by_rating\">Nilai</string>\n    <string name=\"sort_order\">Urutkan</string>\n    <string name=\"filter\">Saring</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"light\">Terang</string>\n    <string name=\"dark\">Gelap</string>\n    <string name=\"follow_system\">Ikuti sistem</string>\n    <string name=\"pages\">Halaman</string>\n    <string name=\"clear\">Bersihkan</string>\n    <string name=\"remove\">Hapus</string>\n    <string name=\"clear_history\">Hapus riwayat</string>\n    <string name=\"delete\">Hapus</string>\n    <string name=\"operation_not_supported\">Tindakan ini tidak didukung</string>\n    <string name=\"text_file_not_supported\">Pilih antara berkas ZIP atau CBZ.</string>\n    <string name=\"no_description\">Tidak ada deskripsi</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" dihapus dari penyimpanan</string>\n    <string name=\"save_page\">Simpan halaman</string>\n    <string name=\"page_saved\">Halaman disimpan</string>\n    <string name=\"share_image\">Bagikan gambar</string>\n    <string name=\"_import\">Impor</string>\n    <string name=\"clear_pages_cache\">Bersihkan singgahan halaman</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Standar</string>\n    <string name=\"grid_size\">Ukuran kisi</string>\n    <string name=\"search_on_s\">Cari di %s</string>\n    <string name=\"delete_manga\">Hapus manga</string>\n    <string name=\"text_delete_local_manga\">Hapus \\\"%s\\\" dari perangkat secara permanen?</string>\n    <string name=\"reader_settings\">Pengaturan pembaca</string>\n    <string name=\"switch_pages\">Ganti halaman</string>\n    <string name=\"chapters\">Bab</string>\n    <string name=\"list\">Daftar</string>\n    <string name=\"detailed_list\">Daftar rinci</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Mode baca</string>\n    <string name=\"_continue\">Lanjut</string>\n    <string name=\"clear_thumbs_cache\">Bersihkan singgahan gambar mini</string>\n    <string name=\"clear_search_history\">Bersihkan riwayat pencarian</string>\n    <string name=\"search_history_cleared\">Dibersihkan</string>\n    <string name=\"domain\">Ranah web</string>\n    <string name=\"app_update_available\">Versi baru aplikasi tersedia</string>\n    <string name=\"open_in_browser\">Buka di peramban web</string>\n    <string name=\"notifications\">Notifikasi</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d dari %2$d diaktifkan</string>\n    <string name=\"new_chapters\">Sub-bab baru</string>\n    <string name=\"download\">Unduh</string>\n    <string name=\"notifications_settings\">Pengaturan notifikasi</string>\n    <string name=\"notification_sound\">Suara notifikasi</string>\n    <string name=\"text_empty_holder_primary\">Sepi juga di sini…</string>\n    <string name=\"text_history_holder_primary\">Apa yang Anda baca akan ditampilkan di sini</string>\n    <string name=\"text_history_holder_secondary\">Cari sesuatu untuk dibaca via «Eksplor»</string>\n    <string name=\"text_local_holder_primary\">Simpan sesuatu dulu</string>\n    <string name=\"manga_shelf\">Rak</string>\n    <string name=\"recent_manga\">Baru-baru ini</string>\n    <string name=\"pages_animation\">Animasi halaman</string>\n    <string name=\"manga_save_location\">Folder unduhan</string>\n    <string name=\"not_available\">Tidak tersedia</string>\n    <string name=\"cannot_find_available_storage\">Tidak ada penyimpanan yang tersedia</string>\n    <string name=\"other_storage\">Penyimpanan lain</string>\n    <string name=\"done\">Selesai</string>\n    <string name=\"all_favourites\">Semua kesukaan</string>\n    <string name=\"favourites_category_empty\">Kategori kosong</string>\n    <string name=\"read_later\">Baca nanti</string>\n    <string name=\"updates\">Pembaruan</string>\n    <string name=\"text_feed_holder\">Bab baru dari apa yang Anda baca ditampilkan di sini</string>\n    <string name=\"search_results\">Hasil pencarian</string>\n    <string name=\"new_version_s\">Versi baru: %s</string>\n    <string name=\"size_s\">Ukuran: %s</string>\n    <string name=\"updates_feed_cleared\">Dibersihkan</string>\n    <string name=\"update\">Perbarui</string>\n    <string name=\"track_sources\">Mencari pembaruan</string>\n    <string name=\"dont_check\">Jangan periksa</string>\n    <string name=\"wrong_password\">Kata sandi salah</string>\n    <string name=\"protect_application\">Lindungi aplikasi</string>\n    <string name=\"repeat_password\">Ulangi kata sandi</string>\n    <string name=\"scale_mode\">Mode skala</string>\n    <string name=\"create_backup\">Buat cadangan data</string>\n    <string name=\"data_restored\">Dipulihkan</string>\n    <string name=\"data_restored_success\">Semua data dipulihkan</string>\n    <string name=\"data_restored_with_errors\">Data berhasil dipulihkan, tapi ada kesalahan</string>\n    <string name=\"backup_information\">Anda dapat membuat cadangan riwayat dan favorit dan memulihkannya</string>\n    <string name=\"just_now\">Baru saja</string>\n    <string name=\"long_ago\">Lawas</string>\n    <string name=\"group\">Kelompok</string>\n    <string name=\"today\">Hari ini</string>\n    <string name=\"tap_to_try_again\">Ketuk untuk coba lagi</string>\n    <string name=\"reader_mode_hint\">Konfigurasi yang dipilih akan diingat untuk manga ini</string>\n    <string name=\"silent\">Diam</string>\n    <string name=\"captcha_required\">CAPTCHA diperlukan</string>\n    <string name=\"captcha_solve\">Selesaikan</string>\n    <string name=\"clear_cookies\">Bersihkan kuki</string>\n    <string name=\"cookies_cleared\">Semua kuki telah dihapus</string>\n    <string name=\"clear_feed\">Bersihkan umpan</string>\n    <string name=\"check_for_new_chapters\">Periksa bab baru</string>\n    <string name=\"auth_required\">Masuk untuk melihat konten ini</string>\n    <string name=\"next\">Selanjutnya</string>\n    <string name=\"protect_application_subtitle\">Masukkan kata sandi untuk memulai aplikasi</string>\n    <string name=\"confirm\">Konfirmasi</string>\n    <string name=\"text_clear_search_history_prompt\">Hapus semua kueri pencarian baru-baru ini secara permanen\\?</string>\n    <string name=\"welcome\">Selamat Datang</string>\n    <string name=\"backup_saved\">Cadangan disimpan</string>\n    <string name=\"tracker_warning\">Beberapa perangkat mempunyai perilaku sistem yang berbeda, yang mungkin akan merusak tugas latar belakang.</string>\n    <string name=\"read_more\">Baca lebih lanjut</string>\n    <string name=\"chapter_is_missing\">Bab ini tidak ada</string>\n    <string name=\"about_app_translation\">Terjemahan</string>\n    <string name=\"auth_not_supported_by\">Masuk pada %s tidak didukung</string>\n    <string name=\"text_clear_cookies_prompt\">Anda akan keluar dari semua sumber</string>\n    <string name=\"genres\">Genre</string>\n    <string name=\"state_finished\">Tamat</string>\n    <string name=\"state_ongoing\">Masih lanjut</string>\n    <string name=\"system_default\">Bawaan</string>\n    <string name=\"exclude_nsfw_from_history\">Kecualikan manga TAUSB dari riwayat</string>\n    <string name=\"show_pages_numbers\">Nomor halaman</string>\n    <string name=\"screenshots_policy\">Kebijakan tangkapan layar</string>\n    <string name=\"screenshots_allow\">Bolehkan</string>\n    <string name=\"screenshots_block_nsfw\">Blokir TAUSB</string>\n    <string name=\"screenshots_block_all\">Selalu blokir</string>\n    <string name=\"suggestions\">Saran</string>\n    <string name=\"suggestions_enable\">Aktifkan saran</string>\n    <string name=\"suggestions_summary\">Sarankan manga berdasarkan preferensi Anda</string>\n    <string name=\"suggestions_info\">Semua data hanya dianalisis secara lokal pada perangkat ini dan tidak pernah dikirim ke mana pun.</string>\n    <string name=\"text_suggestion_holder\">Mulai membaca manga dan Anda akan mendapatkan saran yang dipersonalisasi</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Jangan menyarankan manga TAUSB</string>\n    <string name=\"always\">Selalu</string>\n    <string name=\"preload_pages\">Muat ulang halaman</string>\n    <string name=\"logged_in_as\">Masuk sebagai %s</string>\n    <string name=\"enabled\">Diaktifkan</string>\n    <string name=\"disabled\">Dinonaktifkan</string>\n    <string name=\"reset_filter\">Atur ulang filter</string>\n    <string name=\"onboard_text\">Pilih bahasa manga yang ingin Anda baca. Anda bisa mengubahnya nanti di pengaturan.</string>\n    <string name=\"never\">Jangan Pernah</string>\n    <string name=\"only_using_wifi\">Hanya di Wi-Fi</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Berbagai bahasa</string>\n    <string name=\"search_chapters\">Cari bab</string>\n    <string name=\"chapters_empty\">Tidak ada bab di manga ini</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Tampilan</string>\n    <string name=\"suggestions_excluded_genres\">Kecualikan genre</string>\n    <string name=\"suggestions_excluded_genres_summary\">Tentukan genre yang Anda tidak ingin lihat di saran</string>\n    <string name=\"text_delete_local_manga_batch\">Hapus yang dipilih dari perangkat secara permanen\\?</string>\n    <string name=\"removal_completed\">Selesai menghapus</string>\n    <string name=\"download_slowdown\">Perlambat unduhan</string>\n    <string name=\"suggestions_updating\">Memperbarui saran</string>\n    <string name=\"download_slowdown_summary\">Membantu menghidari pemblokiran alamat IP Anda</string>\n    <string name=\"chapters_will_removed_background\">Bab akan dihapus di latar belakang</string>\n    <string name=\"hide\">Sembunyikan</string>\n    <string name=\"new_sources_text\">Tersedia sumber manga baru</string>\n    <string name=\"check_new_chapters_title\">Periksa bab baru dan beri tahu tentang itu</string>\n    <string name=\"show_notification_new_chapters_on\">Anda akan menerima notifikasi tentang pembaruan manga yang Anda baca</string>\n    <string name=\"show_notification_new_chapters_off\">Anda tidak akan menerima notifikasi, tapi bab baru akan disorot di daftar</string>\n    <string name=\"notifications_enable\">Aktifkan notifikasi</string>\n    <string name=\"name\">Nama</string>\n    <string name=\"edit\">Sunting</string>\n    <string name=\"edit_category\">Sunting kategori</string>\n    <string name=\"empty_favourite_categories\">Tidak ada kategori kesukaan</string>\n    <string name=\"bookmark_add\">Tambah markah</string>\n    <string name=\"bookmark_remove\">Hapus markah</string>\n    <string name=\"bookmarks\">Markah</string>\n    <string name=\"bookmark_removed\">Markah dihapus</string>\n    <string name=\"bookmark_added\">Markah ditambahkan</string>\n    <string name=\"undo\">Urung</string>\n    <string name=\"removed_from_history\">Dihapus dari riwayat</string>\n    <string name=\"default_mode\">Mode bawaan</string>\n    <string name=\"detect_reader_mode\">Otomatis deteksi mode pembaca</string>\n    <string name=\"detect_reader_mode_summary\">Otomatis mendeteksi jika manga adalah webtoon</string>\n    <string name=\"create_category\">Kategori baru</string>\n    <string name=\"light_indicator\">Indikator LED</string>\n    <string name=\"vibration\">Getaran</string>\n    <string name=\"favourites_categories\">Kategori disukai</string>\n    <string name=\"remove_category\">Hapus</string>\n    <string name=\"clear_updates_feed\">Bersihkan aliran pembaruan</string>\n    <string name=\"right_to_left\">Kanan-ke-kiri</string>\n    <string name=\"rotate_screen\">Putar layar</string>\n    <string name=\"feed_will_update_soon\">Pembaruan umpan akan dimulai</string>\n    <string name=\"enter_password\">Masukkan kata sandi</string>\n    <string name=\"protect_application_summary\">Tanya kata sandi ketika memulai Kotatsu</string>\n    <string name=\"about\">Tentang</string>\n    <string name=\"check_for_updates\">Periksa pembaruan</string>\n    <string name=\"passwords_mismatch\">Kata sandi tidak sama</string>\n    <string name=\"app_version\">Versi %s</string>\n    <string name=\"internal_storage\">Penyimpanan internal</string>\n    <string name=\"external_storage\">Penyimpanan eksternal</string>\n    <string name=\"error\">Kesalahan</string>\n    <string name=\"no_update_available\">Tidak ada pembaruan yang tersedia</string>\n    <string name=\"black_dark_theme_summary\">Menggunakan daya lebih sedikit pada layar AMOLED</string>\n    <string name=\"black_dark_theme\">Hitam</string>\n    <string name=\"backup_restore\">Pencadangan dan pemulihan</string>\n    <string name=\"yesterday\">Kemarin</string>\n    <string name=\"restore_backup\">Pulihkan dari cadangan</string>\n    <string name=\"file_not_found\">Berkas tidak ditemukan</string>\n    <string name=\"password_length_hint\">Kata sandi harus 4 karakter atau lebih</string>\n    <string name=\"preparing_\">Mempersiapkan…</string>\n    <string name=\"text_clear_updates_feed_prompt\">Bersihkan semua riwayat pembaruan secara permanen\\?</string>\n    <string name=\"sign_in\">Masuk</string>\n    <string name=\"default_s\">Standar: %s</string>\n    <string name=\"about_app_translation_summary\">Terjemahkan aplikasi ini</string>\n    <string name=\"zoom_mode_fit_center\">Pas tengah</string>\n    <string name=\"zoom_mode_fit_height\">Pas tinggi</string>\n    <string name=\"zoom_mode_fit_width\">Pas lebar</string>\n    <string name=\"reverse\">Balik</string>\n    <string name=\"queued\">Mengantre</string>\n    <string name=\"auth_complete\">Resmi</string>\n    <string name=\"text_local_holder_secondary\">Simpan dari sumber daring atau berkas impor.</string>\n    <string name=\"email_enter_hint\">Masukkan surel Anda untuk melanjutkan</string>\n    <string name=\"status_re_reading\">Dibaca ulang</string>\n    <string name=\"explore\">Jelajah</string>\n    <string name=\"status_planned\">Direncanakan</string>\n    <string name=\"status_completed\">Selesai</string>\n    <string name=\"canceled\">Dibatalkan</string>\n    <string name=\"sync_title\">Sinkronkan data Anda</string>\n    <string name=\"tracking\">Pelacakan</string>\n    <string name=\"logout\">Keluar</string>\n    <string name=\"sync\">Sinkronisasi</string>\n    <string name=\"send\">Kirim</string>\n    <string name=\"status_reading\">Dibaca</string>\n    <string name=\"status_on_hold\">Ditunda</string>\n    <string name=\"invalid_domain_message\">Domain tak sah</string>\n    <string name=\"no_bookmarks_yet\">Belum ada markah</string>\n    <string name=\"no_bookmarks_summary\">Anda bisa membuat markah ketika membaca manga</string>\n    <string name=\"bookmarks_removed\">Markah dihapus</string>\n    <string name=\"no_manga_sources\">Tidak ada sumber manga</string>\n    <string name=\"random\">Acak</string>\n    <string name=\"empty\">Kosong</string>\n    <string name=\"reader_info_bar\">Tampilkan bilah informasi di pembaca</string>\n    <string name=\"importing_manga\">Mengimpor manga</string>\n    <string name=\"last_2_hours\">2 jam terakhir</string>\n    <string name=\"show_all\">Tampilkan semua</string>\n    <string name=\"history_cleared\">Riwayat dihapus</string>\n    <string name=\"clear_all_history\">Hapus semua riwayat</string>\n    <string name=\"options\">Pilihan</string>\n    <string name=\"import_completed\">Impor selesai</string>\n    <string name=\"removed_from_favourites\">Dihapus dari kesukaan</string>\n    <string name=\"import_will_start_soon\">Impor akan segera dimulai</string>\n    <string name=\"not_found_404\">Konten tidak ditemukan atau dihapus</string>\n    <string name=\"exit_confirmation_summary\">Tekan Kembali dua kali untuk keluar dari aplikasi</string>\n    <string name=\"exit_confirmation\">Konfirmasi keluar</string>\n    <string name=\"pages_cache\">Singgahan halaman</string>\n    <string name=\"other_cache\">Singgahan lainnya</string>\n    <string name=\"storage_usage\">Penggunaan penyimpanan</string>\n    <string name=\"available\">Tersedia</string>\n    <string name=\"incognito_mode\">Mode penyamaran</string>\n    <string name=\"automatic_scroll\">Pengguliran otomatis</string>\n    <string name=\"comics_archive\">Arsip komik</string>\n    <string name=\"folder_with_images\">Folder dengan gambar</string>\n    <string name=\"import_completed_hint\">Anda bisa menghapus berkas asli dari penyimpanan untuk menghemat ruang</string>\n    <string name=\"feed\">Umpan</string>\n    <string name=\"manga_error_description_pattern\">Rincian galat:&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Coba &lt;a href=%2$s&gt;buka manga di peramban web&lt;/a&gt; untuk memastikan manga tersebut tersedia di sumbernya&lt;/a&gt;2. Pastikan Anda menggunakan Kotatsu &lt;a href=kotatsu://about&gt;versi terbaru&lt;/a&gt;&lt;br&gt;3. Jika itu tersedia, kirimkan laporan kesalahan ke pengembang.</string>\n    <string name=\"brightness\">Kecerahan</string>\n    <string name=\"contrast\">Kontras</string>\n    <string name=\"reset\">Atur Ulang</string>\n    <string name=\"text_unsaved_changes_prompt\">Simpan atau buang perubahan yang belum disimpan?</string>\n    <string name=\"discard\">Buang</string>\n    <string name=\"use_fingerprint\">Gunakan biometrik jika tersedia</string>\n    <string name=\"appwidget_shelf_description\">Manga dari kesukaan Anda</string>\n    <string name=\"appwidget_recent_description\">Manga yang baru-baru ini Anda baca</string>\n    <string name=\"data_deletion\">Penghapusan data</string>\n    <string name=\"account_already_exists\">Akun sudah ada</string>\n    <string name=\"back\">Kembali</string>\n    <string name=\"disable_battery_optimization_summary\">Membantu pemeriksaan pembaruan di latar belakang</string>\n    <string name=\"crash_text\">Ada sesuatu yang salah. Mohon untuk mengirim laporan kutu ke pengembang untuk membantu kami memperbaikinya.</string>\n    <string name=\"report\">Lapor</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga yang ditandai sebagai TAUSB tidak akan ditambahkan ke riwayat dan progres Anda tidak akan disimpan</string>\n    <string name=\"clear_cookies_summary\">Bisa membantu dalam beberapa masalah. Seluruh otorisasi akan menjadi tidak valid</string>\n    <string name=\"manage\">Kelola</string>\n    <string name=\"no_manga_sources_text\">Aktifkan sumber manga untuk membaca manga daring</string>\n    <string name=\"categories_delete_confirm\">Yakin ingin menghapus kategori favorit yang dipilih? \\nSemua manga di dalamnya akan hilang dan ini tidak dapat dibatalkan.</string>\n    <string name=\"reorder\">Atur Ulang</string>\n    <string name=\"saved_manga\">Manga tersimpan</string>\n    <string name=\"confirm_exit\">Tekan Kembali lagi untuk keluar</string>\n    <string name=\"no_chapters\">Tidak ada bab</string>\n    <string name=\"history_shortcuts\">Tampilkan pintasan manga baru-baru ini</string>\n    <string name=\"history_shortcuts_summary\">Buat manga terbaru tersedia dengan menekan lama ikon aplikasi</string>\n    <string name=\"select_range\">Pilih rentang</string>\n    <string name=\"disable_all\">Matikan semua</string>\n    <string name=\"dns_over_https\">DNS melalui HTTPS</string>\n    <string name=\"status_dropped\">Didrop</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"server_error\">Kesalahan di sisi server (%1$d). Silakan coba lagi nanti</string>\n    <string name=\"compact\">Padat</string>\n    <string name=\"prefetch_content\">Pramuat konten</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"nothing_here\">Tidak ada apapun di sini</string>\n    <string name=\"reader_control_ltr_summary\">Jangan ubah arah pergantian halaman ke mode pembaca, misalnya, menekan tombol kanan selalu beralih ke halaman berikutnya. Opsi ini hanya berlaku untuk perangkat masukan hardware</string>\n    <string name=\"source_disabled\">Sumber dinonaktifkan</string>\n    <string name=\"mark_as_current\">Tandai sebagai saat ini</string>\n    <string name=\"show_suspicious_content\">Tampilkan konten yang mencurigakan</string>\n    <string name=\"scrobbling_empty_hint\">Untuk melacak progres membaca, pilih Menu → Lacak di layar detail manga.</string>\n    <string name=\"services\">Layanan</string>\n    <string name=\"clear_new_chapters_counters\">Juga bersihkan informasi tentang bab baru</string>\n    <string name=\"network_unavailable_hint\">Nyalakan Wi-Fi atau jaringan seluler untuk membaca manga daring</string>\n    <string name=\"user_agent\">Tajuk Agen Pengguna</string>\n    <string name=\"settings_apply_restart_required\">Mulai ulang aplikasi untuk menerapkan perubahan ini</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"share_logs\">Bagikan log</string>\n    <string name=\"error_no_space_left\">Tidak ada ruang tersisa di perangkat</string>\n    <string name=\"allow_unstable_updates\">Izinkan pembaruan yang tidak stabil</string>\n    <string name=\"allow_unstable_updates_summary\">Terima notifikasi tentang build yang tidak stabil</string>\n    <string name=\"download_started\">Unduhan dimulai</string>\n    <string name=\"show_reading_indicators\">Tampilkan indikator progres membaca</string>\n    <string name=\"show_reading_indicators_summary\">Tampilkan persentase baca di riwayat dan favorit</string>\n    <string name=\"language\">Bahasa</string>\n    <string name=\"text_search_holder_secondary\">Cobalah untuk memformulasi ulang kueri.</string>\n    <string name=\"zoom_mode_keep_start\">Tetap di awal</string>\n    <string name=\"reader_info_pattern\">Bab %1$d/%2$d Hlm. %3$d/%4$d</string>\n    <string name=\"enable_logging\">Aktifkan pencatatan</string>\n    <string name=\"enable_logging_summary\">Rekam beberapa tindakan untuk tujuan awakutu. Jangan nyalakan jika Anda tidak yakin dengan apa yang Anda lakukan</string>\n    <string name=\"theme_name_dynamic\">Dinamis</string>\n    <string name=\"color_theme\">Skema warna</string>\n    <string name=\"show_in_grid_view\">Perlihatkan dalam tampilan kisi</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"local_manga_processing\">Pemrosesan manga tersimpan</string>\n    <string name=\"disable_battery_optimization\">Nonaktifkan pengoptimalan baterai</string>\n    <string name=\"reader_control_ltr\">Kontrol pembaca ergonomis</string>\n    <string name=\"color_correction\">Koreksi warna</string>\n    <string name=\"reader_slider\">Perlihatkan penggeser peralihan halaman</string>\n    <string name=\"webtoon_zoom\">Pembesaran Webtoon</string>\n    <string name=\"network_unavailable\">Jaringan tidak tersedia</string>\n    <string name=\"got_it\">Oke</string>\n    <string name=\"sources_reorder_tip\">Ketuk dan tahan item untuk menyusun ulang</string>\n    <string name=\"comics_archive_import_description\">Anda dapat memilih satu atau beberapa berkas .cbz atau .zip, setiap file akan dikenali sebagai manga terpisah.</string>\n    <string name=\"folder_with_images_import_description\">Anda dapat memilih direktori yang berisi arsip atau gambar. Setiap arsip (atau subdirektori) akan dikenali sebagai sebuah bab.</string>\n    <string name=\"speed\">Kecepatan</string>\n    <string name=\"show_on_shelf\">Tampilkan di Rak</string>\n    <string name=\"sync_auth_hint\">Anda dapat masuk ke akun yang sudah ada atau membuat akun baru</string>\n    <string name=\"find_similar\">Temukan serupa</string>\n    <string name=\"server_address\">Alamat server</string>\n    <string name=\"downloads_wifi_only\">Unduh hanya melalui Wi-Fi</string>\n    <string name=\"downloads_cancelled\">Unduhan telah dibatalkan</string>\n    <string name=\"enable\">Aktifkan</string>\n    <string name=\"no_thanks\">Tidak, terima kasih</string>\n    <string name=\"sync_settings\">Pengaturan sinkronisasi</string>\n    <string name=\"sync_host_description\">Anda dapat menggunakan server sinkronisasi yang dihos sendiri atau server bawaan. Jangan ubah ini jika Anda tidak yakin dengan apa yang Anda lakukan.</string>\n    <string name=\"ignore_ssl_errors\">Abaikan galat SSL</string>\n    <string name=\"mirror_switching\">Pilih cermin secara otomatis</string>\n    <string name=\"mirror_switching_summary\">Alihkan domain secara otomatis ke sumber lain saat terjadi kesalahan jika alternatif tersedia</string>\n    <string name=\"pause\">Jeda</string>\n    <string name=\"resume\">Lanjut</string>\n    <string name=\"paused\">Dijeda</string>\n    <string name=\"remove_completed\">Hapus selesai</string>\n    <string name=\"cancel_all\">Batalkan semua</string>\n    <string name=\"downloads_wifi_only_summary\">Berhenti mengunduh saat beralih ke jaringan seluler</string>\n    <string name=\"suggestion_manga\">Saran: %s</string>\n    <string name=\"suggestions_notifications_summary\">Terkadang menampilkan notifikasi dengan manga yang disarankan</string>\n    <string name=\"more\">Lebih</string>\n    <string name=\"cancel_all_downloads_confirm\">Semua unduhan aktif akan dibatalkan, data yang sudah terunduh sebagian akan hilang</string>\n    <string name=\"remove_completed_downloads_confirm\">Riwayat unduhan Anda akan dihapus secara permanen. Berkas yang diunduh tidak akan terpengaruh</string>\n    <string name=\"text_downloads_list_holder\">Anda tidak memiliki unduhan apa pun</string>\n    <string name=\"downloads_resumed\">Unduhan telah dilanjutkan</string>\n    <string name=\"downloads_paused\">Unduhan telah dijeda</string>\n    <string name=\"downloads_removed\">Unduhan telah dihapus</string>\n    <string name=\"suggestions_enable_prompt\">Apakah Anda ingin menerima saran manga yang dipersonalisasi\\?</string>\n    <string name=\"web_view_unavailable\">WebView tidak tersedia: periksa apakah penyedia WebView telah diinstal</string>\n    <string name=\"clear_network_cache\">Hapus singgahan jaringan</string>\n    <string name=\"type\">Tipe</string>\n    <string name=\"address\">Alamat</string>\n    <string name=\"port\">Porta</string>\n    <string name=\"proxy\">Proksi</string>\n    <string name=\"invalid_value_message\">Nilai tidak sah</string>\n    <string name=\"images_procy_description\">Gunakan layanan wsrv.nl untuk mengurangi penggunaan lalu lintas dan mempercepat pemuatan gambar jika memungkinkan</string>\n    <string name=\"images_proxy_title\">Proksi pengoptimalan gambar</string>\n    <string name=\"username\">NamaUser</string>\n    <string name=\"downloaded\">Diunduh</string>\n    <string name=\"invert_colors\">Balikkan warna</string>\n    <string name=\"password\">Kata sandi</string>\n    <string name=\"authorization_optional\">Otorisasi (opsional)</string>\n    <string name=\"data_and_privacy\">Data dan privasi</string>\n    <string name=\"restore_summary\">Pulihkan cadangan yang dibuat sebelumnya</string>\n    <string name=\"invalid_port_number\">Nomor porta tak sah</string>\n    <string name=\"network\">Jaringan</string>\n    <string name=\"show_pages_numbers_summary\">Tampilkan nomor halaman di pojok bawah</string>\n    <string name=\"webtoon_zoom_summary\">Izinkan gestur zum pada mode webtoon</string>\n    <string name=\"reader_info_bar_summary\">Tampilkan waktu saat ini dan progres bacaan di bagian atas layar</string>\n    <string name=\"clear_source_cookies_summary\">Hapus kuki hanya untuk domain tertentu. Dalam kebanyakan kasus akan membatalkan otorisasi</string>\n    <string name=\"download_option_whole_manga\">Seluruh manga</string>\n    <string name=\"download_option_first_n_chapters\">Pertama %s</string>\n    <string name=\"download_option_all_unread\">Semua bab yang belum dibaca</string>\n    <string name=\"download_option_all_unread_b\">Semua bab yang belum dibaca (%s)</string>\n    <string name=\"download_option_all_chapters\">Semua bab dengan terjemahan %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Belum dibaca %s</string>\n    <string name=\"download_option_manual_selection\">Pilih bab secara manual</string>\n    <string name=\"pick_custom_directory\">Pilih direktori khusus</string>\n    <string name=\"no_access_to_file\">Anda tidak memiliki akses ke file atau direktori ini</string>\n    <string name=\"local_manga_directories\">Direktori manga</string>\n    <string name=\"disable_nsfw\">Nonaktifkan TAUSB</string>\n    <string name=\"description\">Deskripsi</string>\n    <string name=\"manage_categories\">Atur kategori</string>\n    <string name=\"show\">Tampilkan</string>\n    <string name=\"related_manga_summary\">Tampilkan daftar manga terkait. Bisa jadi tidak akurat atau hilang</string>\n    <string name=\"suggestions_wifi_only_summary\">Jangan perbarui saran dengan koneksi jaringan internet terbatas</string>\n    <string name=\"tracker_wifi_only_summary\">Jangan perbarui bab baru dengan koneksi jaringan internet terbatas</string>\n    <string name=\"search_hint\">Masukkan judul, genre, atau nama sumber manga</string>\n    <string name=\"progress\">Progres</string>\n    <string name=\"order_added\">Ditambahkan</string>\n    <string name=\"too_many_requests_message\">Terlalu banyak permintaan. Coba lain kali</string>\n    <string name=\"related_manga\">Manga terkait</string>\n    <string name=\"this_month\">Bulan ini</string>\n    <string name=\"voice_search\">Pencarian suara</string>\n    <string name=\"color_dark\">Gelap</string>\n    <string name=\"color_light\">Terang</string>\n    <string name=\"color_white\">Putih</string>\n    <string name=\"color_black\">Hitam</string>\n    <string name=\"background\">Latar belakang</string>\n    <string name=\"data_not_restored\">Data tidak dikembalikan</string>\n    <string name=\"data_not_restored_text\">Pastikan Anda memilih berkas cadangan yang tepat</string>\n    <string name=\"captcha_required_summary\">%s memerlukan penyelesaian Chaptcha agar dapat diakses</string>\n    <string name=\"languages\">Bahasa</string>\n    <string name=\"unknown\">Tidak diketahui</string>\n    <string name=\"in_progress\">Dalam progres</string>\n    <string name=\"advanced\">Lanjutan</string>\n    <string name=\"manga_list\">Daftar manga</string>\n    <string name=\"error_corrupted_file\">Data yang dikembalikan tak sah atau berkas rusak</string>\n    <string name=\"on_device\">Dari perangkat</string>\n    <string name=\"items_limit_exceeded\">Tidak ada lagi item yang bisa ditambahkan</string>\n    <string name=\"directories\">Direktori</string>\n    <string name=\"main_screen_sections\">Bagian layar utama</string>\n    <string name=\"to_top\">Ke atas</string>\n    <string name=\"zoom_in\">Perbesar</string>\n    <string name=\"frequency_every_day\">Setiap hari</string>\n    <string name=\"categories\">Kategori</string>\n    <string name=\"frequency_every_2_days\">Setiap 2 Hari</string>\n    <string name=\"online_variant\">Varian daring</string>\n    <string name=\"keep_screen_on\">Biarkan Layar Menyala</string>\n    <string name=\"zoom_out\">Perkecil</string>\n    <string name=\"keep_screen_on_summary\">Jangan matikan layar saat membaca manga</string>\n    <string name=\"list_options\">Opsi daftar</string>\n    <string name=\"reader_zoom_buttons_summary\">Apakah menampilkan tombol kontrol zum di sudut kanan bawah</string>\n    <string name=\"backup_frequency\">Frekuensi pembuatan cadangan</string>\n    <string name=\"suggest_new_sources\">Sumber baru yang disarankan setelah pembaruan aplikasi</string>\n    <string name=\"periodic_backups_enable\">Aktifkan pencadangan berkala</string>\n    <string name=\"moved_to_top\">Bergerak ke atas</string>\n    <string name=\"enhanced_colors_summary\">Mengurangi banding, tetapi dapat mempengaruhi kinerja</string>\n    <string name=\"frequency_once_per_week\">Sekali dalam seminggu</string>\n    <string name=\"periodic_backups\">Pencadangan berkala</string>\n    <string name=\"reader_zoom_buttons\">Tampilkan tombol zum</string>\n    <string name=\"frequency_twice_per_month\">Dua kali sebulan</string>\n    <string name=\"by_relevance\">Relevansi</string>\n    <string name=\"state_abandoned\">Ditinggalkan</string>\n    <string name=\"frequency_once_per_month\">Sebulan sekali</string>\n    <string name=\"enhanced_colors\">mode warna 32-bit</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"last_successful_backup\">Cadangan sukses terakhir: %s</string>\n    <string name=\"backups_output_directory\">Direktori keluaran cadangan</string>\n    <string name=\"suggest_new_sources_summary\">Prompt untuk mengaktifkan sumber baru yang ditambahkan setelah memperbarui aplikasi</string>\n    <string name=\"sources_catalog\">Sumber katalog</string>\n    <string name=\"rating_safe\">Aman</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Dewasa</string>\n    <string name=\"error_filter_states_genre_not_supported\">Filter berdasarkan genre dan status tidak didukung untuk sumber ini</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Filter bedasarkan genre dan lokal tidak didukung oleh sumber ini</string>\n    <string name=\"content_type_comics\">Komik</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"welcome_text\">Silakan pilih sumber konten yang akan diaktifkan. Bisa dilakukan nanti di pengaturan</string>\n    <string name=\"genres_exclude\">Kecualikan genre</string>\n    <string name=\"reader_optimize\">Kurangi konsumsi memori (beta)</string>\n    <string name=\"apply\">Terapkan</string>\n    <string name=\"restore\">Kembalikan</string>\n    <string name=\"manage_sources\">Kelola sumber</string>\n    <string name=\"no_manga_sources_found\">Tidak ditemukan sumber manga berdasarkan kueri Anda</string>\n    <string name=\"genres_search_hint\">Ketik nama genre</string>\n    <string name=\"globally\">Secara global</string>\n    <string name=\"rating_adult\">Dewasa</string>\n    <string name=\"error_multiple_genres_not_supported\">Filter berdasarkan banyak genre sekaligus tidak didukung berdasarkan sumber manga ini</string>\n    <string name=\"this_manga\">Manga ini</string>\n    <string name=\"lock_screen_rotation\">Kunci rotasi layar</string>\n    <string name=\"skip\">Lewati</string>\n    <string name=\"error_search_not_supported\">Pencarian tidak didukung untuk sumber manga ini</string>\n    <string name=\"state_upcoming\">Mendatang</string>\n    <string name=\"color_correction_apply_text\">Pengaturan ini dapat diterapkan secara menyeluruh atau hanya pada manga saat ini. Jika diterapkan secara menyeluruh, pengaturan pada manga tidak akan ditimpa.</string>\n    <string name=\"source_enabled\">Sumber yang diaktifkan</string>\n    <string name=\"disable_nsfw_summary\">Nonaktifkan sumber TAUSB dan sembunyikan manga dewasa dari daftar jika memungkinkan</string>\n    <string name=\"content_rating\">Peringkat konten</string>\n    <string name=\"backup_date_\">Tanggal dicadangkan %s</string>\n    <string name=\"available_d\">Tersedia:%1$d</string>\n    <string name=\"state\">Status</string>\n    <string name=\"state_paused\">Dijeda</string>\n    <string name=\"content_type_other\">Lainnya</string>\n    <string name=\"sync_auth\">Masuk untuk sinkronkan akun</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"downloads_settings_info\">Anda dapat mengaktifkan perlambatan pengunduhan untuk setiap sumber manga satu per satu di pengaturan sumber, jika Anda mengalami masalah dengan pemblokiran sisi server</string>\n    <string name=\"default_tab\">Tab bawaan</string>\n    <string name=\"category_hidden_done\">Kategori ini telah disembunyikan dari tampilan utama dan dapat di akses melalui Menu -&gt; Manage categoties</string>\n    <string name=\"grayscale\">Skala abu-abu</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"no_manga_sources_catalog_text\">Tidak ada sumber yang tersedia di bagian ini, atau semuanya mungkin sudah ditambahkan.\n\\nPantau terus</string>\n    <string name=\"mark_as_completed\">Tandai telah selesai</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Mungkin membantu dengan memulai pengunduhan jika Anda memiliki masalah dengannya</string>\n    <string name=\"mark_as_completed_prompt\">Tandai manga yang dipilih telah selesai dibaca\n\\n\n\\nPeringatan : proses baca saat ini akan hilang.</string>\n    <string name=\"error_multiple_states_not_supported\">Filter berdasarkan banyak status tidak didukung berdasarkan sumber manga ini</string>\n    <string name=\"reader_optimize_summary\">Mengurangi kualitas halaman di luar layar untuk menggunakan lebih sedikit memori</string>\n    <string name=\"by_name_reverse\">Nama terbalik</string>\n    <string name=\"volume_\">Volume%d</string>\n    <string name=\"volume_unknown\">Volume tidak diketahui</string>\n    <string name=\"incognito_mode_hint\">Progres membaca Anda tidak akan tersimpan</string>\n    <string name=\"rating_suggestive\">Disarankan</string>\n    <string name=\"vertical\">Vertikal</string>\n    <string name=\"last_read\">Terakhir dibaca</string>\n    <string name=\"remaining_time_pattern\">%1$s%2$s</string>\n    <string name=\"toggle_ui\">Tampilkan/Sembunyikan UI</string>\n    <string name=\"prev_chapter\">Bab sebelumnya</string>\n    <string name=\"switch_pages_volume_buttons\">Aktifkan tombol volume</string>\n    <string name=\"reader_actions_summary\">Konfigurasikan tindakan untuk area layar yang dapat diketuk</string>\n    <string name=\"tap_action\">Tindakan saat diketuk</string>\n    <string name=\"reader_actions\">Tindakan saat membaca</string>\n    <string name=\"long_tap_action\">Tindakan saat diketuk lama</string>\n    <string name=\"none\">Tidak ada</string>\n    <string name=\"next_chapter\">Bab selanjutnya</string>\n    <string name=\"prev_page\">Halaman sebelumnya</string>\n    <string name=\"next_page\">Halaman selanjutnya</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Gunakan tombol volume untuk berpindah halaman</string>\n    <string name=\"show_menu\">Tampilkan menu</string>\n    <string name=\"config_reset_confirm\">Kembalikan pengaturan ke bawaan? Tindakan ini tidak bisa dibatalkan.</string>\n    <string name=\"use_two_pages_landscape\">Gunakan tata letak dua halaman pada orientasi lanskap (beta)</string>\n    <string name=\"email_password_enter_hint\">Masukkan surel dan kata sandi untuk melanjutkan</string>\n    <string name=\"reading_time_estimation_summary\">Perkiraan waktu mungkin tidak akurat</string>\n    <string name=\"suggestions_unavailable_text\">Fitur saran dinonaktifkan</string>\n    <string name=\"check_for_new_chapters_disabled\">Memeriksa bab baru dinonaktifkan</string>\n    <string name=\"show_labels_in_navbar\">Tampilkan label di bilah navigasi</string>\n    <string name=\"pages_saving\">Menyimpan halaman</string>\n    <string name=\"location\">Lokasi</string>\n    <string name=\"remove_from_history\">Hapus dari riwayat</string>\n    <string name=\"less_than_minute\">Kurang dari semenit</string>\n    <string name=\"statistics\">Statistik</string>\n    <string name=\"chapters_grid_view\">Tampilan kisi</string>\n    <string name=\"reading_time_estimation\">Tampilkan perkiraan waktu membaca</string>\n    <string name=\"ask_for_dest_dir_every_time\">Tanya terus direktori tujuan</string>\n    <string name=\"month\">Bulan</string>\n    <string name=\"day\">Hari</string>\n    <string name=\"three_months\">Tiga bulan</string>\n    <string name=\"alternatives\">Alternatif</string>\n    <string name=\"all_time\">Sepanjang waktu</string>\n    <string name=\"stats_cleared\">Statistik dibersihkan</string>\n    <string name=\"clear_stats_confirm\">Apakah Anda benar-benar ingin menghapus semua statistik membaca? Tindakan ini tidak bisa dikembalikan.</string>\n    <string name=\"week\">Pekan</string>\n    <string name=\"fullscreen_mode\">Mode layar penuh</string>\n    <string name=\"reader_fullscreen_summary\">Sembunyikan status sistem dan bilah navigasi</string>\n    <string name=\"automatic\">Otomatis</string>\n    <string name=\"default_webtoon_zoom_out\">Zoom webtoon default</string>\n    <string name=\"last_used\">Terakhir digunakan</string>\n    <string name=\"_new\">Terbaru</string>\n    <string name=\"hours_short\">%d j</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"hours_minutes_short\">%1$d j %2$d m</string>\n    <string name=\"default_page_save_dir\">Direktori penyimpanan halaman default</string>\n    <string name=\"clear_stats\">Bersihkan statistik</string>\n    <string name=\"delete_read_chapters_prompt\">Hal ini akan menghapus permanen semua bab yang ditandai telah dibaca dari penyimpanan lokal Anda. Anda dapat mengunduh ulang nanti, tetapi bab yang diimpor mungkin akan hilang selamanya</string>\n    <string name=\"reading_stats\">Statistik membaca</string>\n    <string name=\"other_manga\">Manga lainnya</string>\n    <string name=\"show_pages_thumbs\">Menampilkan gambar mini halaman</string>\n    <string name=\"show_updated\">Tampilkan yang diperbarui</string>\n    <string name=\"ignore_ssl_errors_summary\">Anda dapat menonaktifkan verifikasi sertifikat SSL jika Anda menghadapi masalah terkait SSL saat mengakses sumber daya jaringan. Hal ini dapat memengaruhi keamanan Anda. Diperlukan pengaktifan ulang aplikasi setelah mengubah pengaturan ini.</string>\n    <string name=\"multiple_cbz_files\">Beberapa file CBZ</string>\n    <string name=\"pages_read_s\">Halaman yang dibaca: %s</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" dari \\\"%2$s\\\" akan diganti dengan \\\"%3$s\\\" dari \\\"%4$s\\\" di riwayat dan favorit Anda (jika ada)</string>\n    <string name=\"unread\">Belum dibaca</string>\n    <string name=\"show_pages_thumbs_summary\">Aktifkan tab \\\"Halaman\\\" pada layar detail</string>\n    <string name=\"unsupported_backup_message\">Pilih file cadangan Kotatsu yang tepat</string>\n    <string name=\"fix\">Memperbaiki</string>\n    <string name=\"missing_storage_permission\">Tidak ada izin untuk mengakses manga di penyimpanan eksternal</string>\n    <string name=\"disable_nsfw_notifications_summary\">Jangan tampilkan notifikasi tentang pembaruan manga TAUSB</string>\n    <string name=\"search_suggestions\">Saran pencarian</string>\n    <string name=\"recent_queries\">Pertanyaan terbaru</string>\n    <string name=\"authors\">Penulis</string>\n    <string name=\"single_cbz_file\">File CBZ tunggal</string>\n    <string name=\"empty_stats_text\">Tidak ada statistik untuk periode yang dipilih</string>\n    <string name=\"preferred_download_format\">Format unduhan yang disukai</string>\n    <string name=\"migration_completed\">Migrasi selesai</string>\n    <string name=\"delete_read_chapters_summary\">Hapus bab yang telah Anda baca dari penyimpanan lokal untuk mengosongkan ruang</string>\n    <string name=\"migrate\">Migrasi</string>\n    <string name=\"manga_migration\">Manga migration</string>\n    <string name=\"delete_read_chapters_auto\">Otomatis hapus bab yang sudah dibaca</string>\n    <string name=\"runs_on_app_start\">Berjalan saat aplikasi dimulai</string>\n    <string name=\"delete_read_chapters\">Hapus bab yang sudah dibaca</string>\n    <string name=\"no_chapters_deleted\">Tidak ada bab yang dihapus</string>\n    <string name=\"split_by_translations\">Dibagi berdasarkan terjemahan</string>\n    <string name=\"split_by_translations_summary\">Menampilkan bab dengan terjemahan yang berbeda secara terpisah, bukan dalam satu daftar</string>\n    <string name=\"order_oldest\">Terlama</string>\n    <string name=\"long_ago_read\">Dulu pernah dibaca</string>\n    <string name=\"webtoon_gaps\">Celah dalam mode webtoon</string>\n    <string name=\"more_frequently\">Lebih sering</string>\n    <string name=\"webtoon_gaps_summary\">Menampilkan celah vertikal di antara halaman dalam mode webtoon</string>\n    <string name=\"frequency_of_check\">Frekuensi pemeriksaan</string>\n    <string name=\"pin_navigation_ui\">Pin UI navigasi</string>\n    <string name=\"tracker_debug_info\">Memeriksa log bab baru</string>\n    <string name=\"disable_connectivity_check\">Menonaktifkan pemeriksaan konektivitas</string>\n    <string name=\"disable_connectivity_check_summary\">Lewati pemeriksaan konektivitas jika Anda mengalami masalah dengan konektivitas (misalnya, masuk ke mode offline meskipun jaringan tersambung)</string>\n    <string name=\"disable_nsfw_notifications\">Nonaktifkan notifikasi TAUSB</string>\n    <string name=\"tracker_debug_info_summary\">Informasi debug tentang pemeriksaan latar belakang untuk bab baru</string>\n    <string name=\"all_languages\">Semua bahasa</string>\n    <string name=\"screenshots_block_incognito\">Blokir saat mode penyamaran</string>\n    <string name=\"error_no_data_received\">Tidak ada data yang diterima dari server</string>\n    <string name=\"disable\">Nonaktifkan</string>\n    <string name=\"sources_disabled\">Sumber dinonaktifkan</string>\n    <string name=\"enable_source\">Aktifkan sumber</string>\n    <string name=\"unsupported_source\">Sumber manga ini tidak didukung</string>\n    <string name=\"blocked_by_server_message\">Anda diblokir oleh server. Coba gunakan koneksi jaringan yang berbeda (VPN, Proxy, dll.)</string>\n    <string name=\"less_frequently\">Lebih jarang</string>\n    <string name=\"pin_navigation_ui_summary\">Jangan sembunyikan bilah navigasi dan tampilan pencarian saat menggulir</string>\n    <string name=\"chapters_deleted_pattern\">%1$s dihapus, %2$s dibersihkan</string>\n    <string name=\"suggested_queries\">Pertanyaan yang disarankan</string>\n    <string name=\"source_pinned\">Sumber disematkan</string>\n    <string name=\"recent_sources\">Sumber sumbver terbaru</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Tidak ada manga sesuai filter yang ditemukan</string>\n    <string name=\"image_server\">Server gambar yang dipilih</string>\n    <string name=\"crop_pages\">Potong Halaman</string>\n    <string name=\"pin\">Sematkan</string>\n    <string name=\"unpin\">Batal sematkan</string>\n    <string name=\"source_unpinned\">Sumber yang tidak disematkan</string>\n    <string name=\"scrobbler_auth_required\">Masuk ke %s untuk melanjutkan</string>\n    <string name=\"unstable_feature\">Fitur tidak stabil</string>\n    <string name=\"unstable_feature_summary\">Fungsi ini bersifat eksperimental. Pastikan Anda memiliki cadangan untuk menghindari kehilangan data</string>\n    <string name=\"invalid_proxy_configuration\">Konfigurasi proxy tidak valid</string>\n    <string name=\"chapters_read\">Bab yang dibaca</string>\n    <string name=\"percent_read\">Persen dibaca</string>\n    <string name=\"chapters_left\">Bab yang tersisa</string>\n    <string name=\"external_source\">Eksternal/plugin</string>\n    <string name=\"sources_unpinned\">Sumber tidak disematkan</string>\n    <string name=\"sources_pinned\">Sumber disematkan</string>\n    <string name=\"percent_left\">Persen tersisa</string>\n    <string name=\"scrobbler_auth_intro\">Masuk untuk mengatur integrasi dengan %s. Ini akan memungkinkan Anda untuk melacak kemajuan dan status membaca manga Anda</string>\n    <string name=\"retry\">Ulangi</string>\n    <string name=\"invalid_server_address_message\">Alamat server tidak valid</string>\n    <string name=\"too_many_requests_message_retry\">Terlalu banyak permintaan. Coba lagi setelah %s</string>\n    <string name=\"seconds_short\">%d d</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d d</string>\n    <string name=\"connection_ok\">Koneksi OK</string>\n    <string name=\"show_quick_filters\">Menampilkan filter cepat</string>\n    <string name=\"show_quick_filters_summary\">Memberikan kemampuan untuk memfilter daftar manga berdasarkan parameter tertentu</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"skip_all\">Lewati semua</string>\n    <string name=\"not_in_favorites\">Tidak di kesukaan</string>\n    <string name=\"plugin_incompatible\">Plugin yang tidak kompatibel atau kesalahan internal. Pastikan Anda menggunakan versi terbaru dari plugin dan Kotatsu</string>\n    <string name=\"updated_long_ago\">Diperbarui sejak lama</string>\n    <string name=\"unpopular\">Tidak populer</string>\n    <string name=\"low_rating\">Rating rendah</string>\n    <string name=\"sort_order_asc\">Naik</string>\n    <string name=\"sort_order_desc\">Turun</string>\n    <string name=\"by_date\">Tanggal</string>\n    <string name=\"popularity\">Popularitas</string>\n    <string name=\"recently_added\">Baru ditambahkan</string>\n    <string name=\"popular_today\">Populer hari ini</string>\n    <string name=\"popular_in_year\">Populer tahun ini</string>\n    <string name=\"original_language\">Bahasa asli</string>\n    <string name=\"year\">Tahun</string>\n    <string name=\"show_slider\">Tampilkan penggeser</string>\n    <string name=\"backup_tg_check\">Periksa apakah API bekerja</string>\n    <string name=\"backup_tg_echo\">Tes pesan</string>\n    <string name=\"telegram_chat_id\">ID chat Telegram</string>\n    <string name=\"pages_saved\">Halaman disimpan</string>\n    <string name=\"save_manga_confirm\">Simpan manga yang dipilih? Ini akan mengkonsumsi data dan ruang penyimpanan</string>\n    <string name=\"no_fix_required\">Perbaikan tidak diperlukan untuk \\\"%s\\\"</string>\n    <string name=\"save_manga\">Simpan manga</string>\n    <string name=\"portrait\">Potret</string>\n    <string name=\"genre\">Genre</string>\n    <string name=\"download_added\">Unduhan ditambahkan</string>\n    <string name=\"enable_all_sources_summary\">Semua sumber manga yang tersedia akan diaktifkan permanen</string>\n    <string name=\"all_sources_enabled\">Semua sumber diaktifkan</string>\n    <string name=\"email\">Surel</string>\n    <string name=\"downloads_background\">Pengunduhan latar belakang</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"years\">Tahun</string>\n    <string name=\"content_type_image_set\">Set gambar</string>\n    <string name=\"start_download\">Mulai unduh</string>\n    <string name=\"download_over_cellular\">Mengunduh melalui jaringan seluler</string>\n    <string name=\"chapters_all\">Semua</string>\n    <string name=\"allow_once\">Izinkan sekali</string>\n    <string name=\"screen_orientation\">Orientasi layar</string>\n    <string name=\"ask_every_time\">Selalu tanya</string>\n    <string name=\"landscape\">Lanskap</string>\n    <string name=\"access_denied_403\">Akses ditolak (403)</string>\n    <string name=\"max_backups_count\">Jumlah maks cadangan</string>\n    <string name=\"error_not_image\">Format tidak valid: seharusnya gambar tetapi %s</string>\n    <string name=\"clear_database_summary\">Hapus informasi tentang manga yang tidak digunakan</string>\n    <string name=\"clear_database\">Bersihkan basisdata</string>\n    <string name=\"no_alternatives_found\">Alternatif tidak ditemukan untuk \\\"%s\\\"</string>\n    <string name=\"test_connection\">Tes koneksi</string>\n    <string name=\"send_backups_telegram\">Kirim cadangan di Telegram</string>\n    <string name=\"telegram_chat_id_summary\">Masukkan ID chat untuk pengiriman cadangan</string>\n    <string name=\"open_telegram_bot_summary\">Tekan untuk membuka chat dengan Bot Cadangan Kotatsu</string>\n    <string name=\"delete_old_backups\">Hapus cadangan lama</string>\n    <string name=\"delete_old_backups_summary\">Otomatis menghapus berkas cadangan lama untuk menghemat ruang penyimpanan</string>\n    <string name=\"debug\">Awakutu</string>\n    <string name=\"allow_always\">Selalu izinkan</string>\n    <string name=\"filter_search_warning\">Sumber ini tidak mendukung pencarian dengan filter. Filter Anda telah dikosongkan</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) diganti dengan \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"content_type_manhua\">Komik Mandarin (manhua)</string>\n    <string name=\"user_manual\">Manual pengguna</string>\n    <string name=\"destination_directory\">Direktori tujuan</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"download_new_chapters\">Unduh bab baru</string>\n    <string name=\"content_type_novel\">Novel</string>\n    <string name=\"content_type_manhwa\">Komik Korea (Manhwa)</string>\n    <string name=\"source_code\">Kode sumber</string>\n    <string name=\"download_cellular_confirm\">Izinkan mengunduh melalui jaringan seluler?</string>\n    <string name=\"demographics\">Demografis</string>\n    <string name=\"telegram_group\">Grup telegram</string>\n    <string name=\"more_options\">Opsi lainnya</string>\n    <string name=\"popular_in_week\">Populer minggu ini</string>\n    <string name=\"popular_in_month\">Populer bulan ini</string>\n    <string name=\"dont_allow\">Jangan izinkan</string>\n    <string name=\"open_telegram_bot\">Buka bot Telegram</string>\n    <string name=\"enable_all_sources\">Aktifkan semua sumber manga</string>\n    <string name=\"backup_tg_id_not_set\">ID chat belum diset</string>\n    <string name=\"captcha_required_message\">Sumber ini membutuhkan penyelesaian captcha untuk melanjutkan</string>\n    <string name=\"error_image_format\">Format gambar tidak didukung: %s</string>\n    <string name=\"translation\">Terjemahan</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga dengan bab terunduh</string>\n    <string name=\"fixed\">Berhasil diperbaiki</string>\n    <string name=\"manga_fix_prompt\">Fungsi ini akan mencari sumber alternatif untuk manga yang dipilih. Akan membutuhkan waktu sesaat dan dikerjakan di latar belakang</string>\n    <string name=\"fixing_manga\">Memperbaiki manga</string>\n    <string name=\"chapter_selection_hint\">Anda bisa memilih bab untuk diunduh dengan menekan lama item di dalam daftar bab.</string>\n    <string name=\"rating\">Nilai</string>\n    <string name=\"source\">Sumber</string>\n    <string name=\"added_long_ago\">Ditambahkan sejak lama</string>\n    <string name=\"popular_in_hour\">Populer Dalam Satu Jam Terakhir</string>\n    <string name=\"stuck\">Bahasa</string>\n    <string name=\"error_connection_reset\">Koneksi diatur ulang oleh host jarak jauh</string>\n    <string name=\"incognito\">Mode Penyamaran</string>\n    <string name=\"demographic_kodomo\">Anak</string>\n    <string name=\"content_type_one_shot\">Satu tembakan</string>\n    <string name=\"content_type_doujinshi\">Komik independen</string>\n    <string name=\"content_type_artist_cg\">Artis CG</string>\n    <string name=\"reader_info_bar_transparent\">Bilah informasi pembaca transparan</string>\n    <string name=\"handle_links\">Tangani Tautan</string>\n    <string name=\"handle_links_summary\">Menangani tautan manga dari aplikasi eksternal (misalnya browser web). Anda mungkin juga perlu mengaktifkannya secara manual di pengaturan sistem aplikasi</string>\n    <string name=\"any\">Setiap</string>\n    <string name=\"author\">Pengarang</string>\n    <string name=\"plugin_incompatible_with_cause\">Kesalahan plugin: %s\\n.Pastikan Anda menggunakan plugin dan Kotatsu versi terbaru</string>\n    <string name=\"restoring_backup\">Memulihkan Cadangan</string>\n    <string name=\"backup_restored_background\">Pencadangan akan dipulihkan di latar belakang</string>\n    <string name=\"clear_browser_data\">Hapus data peramban</string>\n    <string name=\"error_details\">Detail kesalahan</string>\n    <string name=\"no_write_permission_to_file\">Tidak memiliki izin untuk menulis file</string>\n    <string name=\"error_disclaimer_manga\">Cobalah membuka manga di peramban web untuk memastikan manga tersedia di sumbernya.</string>\n    <string name=\"error_disclaimer_report\">Anda dapat mengirimkan laporan bug kepada pengembang. Ini akan membantu kami menyelidiki dan memperbaiki masalah tersebut.</string>\n    <string name=\"clear_browser_data_summary\">Hapus data browser seperti cache dan cookie. Peringatan: Otorisasi dalam sumber manga mungkin menjadi tidak valid</string>\n    <string name=\"include_disabled_sources\">Sertakan sumber yang dinonaktifkan</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Manga dewasa tidak akan ditampilkan dalam saran. Pilihan ini mungkin tidak akurat pada beberapa</string>\n    <string name=\"search_disabled_sources\">Cari melalui sumber yang dinonaktifkan</string>\n    <string name=\"content_type_game_cg\">Permainan CG</string>\n    <string name=\"unnamed_chapter\">Bab tanpa nama</string>\n    <string name=\"chapter_volume_number\">Vol %1$sBab%2$s</string>\n    <string name=\"chapter_number\">Bab%s</string>\n    <string name=\"simple\">Simpel</string>\n    <string name=\"reader_controls_in_bottom_bar\">Kontrol pembaca di bilah bawah</string>\n    <string name=\"chapters_and_pages\">Bab dan halaman</string>\n    <string name=\"pages_slider\">Penggeser pengalih halaman</string>\n    <string name=\"screen_rotation_locked\">Rotasi layar dikunci</string>\n    <string name=\"screen_rotation_unlocked\">Rotasi layar telah dibuka kuncinya</string>\n    <string name=\"error_disclaimer_app_outdated\">Sepertinya versi Kotatsu Anda sudah kedaluwarsa. Harap instal versi terbaru untuk mendapatkan semua perbaikan yang tersedia.</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"link_to_manga_on_s\">Tautan ke manga di%s</string>\n    <string name=\"link_to_manga_in_app\">Tautan ke manga di kotatsu</string>\n    <string name=\"suggestions_disabled_sources_summary\">Tampilkan saran dari semua sumber manga, termasuk yang dinonaktifkan</string>\n    <string name=\"disable_captcha_notifications\">Nonaktifkan notifikasi captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Anda tidak akan menerima pemberitahuan tentang penyelesaian CAPTCHA untuk sumber ini, tetapi hal ini dapat menyebabkan penghentian operasi latar belakang (memeriksa bab baru, memperoleh rekomendasi, dll.)</string>\n    <string name=\"global_search\">Pencarian global</string>\n    <string name=\"search_everywhere\">Cari dimana saja</string>\n    <string name=\"badges_in_lists\">Lencana dalam daftar</string>\n    <string name=\"tags_warnings\">tandai genre berbahaya</string>\n    <string name=\"tags_warnings_summary\">Tandai genre yang mungkin tidak pantas untuk sebagian besar pengguna</string>\n    <string name=\"error_non_file_uri\">Jalur yang dipilih tidak dapat digunakan karena tidak menunjukkan berkas atau direktori</string>\n    <string name=\"use_default_cover\">gunakan penutup default</string>\n    <string name=\"pick_manga_page\">Pilih halaman manga</string>\n    <string name=\"pick_custom_file\">Pilih file khusus</string>\n    <string name=\"change_cover\">Ganti penutup</string>\n    <string name=\"theme_name_expressive\">Ekspresif (Test)</string>\n    <string name=\"page_switch_timer\">Halaman akan berganti setiap ~%d detik</string>\n    <string name=\"expand\">Memperluas</string>\n    <string name=\"adblock\">Blokir iklan di browser</string>\n    <string name=\"dont_ask_again\">Jangan bertanya lagi</string>\n    <string name=\"incognito_for_nsfw\">Mode penyamaran untuk manga NSFW</string>\n    <string name=\"incognito_mode_hint_nsfw\">Manga ini mungkin mengandung konten dewasa. Apakah Anda ingin menggunakan mode penyamaran?</string>\n    <string name=\"creating_backup\">Membuat cadangan</string>\n    <string name=\"share_backup\">Bagikan cadangan</string>\n    <string name=\"hide_from_main_screen\">Sembunyikan dari layar utama</string>\n    <string name=\"collapse_long_description\">Tutup deskripsi panjang</string>\n    <string name=\"changelog\">Catatan Perubahan</string>\n    <string name=\"changelog_summary\">Riwayat perubahan untuk versi yang baru dirilis</string>\n    <string name=\"reader_multitask_summary\">Memungkinkan anda untuk tetap membuka beberapa \\\"pembaca\\\" dengan manga yang berbeda pada saat yang sama</string>\n    <string name=\"manga_override_hint\">Perubahan ini akan memengaruhi cara manga ditampilkan di aplikasi</string>\n    <string name=\"additional_action_required\">Tindakan tambahan diperlukan</string>\n    <string name=\"adblock_summary\">Blokir iklan di browser bawaan (beta)</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"reader_multitask\">Buka pembaca secara terpisah</string>\n    <string name=\"reader_navigation_inverted_summary\">Tukar arah tombol volume dan tombol arah navigasi perangkat keras (kiri/atas/bawah/kanan)</string>\n    <string name=\"reader_navigation_inverted\">Balik kontrol navigasi</string>\n    <string name=\"book_effect\">Latar belakang kekuning-kuningan (filter biru)</string>\n    <string name=\"packup_creation_failed\">Gagal membuat cadangan</string>\n    <string name=\"main_screen\">Layar utama</string>\n    <string name=\"main_screen_fab\">Tampilkan tombol Lanjutkan mengambang</string>\n    <string name=\"error_corrupted_zip\">Arsip ZIP rusak (%s)</string>\n    <string name=\"main_screen_fab_summary\">Memungkinkan untuk melanjutkan membaca dengan satu klik. Tombol ini tidak akan ditampilkan dalam mode privat atau saat riwayat kosong</string>\n    <string name=\"local_storage_cleanup\">Pembersihan penyimpanan lokal</string>\n    <string name=\"collapse\">Sembunyikan</string>\n    <string name=\"discord_rpc\">Status Aktivitas Discord</string>\n    <string name=\"discord_token\">Token Discord</string>\n    <string name=\"discord_token_summary\">Masukkan Token Discord Anda untuk mengaktifkan status aktivitas</string>\n    <string name=\"discord_token_description\">Masukkan Token Discord Anda atau klik %s untuk mendapatkannya melalui browser</string>\n    <string name=\"discord_token_hint\">Tempelkan Token Discord Anda di sini</string>\n    <string name=\"discord_rpc_summary\">Tampilkan status membaca Anda di Discord</string>\n    <string name=\"discord_rpc_description\">Membaca manga di Kotatsu - aplikasi pembaca manga</string>\n    <string name=\"manga_restricted_description\">Manga ini tidak tersedia untuk dibaca di sumber ini. Coba cari di sumber lain atau buka di browser untuk informasi lebih lanjut</string>\n    <string name=\"chapters_load_failed\">Gagal memuat daftar bab</string>\n    <string name=\"rpc_skip_nsfw_summary\">Jangan gunakan RPC untuk konten dewasa</string>\n    <string name=\"show_floating_control_button\">Tampilkan tombol kontrol mengambang</string>\n    <string name=\"unavailable\">Tidak tersedia</string>\n    <string name=\"invalid_token\">Token tidak valid: %s</string>\n    <string name=\"no_chapters_in_manga\">Manga ini tidak mengandung bab apa pun</string>\n    <string name=\"telegram_integration\">Integrasi Telegram</string>\n    <string name=\"test_parser\">Uji sumber manga</string>\n    <string name=\"reading_s\">Membaca %s</string>\n    <string name=\"read_on_s\">Baca terus %s</string>\n    <string name=\"obtain\">Memperoleh</string>\n    <string name=\"pull_to_prev_chapter\">Lepas untuk membuka bab sebelumnya</string>\n    <string name=\"pull_to_next_chapter\">Lepas untuk membuka bab selanjutnya</string>\n    <string name=\"pull_top_no_prev\">Tidak ada bab sebelumnya</string>\n    <string name=\"pull_bottom_no_next\">Tidak ada bab berikutnya</string>\n    <string name=\"enable_pull_gesture_title\">Aktifkan gerakan tarik</string>\n    <string name=\"enable_pull_gesture_summary\">Gunakan gerakan tarik untuk pindah bab di webtoon</string>\n    <string name=\"two_page_scroll_sensitivity\">Sensitivitas Gulir Dua Halaman</string>\n    <string name=\"saved_filters\">Filter tersimpan</string>\n    <string name=\"enter_name\">Masukkan nama</string>\n    <string name=\"reader_chapter_toast\">Tampilkan popup perubahan bab</string>\n    <string name=\"reader_chapter_toast_summary\">Tampilkan pesan pop-up dengan judul bab saat diubah</string>\n    <string name=\"rename\">Ganti nama</string>\n    <string name=\"save_filter\">Simpan filter</string>\n    <string name=\"overwrite\">Timpa</string>\n    <string name=\"filter_overwrite_confirm\">Filter bernama \\\"%s\\\" sudah ada. Ingin menimpanya?</string>\n    <string name=\"storage_and_network\">Penyimpanan dan jaringan</string>\n    <string name=\"create_or_restore_backup\">Buat atau pulihkan cadangan</string>\n    <string name=\"data_removal\">Penghapusan data</string>\n    <string name=\"source_broken_warning\">Sumber manga ini telah ditandai rusak. Beberapa fitur mungkin tidak berfungsi</string>\n    <string name=\"privacy\">Pribadi</string>\n    <string name=\"frequency_every_6_hours\">Setiap 6 jam</string>\n    <string name=\"download_default_directory\">Direktori default untuk mengunduh manga</string>\n    <string name=\"private_app_directory_warning\">Direktori ini beserta semua datanya akan dihapus jika Anda menghapus aplikasi</string>\n    <string name=\"available_pattern\">%1$s tersedia</string>\n    <string name=\"pinned_sources_only\">Hanya sumber yang disematkan</string>\n    <string name=\"hide_empty_sources\">Sembunyikan sumber kosong</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-it/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nuovo capitolo</item>\n        <item quantity=\"many\">%1$d nuovi capitoli</item>\n        <item quantity=\"other\">%1$d nuovi capitoli</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d elemento</item>\n        <item quantity=\"many\">%1$d elementi</item>\n        <item quantity=\"other\">%1$d elementi</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d capitolo</item>\n        <item quantity=\"many\">%1$d capitoli</item>\n        <item quantity=\"other\">%1$d capitoli</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minuto fa</item>\n        <item quantity=\"many\">%1$d minuti fa</item>\n        <item quantity=\"other\">%1$d minuti fa</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d ora fa</item>\n        <item quantity=\"many\">%1$d ore fa</item>\n        <item quantity=\"other\">%1$d ore fa</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d giorno fa</item>\n        <item quantity=\"many\">%1$d giorni fa</item>\n        <item quantity=\"other\">%1$d giorni fa</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d mese fa</item>\n        <item quantity=\"many\">%1$d mesi fa</item>\n        <item quantity=\"other\">%1$d mesi fa</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d ora</item>\n        <item quantity=\"many\">%1$d ore</item>\n        <item quantity=\"other\">%1$d ore</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuto</item>\n        <item quantity=\"many\">%1$d minuti</item>\n        <item quantity=\"other\">%1$d minuti</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"no_description\">Nessuna descrizione</string>\n    <string name=\"text_file_not_supported\">File non valido. Sono supportati solo ZIP e CBZ.</string>\n    <string name=\"operation_not_supported\">Questa operazione non è supportata</string>\n    <string name=\"delete\">Elimina</string>\n    <string name=\"_import\">Importa</string>\n    <string name=\"share_image\">Condividi l\\'immagine</string>\n    <string name=\"page_saved\">Pagina salvata correttamente</string>\n    <string name=\"save_page\">Salva la pagina</string>\n    <string name=\"pages\">Pagine</string>\n    <string name=\"follow_system\">Automatico</string>\n    <string name=\"dark\">Scuro</string>\n    <string name=\"light\">Chiaro</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"filter\">Filtro</string>\n    <string name=\"sort_order\">Ordinamento</string>\n    <string name=\"by_rating\">Per valutazione</string>\n    <string name=\"newest\">Più recente</string>\n    <string name=\"updated\">Aggiornato</string>\n    <string name=\"popular\">Popolare</string>\n    <string name=\"by_name\">Per nome</string>\n    <string name=\"downloads\">Scaricati</string>\n    <string name=\"download_complete\">Download finito</string>\n    <string name=\"processing_\">Elaborazione…</string>\n    <string name=\"manga_downloading_\">Downloading…</string>\n    <string name=\"search_manga\">Cerca manga</string>\n    <string name=\"search\">Cerca</string>\n    <string name=\"share_s\">Condividi %s</string>\n    <string name=\"create_shortcut\">Crea scorciatoia</string>\n    <string name=\"share\">Condividi</string>\n    <string name=\"save\">Salva</string>\n    <string name=\"add\">Aggiungi</string>\n    <string name=\"add_new_category\">Aggiungi una nuova categoria</string>\n    <string name=\"add_to_favourites\">Aggiungi ai preferiti</string>\n    <string name=\"you_have_not_favourites_yet\">Non hai ancora preferiti</string>\n    <string name=\"read\">Leggi</string>\n    <string name=\"history_is_empty\">La cronologia è vuota</string>\n    <string name=\"nothing_found\">Niente di trovato</string>\n    <string name=\"clear_history\">Cancella la cronologia</string>\n    <string name=\"try_again\">Riprova</string>\n    <string name=\"text_clear_updates_feed_prompt\">Tutta la cronologia degli aggiornamenti sarà cancellata e questa azione non può essere annullata. Sei sicuro/a\\?</string>\n    <string name=\"close\">Chiudi</string>\n    <string name=\"chapter_d_of_d\">Capitolo %1$d di %2$d</string>\n    <string name=\"loading_\">Caricamento…</string>\n    <string name=\"remote_sources\">Fonti manga</string>\n    <string name=\"settings\">Impostazioni</string>\n    <string name=\"list_mode\">Modalità elenco</string>\n    <string name=\"grid\">Griglia</string>\n    <string name=\"detailed_list\">Elenco dettagliato</string>\n    <string name=\"text_delete_local_manga\">Vuoi davvero eliminare \\\" %s\\\" dalla memoria locale del tuo dispositivo?</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" eliminato dall\\'archiviazione locale</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"chapters\">Capitoli</string>\n    <string name=\"details\">Dettagli</string>\n    <string name=\"network_error\">Errore di connessione</string>\n    <string name=\"error_occurred\">Si è verificato un errore</string>\n    <string name=\"history\">Cronologia</string>\n    <string name=\"favourites\">Preferiti</string>\n    <string name=\"local_storage\">Archiviazione locale</string>\n    <string name=\"read_more\">Leggi di più</string>\n    <string name=\"welcome\">Benvenuto/a</string>\n    <string name=\"text_clear_search_history_prompt\">Vuoi davvero rimuovere tutte le ricerche recenti\\?</string>\n    <string name=\"password_length_hint\">La password deve essere di almeno 4 caratteri</string>\n    <string name=\"confirm\">Conferma</string>\n    <string name=\"protect_application_subtitle\">Inserisci la password che sarà richiesta all\\'avvio dell\\'applicazione</string>\n    <string name=\"next\">Prossimo</string>\n    <string name=\"sign_in\">Accedi</string>\n    <string name=\"clear_feed\">Cancella il flusso</string>\n    <string name=\"silent\">Silenzioso</string>\n    <string name=\"tap_to_try_again\">Tocca per riprovare</string>\n    <string name=\"today\">Oggi</string>\n    <string name=\"group\">Gruppo</string>\n    <string name=\"long_ago\">Molto tempo fa</string>\n    <string name=\"yesterday\">Ieri</string>\n    <string name=\"just_now\">Proprio ora</string>\n    <string name=\"file_not_found\">File non trovato</string>\n    <string name=\"preparing_\">Preparazione…</string>\n    <string name=\"data_restored\">Dati ripristinati</string>\n    <string name=\"restore_backup\">Ripristina da un backup</string>\n    <string name=\"create_backup\">Crea un backup dei dati</string>\n    <string name=\"backup_restore\">Backup e ripristino</string>\n    <string name=\"black_dark_theme_summary\">Utile per gli schermi AMOLED</string>\n    <string name=\"black_dark_theme\">Tema nero scuro</string>\n    <string name=\"create_category\">Nuova categoria</string>\n    <string name=\"right_to_left\">Da destra a sinistra</string>\n    <string name=\"no_update_available\">Nessun aggiornamento disponibile</string>\n    <string name=\"check_for_updates\">Controlla gli aggiornamenti</string>\n    <string name=\"app_version\">Versione %s</string>\n    <string name=\"about\">Informazioni</string>\n    <string name=\"passwords_mismatch\">Le password non corrispondono</string>\n    <string name=\"repeat_password\">Ripeti la password</string>\n    <string name=\"protect_application_summary\">Chiedi la password all\\'avvio dell\\'applicazione</string>\n    <string name=\"protect_application\">Proteggi l\\'applicazione</string>\n    <string name=\"wrong_password\">Password sbagliata</string>\n    <string name=\"enter_password\">Inserisci la password</string>\n    <string name=\"dont_check\">Non controllare</string>\n    <string name=\"track_sources\">Controlla gli aggiornamenti per il manga</string>\n    <string name=\"feed_will_update_soon\">L\\'aggiornamento del flusso inizierà presto</string>\n    <string name=\"update\">Aggiorna</string>\n    <string name=\"rotate_screen\">Ruota lo schermo</string>\n    <string name=\"updates_feed_cleared\">Flusso degli aggiornamenti cancellato</string>\n    <string name=\"clear_updates_feed\">Cancella il flusso degli aggiornamenti</string>\n    <string name=\"size_s\">Dimensioni: %s</string>\n    <string name=\"new_version_s\">Nuova versione: %s</string>\n    <string name=\"search_results\">Risultati della ricerca</string>\n    <string name=\"text_feed_holder\">Qui potrai vedere i nuovi capitoli del manga che stai leggendo</string>\n    <string name=\"updates\">Aggiornamenti</string>\n    <string name=\"read_later\">Leggi più tardi</string>\n    <string name=\"favourites_category_empty\">Questa categoria è vuota</string>\n    <string name=\"all_favourites\">Tutti i preferiti</string>\n    <string name=\"done\">Finito</string>\n    <string name=\"other_storage\">Altro spazio di archiviazione</string>\n    <string name=\"cannot_find_available_storage\">Impossibile trovare uno spazio di archiviazione disponibile</string>\n    <string name=\"not_available\">Non disponibile</string>\n    <string name=\"manga_save_location\">Cartella download</string>\n    <string name=\"pages_animation\">Animazione delle pagine</string>\n    <string name=\"recent_manga\">Manga recenti</string>\n    <string name=\"manga_shelf\">Scaffale da manga</string>\n    <string name=\"text_local_holder_secondary\">Salva qualcosa da un catalogo online o importalo da un file.</string>\n    <string name=\"text_local_holder_primary\">Non hai ancora salvato nessun manga</string>\n    <string name=\"text_history_holder_secondary\">Trova cosa leggere nella sezione «Esplora»</string>\n    <string name=\"text_history_holder_primary\">I manga che stai leggendo saranno visualizzati qui</string>\n    <string name=\"text_search_holder_secondary\">Prova a riformulare la domanda.</string>\n    <string name=\"text_empty_holder_primary\">È un po\\' vuoto qui…</string>\n    <string name=\"remove_category\">Rimuovi la categoria</string>\n    <string name=\"favourites_categories\">Categorie di preferiti</string>\n    <string name=\"vibration\">Vibrazione</string>\n    <string name=\"light_indicator\">Indicatore luminoso</string>\n    <string name=\"notification_sound\">Suono di notifica</string>\n    <string name=\"notifications_settings\">Impostazioni notifiche</string>\n    <string name=\"download\">Scarica</string>\n    <string name=\"new_chapters\">Nuovi capitoli</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Abilitato %1$d di %2$d</string>\n    <string name=\"notifications\">Notifiche</string>\n    <string name=\"open_in_browser\">Apri nel browser</string>\n    <string name=\"app_update_available\">Un aggiornamento per l\\'applicazione è disponibile</string>\n    <string name=\"domain\">Dominio</string>\n    <string name=\"external_storage\">Archiviazione esterna</string>\n    <string name=\"internal_storage\">Archiviazione interna</string>\n    <string name=\"search_history_cleared\">Storia della ricerca cancellata</string>\n    <string name=\"clear_search_history\">Cancella la cronologia delle ricerche</string>\n    <string name=\"clear_thumbs_cache\">Svuota la cache delle miniature</string>\n    <string name=\"error\">Errore</string>\n    <string name=\"_continue\">Continua</string>\n    <string name=\"switch_pages\">Cambia pagina</string>\n    <string name=\"reader_settings\">Impostazioni del lettore</string>\n    <string name=\"delete_manga\">Elimina il manga</string>\n    <string name=\"search_on_s\">Ricerca su %s</string>\n    <string name=\"grid_size\">Dimensione della griglia</string>\n    <string name=\"read_mode\">Modalità lettura</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"clear_pages_cache\">Cancella la cache delle pagine</string>\n    <string name=\"remove\">Rimuovi</string>\n    <string name=\"clear\">Cancella</string>\n    <string name=\"tracker_warning\">Alcuni produttori possono cambiare il comportamento del sistema, che può interrompere le attività nello sfondo.</string>\n    <string name=\"backup_saved\">Backup salvato con successo</string>\n    <string name=\"default_s\">Predefinito: %s</string>\n    <string name=\"auth_required\">Devi autorizzare la visualizzazione di questo contenuto</string>\n    <string name=\"reverse\">Inverti</string>\n    <string name=\"check_for_new_chapters\">Controllo dei nuovi capitoli</string>\n    <string name=\"cookies_cleared\">Tutti i cookie sono stati rimossi</string>\n    <string name=\"clear_cookies\">Cancella i cookie</string>\n    <string name=\"captcha_solve\">Risolvi</string>\n    <string name=\"captcha_required\">CAPTCHA è richiesto</string>\n    <string name=\"reader_mode_hint\">La configurazione scelta sarà ricordata per questo manga</string>\n    <string name=\"backup_information\">Puoi creare un backup della tua cronologia e dei tuoi preferiti e ripristinarlo</string>\n    <string name=\"data_restored_with_errors\">I dati sono ripristinati, ma ci sono errori</string>\n    <string name=\"data_restored_success\">Tutti i dati ripristinati con successo</string>\n    <string name=\"zoom_mode_keep_start\">Tieni all\\'inizio</string>\n    <string name=\"zoom_mode_fit_height\">Adatta all\\'altezza</string>\n    <string name=\"zoom_mode_fit_center\">Adatta al centro</string>\n    <string name=\"zoom_mode_fit_width\">Adatta alla larghezza</string>\n    <string name=\"scale_mode\">Modalità scala</string>\n    <string name=\"chapter_is_missing\">Capitolo mancante</string>\n    <string name=\"queued\">In coda</string>\n    <string name=\"about_app_translation\">Traduzione</string>\n    <string name=\"about_app_translation_summary\">Traduci questa applicazione</string>\n    <string name=\"genres\">Generi</string>\n    <string name=\"text_clear_cookies_prompt\">Sarai disconnesso/a da tutte le fonti in cui sei autorizzato/a</string>\n    <string name=\"auth_not_supported_by\">L\\'autorizzazione su %s non è supportata</string>\n    <string name=\"auth_complete\">Autorizzazione completa</string>\n    <string name=\"state_finished\">Finito</string>\n    <string name=\"state_ongoing\">In corso</string>\n    <string name=\"system_default\">Predefinito</string>\n    <string name=\"exclude_nsfw_from_history\">Escludi i manga NSFW dalla storia</string>\n    <string name=\"show_pages_numbers\">Mostra i numeri delle pagine</string>\n    <string name=\"computing_\">Calcolo…</string>\n    <string name=\"screenshots_policy\">Politica sulle schermate</string>\n    <string name=\"screenshots_allow\">Permetti</string>\n    <string name=\"screenshots_block_nsfw\">Blocca per NSFW</string>\n    <string name=\"screenshots_block_all\">Blocca sempre</string>\n    <string name=\"suggestions_enable\">Abilita i suggerimenti</string>\n    <string name=\"suggestions_summary\">Suggerisci manga in base alle tue preferenze</string>\n    <string name=\"suggestions_info\">Tutti i dati vengono analizzati solo localmente su questo dispositivo e mai inviati da nessuna parte.</string>\n    <string name=\"text_suggestion_holder\">Inizia a leggere manga e riceverai suggerimenti personalizzati</string>\n    <string name=\"suggestions\">Suggerimenti</string>\n    <string name=\"enabled\">Abilitato</string>\n    <string name=\"disabled\">Disabilitato</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Non suggerire manga NSFW</string>\n    <string name=\"reset_filter\">Ripristina il filtro</string>\n    <string name=\"onboard_text\">Seleziona le lingue in cui vuoi leggere i manga. Puoi cambiarla in seguito nelle impostazioni.</string>\n    <string name=\"never\">Mai</string>\n    <string name=\"preload_pages\">Precarica le pagine</string>\n    <string name=\"only_using_wifi\">Solo usando il Wi-Fi</string>\n    <string name=\"always\">Sempre</string>\n    <string name=\"logged_in_as\">Registrato come %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Varie lingue</string>\n    <string name=\"search_chapters\">Trova un capitolo</string>\n    <string name=\"chapters_empty\">Nessun capitolo in questo manga</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Aspetto</string>\n    <string name=\"suggestions_updating\">Aggiornamento dei suggerimenti</string>\n    <string name=\"suggestions_excluded_genres_summary\">Specifica i generi che non vuoi vedere nei suggerimenti</string>\n    <string name=\"suggestions_excluded_genres\">Escludi generi</string>\n    <string name=\"removal_completed\">Rimozione completata</string>\n    <string name=\"text_delete_local_manga_batch\">Eliminare gli elementi selezionati dal dispositivo in modo permanente\\?</string>\n    <string name=\"download_slowdown\">Download lento</string>\n    <string name=\"local_manga_processing\">Elaborazione dei manga salvati</string>\n    <string name=\"chapters_will_removed_background\">I capitoli verranno rimossi in background</string>\n    <string name=\"download_slowdown_summary\">Aiuta ad evitare il blocco del tuo indirizzo IP</string>\n    <string name=\"hide\">Nascondi</string>\n    <string name=\"new_sources_text\">Sono disponibili nuove fonti di manga</string>\n    <string name=\"show_notification_new_chapters_on\">Riceverai notifiche sugli aggiornamenti del manga che stai leggendo</string>\n    <string name=\"notifications_enable\">Abilita le notifiche</string>\n    <string name=\"show_notification_new_chapters_off\">Non riceverai notifiche ma i nuovi capitoli saranno evidenziati nelle liste</string>\n    <string name=\"empty_favourite_categories\">Nessuna categoria preferita</string>\n    <string name=\"name\">Nome</string>\n    <string name=\"edit\">Modifica</string>\n    <string name=\"edit_category\">Modifica la categoria</string>\n    <string name=\"check_new_chapters_title\">Controlla i nuovi capitoli e notificarli</string>\n    <string name=\"bookmarks\">Segnalibri</string>\n    <string name=\"removed_from_history\">Rimosso dalla cronologia</string>\n    <string name=\"bookmark_remove\">Rimuovi il segnalibro</string>\n    <string name=\"bookmark_add\">Aggiungi un segnalibro</string>\n    <string name=\"bookmark_removed\">Segnalibro rimosso</string>\n    <string name=\"bookmark_added\">Segnalibro aggiunto</string>\n    <string name=\"undo\">Annulla</string>\n    <string name=\"default_mode\">Modalità predefinita</string>\n    <string name=\"detect_reader_mode\">Modalità di lettura a rilevamento automatico</string>\n    <string name=\"dns_over_https\">DNS su HTTPS</string>\n    <string name=\"detect_reader_mode_summary\">Rileva automaticamente se il manga è un webtoon</string>\n    <string name=\"disable_battery_optimization\">Disattiva l\\'ottimizzazione della batteria</string>\n    <string name=\"disable_battery_optimization_summary\">Contribuisce ai controlli di aggiornamento in sfondo</string>\n    <string name=\"crash_text\">Qualcosa è andato storto. Si prega di inviare una segnalazione di bug agli sviluppatori per aiutarci a risolvere il problema.</string>\n    <string name=\"send\">Invia</string>\n    <string name=\"disable_all\">Disabilita tutto</string>\n    <string name=\"use_fingerprint\">Usa le impronte digitali se disponibili</string>\n    <string name=\"appwidget_shelf_description\">Manga dai tuoi preferiti</string>\n    <string name=\"appwidget_recent_description\">I manga letti di recente</string>\n    <string name=\"report\">Segnala</string>\n    <string name=\"tracking\">Monitoraggio</string>\n    <string name=\"status_reading\">Lettura</string>\n    <string name=\"status_re_reading\">Rilettura</string>\n    <string name=\"status_on_hold\">In attesa</string>\n    <string name=\"show_reading_indicators\">Mostrare gli indicatori di progresso della lettura</string>\n    <string name=\"data_deletion\">Eliminazione dei dati</string>\n    <string name=\"show_reading_indicators_summary\">Mostra la percentuale di lettura nella cronologia e nei preferiti</string>\n    <string name=\"exclude_nsfw_from_history_summary\">I manga contrassegnati come per adulti non verranno mai aggiunti alla cronologia e i progressi non saranno salvati</string>\n    <string name=\"clear_cookies_summary\">Può aiutare in caso di problemi. Tutte le autorizzazioni saranno invalidate</string>\n    <string name=\"show_all\">Mostra tutto</string>\n    <string name=\"logout\">Esci</string>\n    <string name=\"status_planned\">Pianificato</string>\n    <string name=\"status_completed\">Finito</string>\n    <string name=\"status_dropped\">Caduto</string>\n    <string name=\"invalid_domain_message\">Dominio non valido</string>\n    <string name=\"select_range\">Seleziona l\\'intervallo</string>\n    <string name=\"not_found_404\">Contenuto non trovato o rimosso</string>\n    <string name=\"compact\">Compatto</string>\n    <string name=\"source_disabled\">Fonte disabilitata</string>\n    <string name=\"canceled\">Annullato</string>\n    <string name=\"server_error\">Errore lato server (%1$d). Riprovare più tardi</string>\n    <string name=\"clear_new_chapters_counters\">Informazioni chiare anche sui nuovi capitoli</string>\n    <string name=\"prefetch_content\">Precaricamento dei contenuti</string>\n    <string name=\"mark_as_current\">Contrassegna come corrente</string>\n    <string name=\"error_no_space_left\">Non c\\'è più spazio sul dispositivo</string>\n    <string name=\"network_unavailable\">La rete non è disponibile</string>\n    <string name=\"network_unavailable_hint\">Attiva il Wi-Fi o la rete mobile per leggere i manga in linea</string>\n    <string name=\"webtoon_zoom\">Zoom Webtoon</string>\n    <string name=\"account_already_exists\">Questo account già esiste</string>\n    <string name=\"back\">Indietro</string>\n    <string name=\"sync\">Sincronizzazione</string>\n    <string name=\"clear_all_history\">Cancella tutta la cronologia</string>\n    <string name=\"last_2_hours\">Ultime 2 ore</string>\n    <string name=\"history_cleared\">Cronologia cancellata</string>\n    <string name=\"manage\">Gestisci</string>\n    <string name=\"no_bookmarks_yet\">Non ci sono ancora segnalibri</string>\n    <string name=\"no_bookmarks_summary\">È possibile creare segnalibri durante la lettura dei manga</string>\n    <string name=\"bookmarks_removed\">Segnalibri rimossi</string>\n    <string name=\"no_manga_sources\">Nessuna fonte manga</string>\n    <string name=\"no_manga_sources_text\">Abilita le fonti manga per leggere manga in linea</string>\n    <string name=\"random\">Casuale</string>\n    <string name=\"reorder\">Riordina</string>\n    <string name=\"empty\">Vuoto</string>\n    <string name=\"explore\">Esplora</string>\n    <string name=\"incognito_mode\">Modalità Incognito</string>\n    <string name=\"import_completed\">Importazione completata</string>\n    <string name=\"import_completed_hint\">È possibile eliminare il file originale dalla memoria per risparmiare spazio</string>\n    <string name=\"confirm_exit\">Premi nuovamente Indietro per uscire</string>\n    <string name=\"exit_confirmation_summary\">Premi due volte Indietro per uscire dall\\'applicazione</string>\n    <string name=\"exit_confirmation\">Conferma di uscita</string>\n    <string name=\"saved_manga\">Manga salvati</string>\n    <string name=\"pages_cache\">Cache delle pagine</string>\n    <string name=\"other_cache\">Altra cache</string>\n    <string name=\"storage_usage\">Utilizzo dello spazio di archiviazione</string>\n    <string name=\"available\">Disponibile</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"removed_from_favourites\">Rimosso dai preferiti</string>\n    <string name=\"options\">Opzioni</string>\n    <string name=\"no_chapters\">Nessun capitolo</string>\n    <string name=\"automatic_scroll\">Scorrimento automatico</string>\n    <string name=\"reader_info_pattern\">Ca. %1$d/%2$d Pg. %3$d/%4$d</string>\n    <string name=\"comics_archive\">Archivio fumetti</string>\n    <string name=\"folder_with_images\">Cartella con immagini</string>\n    <string name=\"importing_manga\">Importazione di manga</string>\n    <string name=\"import_will_start_soon\">L\\'importazione inizierà presto</string>\n    <string name=\"feed\">Flusso</string>\n    <string name=\"reader_control_ltr_summary\">Non adeguare la direzione dello sfoglio alla modalità di lettura impostata. Ad esempio: premere la freccia destra passerà sempre alla pagina seguente. Questa impostazione ha effetto solo sui dispositivi di input hardware</string>\n    <string name=\"contrast\">Contrasto</string>\n    <string name=\"reset\">Ripristina</string>\n    <string name=\"reader_slider\">Mostra il cursore di cambio pagina</string>\n    <string name=\"color_correction\">Correzione del colore</string>\n    <string name=\"email_enter_hint\">Inserisci il tuo indirizzo e-mail per continuare</string>\n    <string name=\"sync_title\">Sincronizza i tuoi dati</string>\n    <string name=\"history_shortcuts\">Mostra i collegamenti ai manga recenti</string>\n    <string name=\"reader_control_ltr\">Controllo ergonomico del lettore</string>\n    <string name=\"brightness\">Luminosità</string>\n    <string name=\"categories_delete_confirm\">Sei sicuro/a di voler eliminare le categorie preferite selezionate? \\n Tutti i manga in esso contenuti andranno persi e questo non può essere annullato.</string>\n    <string name=\"history_shortcuts_summary\">Rendere disponibili i manga recenti premendo a lungo sull\\'icona dell\\'applicazione</string>\n    <string name=\"reader_info_bar\">Mostra la barra delle informazioni nel lettore</string>\n    <string name=\"manga_error_description_pattern\">Dettagli dell\\'errore:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Prova ad &lt;a href=%2$s&gt;aprire il manga in un browser web&lt;/a&gt; per assicurarsi che sia disponibile sulla sua fonte&lt;br&gt;2. Controllare di stare usando la &lt;a href=kotatsu://about&gt;versione più recente di Kotatsu&lt;/a&gt;&lt;br&gt;3. Se è disponibile, inviare una segnalazione di errore agli sviluppatori.</string>\n    <string name=\"text_unsaved_changes_prompt\">Salvare o eliminare le modifiche non salvate\\?</string>\n    <string name=\"discard\">Abbandona</string>\n    <string name=\"language\">Lingua</string>\n    <string name=\"share_logs\">Condividi i registri</string>\n    <string name=\"enable_logging_summary\">Registra alcune azioni a scopo di debug. Non attivare se non si è sicuri di cosa si stia facendo</string>\n    <string name=\"enable_logging\">Abilita la registrazione</string>\n    <string name=\"show_suspicious_content\">Mostra il contenuto sospetto</string>\n    <string name=\"theme_name_dynamic\">Dinamico</string>\n    <string name=\"color_theme\">Schema colori</string>\n    <string name=\"show_in_grid_view\">Mostra nella vista griglia</string>\n    <string name=\"scrobbling_empty_hint\">Per tenere traccia dell\\'avanzamento della lettura, seleziona Menu → Traccia nella schermata dei dettagli del manga.</string>\n    <string name=\"nothing_here\">Non c\\'è niente qui</string>\n    <string name=\"services\">Servizi</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"allow_unstable_updates\">Permetti aggiornamenti instabili</string>\n    <string name=\"allow_unstable_updates_summary\">Ricevi notifiche riguardo versioni instabili</string>\n    <string name=\"download_started\">Download iniziato</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"languages\">Lingue</string>\n    <string name=\"zoom_in\">Ingrandire</string>\n    <string name=\"captcha_required_summary\">%s richiede il completamento di un captcha per funzionare correttamente</string>\n    <string name=\"sources_catalog\">Catalogo fonti</string>\n    <string name=\"download_option_all_unread\">Tutti i capitoli non letti</string>\n    <string name=\"frequency_every_day\">Ogni giorno</string>\n    <string name=\"categories\">Categorie</string>\n    <string name=\"progress\">Progresso</string>\n    <string name=\"cancel_all\">Annulla tutti</string>\n    <string name=\"sync_host_description\">Puoi usare un server di sincronizzazione self-hosted oppure uno di default. Non cambiare se non si è sicuri di cosa si stia facendo.</string>\n    <string name=\"error_corrupted_file\">Dati non validi in ritorno o file corrotto</string>\n    <string name=\"pick_custom_directory\">Seleziona cartella personalizzata</string>\n    <string name=\"list_options\">Mostra lista opzioni</string>\n    <string name=\"related_manga_summary\">Mostra una lista di manga correlati. In alcuni casi potrebbe essere imprecisa o mancante</string>\n    <string name=\"remove_completed_downloads_confirm\">La tua cronologia dei download sarà permanentemente cancellata. I file scaricati non saranno cancellati</string>\n    <string name=\"reader_zoom_buttons_summary\">Se mostrare o no i tasti di controllo dello zoom in basso a destra</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"tracker_wifi_only_summary\">Non controllare nuovi capitoli mentre è in uso una connessione a consumo</string>\n    <string name=\"error_multiple_states_not_supported\">Questa fonte di manga non supporta il filtraggio per multipli stati</string>\n    <string name=\"order_added\">Aggiunto</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"on_device\">Su dispositivo</string>\n    <string name=\"password\">Password</string>\n    <string name=\"download_option_whole_manga\">L\\'intero manga</string>\n    <string name=\"settings_apply_restart_required\">Riavvia l\\'applicazione per applicare queste modifiche</string>\n    <string name=\"backup_frequency\">Frequenza creazione backup</string>\n    <string name=\"data_and_privacy\">Dati e privacy</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"clear_source_cookies_summary\">Pulisci cookies solo per dominio specifico. Nella maggior parte dei casi invaliderà l\\'autorizzazione</string>\n    <string name=\"downloads_wifi_only_summary\">Smetti di scaricare quando si passa a rete mobile</string>\n    <string name=\"suggest_new_sources\">Suggerisci nuove fonti dopo aggiornamento dell\\'app</string>\n    <string name=\"user_agent\">Header UserAgent</string>\n    <string name=\"error_filter_states_genre_not_supported\">Questa fonte non supporta il filtraggio per sia genere che stato</string>\n    <string name=\"ignore_ssl_errors\">Ignora errori SSL</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Questa fonte non supporta il filtraggio per sia genere che localizzazione</string>\n    <string name=\"periodic_backups_enable\">Abilita backup periodici</string>\n    <string name=\"server_address\">Indirizzo server</string>\n    <string name=\"content_type_comics\">Fumetti</string>\n    <string name=\"moved_to_top\">Spostato in cima</string>\n    <string name=\"find_similar\">Trova simili</string>\n    <string name=\"data_not_restored_text\">Assicurati di aver selezionato il file di backup corretto</string>\n    <string name=\"catalog\">Catalogo</string>\n    <string name=\"welcome_text\">Selezionare le fonti di contenuti da abilitare. Si può configurare anche successivamente nelle impostazioni</string>\n    <string name=\"unknown\">Sconosciuto</string>\n    <string name=\"in_progress\">In progresso</string>\n    <string name=\"download_option_manual_selection\">Selezione manuale capitoli</string>\n    <string name=\"enhanced_colors_summary\">Riduce il banding, ma può impattare la performance</string>\n    <string name=\"pause\">Pausa</string>\n    <string name=\"remove_completed\">Rimuovi completati</string>\n    <string name=\"items_limit_exceeded\">Impossibile aggiungere altri oggetti</string>\n    <string name=\"frequency_every_2_days\">Ogni 2 giorni</string>\n    <string name=\"suggestions_notifications_summary\">A volte mostra le notifiche sui manga suggeriti</string>\n    <string name=\"invalid_value_message\">Valore non valido</string>\n    <string name=\"downloads_cancelled\">I download sono stati annullati</string>\n    <string name=\"reader_optimize\">Riduci consumo di memoria (beta)</string>\n    <string name=\"apply\">Applica</string>\n    <string name=\"restore\">Ripristina</string>\n    <string name=\"data_not_restored\">Dati non ripristinati</string>\n    <string name=\"manage_sources\">Gestisci fonti</string>\n    <string name=\"directories\">Cartelle</string>\n    <string name=\"local_manga_directories\">Cartelle manga locali</string>\n    <string name=\"manage_categories\">Gestisci categorie</string>\n    <string name=\"no_manga_sources_found\">Nessuna fonte manga trovata in base alla tua query</string>\n    <string name=\"color_light\">Chiaro</string>\n    <string name=\"web_view_unavailable\">WebView non disponibile: controlla se è installato un provider di WebView</string>\n    <string name=\"genres_search_hint\">Inizia a scrivere il nome del genere</string>\n    <string name=\"port\">Porta</string>\n    <string name=\"type\">Tipo</string>\n    <string name=\"search_hint\">Inserisci titolo manga, genere o nome fonte</string>\n    <string name=\"frequency_once_per_week\">Una volta a settimana</string>\n    <string name=\"description\">Descrizione</string>\n    <string name=\"periodic_backups\">Backup periodici</string>\n    <string name=\"reader_zoom_buttons\">Mostra tasti zoom</string>\n    <string name=\"sources_reorder_tip\">Tocca e tieni premuto su un elemento per riordinarlo</string>\n    <string name=\"globally\">Globalmente</string>\n    <string name=\"resume\">Riprendi</string>\n    <string name=\"images_proxy_title\">Proxy ottimizzazione immagini</string>\n    <string name=\"username\">Nome utente</string>\n    <string name=\"frequency_twice_per_month\">Due volte al mese</string>\n    <string name=\"main_screen_sections\">Sezioni schermata principale</string>\n    <string name=\"advanced\">Avanzate</string>\n    <string name=\"downloads_settings_info\">Se stai avendo problemi di blocco dal server, puoi abilitare il rallentamento dei download per ogni fonte di manga nelle impostazioni delle fonti</string>\n    <string name=\"sync_settings\">Impostazioni di sincronizzazione</string>\n    <string name=\"online_variant\">Variante online</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Potrebbe aiutare a far iniziare il download in caso di problemi</string>\n    <string name=\"error_multiple_genres_not_supported\">Questa fonte di manga non supporta il filtraggio per multipli generi</string>\n    <string name=\"download_option_all_unread_b\">Tutti i capitoli non letti (%s)</string>\n    <string name=\"authorization_optional\">Autorizzazione (opzionale)</string>\n    <string name=\"color_dark\">Scuro</string>\n    <string name=\"this_manga\">Questo manga</string>\n    <string name=\"reader_info_bar_summary\">Mostra l\\'orario attuale e il progresso di lettura in cima allo schermo</string>\n    <string name=\"downloads_paused\">I download sono stati messi in pausa</string>\n    <string name=\"too_many_requests_message\">Troppe richieste. Riprova più tardi</string>\n    <string name=\"downloads_wifi_only\">Scarica solo via Wi-Fi</string>\n    <string name=\"lock_screen_rotation\">Blocca rotazione schermo</string>\n    <string name=\"cancel_all_downloads_confirm\">Tutti i download attivi saranno annullati, i dati scaricati parzialmente saranno persi</string>\n    <string name=\"by_relevance\">Rilevanza</string>\n    <string name=\"related_manga\">Manga correlati</string>\n    <string name=\"state_abandoned\">Abbandonato</string>\n    <string name=\"download_option_first_n_chapters\">Primo %s</string>\n    <string name=\"keep_screen_on\">Tieni schermo acceso</string>\n    <string name=\"paused\">In pausa</string>\n    <string name=\"text_downloads_list_holder\">Non hai alcun download</string>\n    <string name=\"skip\">Salta</string>\n    <string name=\"error_search_not_supported\">Questa fonte di manga non supporta la ricerca</string>\n    <string name=\"invalid_port_number\">Numero di porta non valido</string>\n    <string name=\"suggestions_wifi_only_summary\">Non aggiornare suggerimenti mentre è in uso una connessione a consumo</string>\n    <string name=\"webtoon_zoom_summary\">Consenti gesto di zoom in modalità webtoon</string>\n    <string name=\"frequency_once_per_month\">Una volta al mese</string>\n    <string name=\"network\">Rete</string>\n    <string name=\"downloaded\">Scaricato</string>\n    <string name=\"suggestions_enable_prompt\">Vuoi ricevere suggerimenti personalizzati di manga?</string>\n    <string name=\"manual\">Manuale</string>\n    <string name=\"more\">Di più</string>\n    <string name=\"reader_optimize_summary\">Riduci la qualità delle pagine fuori schermo per usare meno memoria</string>\n    <string name=\"address\">Indirizzo</string>\n    <string name=\"color_correction_apply_text\">Queste impostazioni possono essere applicate globalmente o per solo questo manga. Le applicazioni globali non sovrascrivono quelle individuali.</string>\n    <string name=\"source_enabled\">Fonte abilitata</string>\n    <string name=\"enhanced_colors\">Modalità colori 32-bit</string>\n    <string name=\"folder_with_images_import_description\">Puoi selezionare una cartella contenente archivi o immagini. Ogni archivio (o sottocartella) sarà riconosciuto come singolo capitolo.</string>\n    <string name=\"background\">Sfondo</string>\n    <string name=\"disable_nsfw_summary\">Disabilita fonti per adulti e nascondi manga per adulti dalla lista se possibile</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"downloads_removed\">I download sono stati rimossi</string>\n    <string name=\"no_access_to_file\">Non hai accesso a questo file o cartella</string>\n    <string name=\"mirror_switching\">Scegli mirror automaticamente</string>\n    <string name=\"restore_summary\">Ripristina backup creato in precedenza</string>\n    <string name=\"show_pages_numbers_summary\">Mostra numeri di pagina nell\\'angolo inferiore</string>\n    <string name=\"zoom_out\">Rimpicciolire</string>\n    <string name=\"keep_screen_on_summary\">Non spegnere lo schermo durante la lettura di manga</string>\n    <string name=\"download_option_next_unread_n_chapters\">Prossimo %s non letto</string>\n    <string name=\"clear_network_cache\">Pulisci cache di rete</string>\n    <string name=\"voice_search\">Ricerca vocale</string>\n    <string name=\"enable\">Abilita</string>\n    <string name=\"backup_date_\">Data del backup: %s</string>\n    <string name=\"images_procy_description\">Usa il servizio wsrv.nl per ridurre i consumi di rete e velocizzare il caricamento delle immagini, se possibile</string>\n    <string name=\"no_manga_sources_catalog_text\">Non sono disponibili fonti in questa sezione, o potrebbero essere state aggiunte tutte.\n\\nRimani sintonizzato</string>\n    <string name=\"available_d\">Disponibile: %1$d</string>\n    <string name=\"state\">Stato</string>\n    <string name=\"manga_list\">Lista manga</string>\n    <string name=\"grayscale\">Bianco e nero</string>\n    <string name=\"disable_nsfw\">Disabilita contenuti NSFW</string>\n    <string name=\"last_successful_backup\">Ultimo backup completato: %s</string>\n    <string name=\"color_white\">Bianco</string>\n    <string name=\"downloads_resumed\">I download sono stati ripresi</string>\n    <string name=\"state_paused\">In pausa</string>\n    <string name=\"to_top\">Va in cima</string>\n    <string name=\"show\">Mostra</string>\n    <string name=\"sync_auth_hint\">Puoi accedere ad un account esistente o crearne uno nuovo</string>\n    <string name=\"backups_output_directory\">Cartella salvataggio backup</string>\n    <string name=\"invert_colors\">Inverti colori</string>\n    <string name=\"mirror_switching_summary\">Cambiare dominio di fonte del manga automaticamente in caso di errore se sono disponibili mirror</string>\n    <string name=\"no_thanks\">No grazie</string>\n    <string name=\"suggest_new_sources_summary\">Chiedi se aggiungere nuove fonti dopo aggiornamento dell\\'applicazione</string>\n    <string name=\"speed\">Velocità</string>\n    <string name=\"download_option_all_chapters\">Tutti i capitoli con traduzioni %s</string>\n    <string name=\"content_type_other\">Altro</string>\n    <string name=\"suggestion_manga\">Consigliato: %s</string>\n    <string name=\"color_black\">Nero</string>\n    <string name=\"this_month\">Questo mese</string>\n    <string name=\"sync_auth\">Accedi per sincronizzare l\\'account</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"got_it\">Ho capito</string>\n    <string name=\"comics_archive_import_description\">Puoi selezionare uno o più file .cbz o .zip, ogni file sarà riconosciuto come un manga a parte.</string>\n    <string name=\"show_on_shelf\">Mostra sullo Scaffale</string>\n    <string name=\"state_upcoming\">Prossime</string>\n    <string name=\"by_name_reverse\">Nome invertito</string>\n    <string name=\"content_rating\">Classificazione dei contenuti</string>\n    <string name=\"genres_exclude\">Escludi generi</string>\n    <string name=\"rating_safe\">Sicuro</string>\n    <string name=\"rating_suggestive\">Suggestivi</string>\n    <string name=\"rating_adult\">Adulto</string>\n    <string name=\"default_tab\">Scheda predefinita</string>\n    <string name=\"mark_as_completed\">Segna come completo</string>\n    <string name=\"mark_as_completed_prompt\">Segna il manga selezionato come completamente letto?\n\\n\n\\nAttenzione: l\\'attuale avanzamento della lettura sarà perso.</string>\n    <string name=\"volume_unknown\">Volume sconosciuto</string>\n    <string name=\"volume_\">Volume %d</string>\n    <string name=\"category_hidden_done\">Questa categoria è stata nascosta dalla schermata principale ed è accessibile tramite Menù → Gestisci categorie</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"last_used\">Ultimo utilizzo</string>\n    <string name=\"incognito_mode_hint\">I tuoi progressi di lettura non saranno salvati</string>\n    <string name=\"vertical\">Verticale</string>\n    <string name=\"last_read\">Ultima lettura</string>\n    <string name=\"reading_time_estimation_summary\">Il tempo di lettura stimato potrebbe essere inaccurato</string>\n    <string name=\"reading_time_estimation\">Mostra il tempo di lettura stimato</string>\n    <string name=\"email_password_enter_hint\">Inserisci email e password per continuare</string>\n    <string name=\"show_menu\">Mostra menu</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Usa i pulsanti del volume per cambiare pagine</string>\n    <string name=\"tap_action\">Azioni tap</string>\n    <string name=\"long_tap_action\">Azioni tap prolungato</string>\n    <string name=\"none\">Nessuno</string>\n    <string name=\"fullscreen_mode\">Modalità schermo interno</string>\n    <string name=\"chapters_grid_view\">Vista griglia</string>\n    <string name=\"prev_chapter\">Capitolo precedente</string>\n    <string name=\"reader_actions_summary\">Configura le azioni per le aree di schermo cliccabili</string>\n    <string name=\"suggestions_unavailable_text\">La funzione di suggerimento è disabilitata</string>\n    <string name=\"show_labels_in_navbar\">Mostra le etichette nella barra di navigazione</string>\n    <string name=\"delete_read_chapters_summary\">Rimuovi i capitoli già letti dalla memoria locale per liberare spazio</string>\n    <string name=\"delete_read_chapters_prompt\">Questo eliminerà in modo permanente tutti i capitoli segnati come già letti dalla memoria locale. Puoi scaricarli di nuovo, ma i capitoli importati potrebbero essere persi per sempre</string>\n    <string name=\"order_oldest\">Meno recenti</string>\n    <string name=\"long_ago_read\">Letto molto tempo fa</string>\n    <string name=\"unsupported_source\">Questa fonte manga non è supportata</string>\n    <string name=\"hours_short\">%d ore</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"show_updated\">Mostra aggiornato</string>\n    <string name=\"toggle_ui\">Mostra/nascondi UI</string>\n    <string name=\"next_chapter\">Prossimo capitolo</string>\n    <string name=\"prev_page\">Pagina precedente</string>\n    <string name=\"next_page\">Pagina successiva</string>\n    <string name=\"reader_actions\">Azioni del lettore</string>\n    <string name=\"switch_pages_volume_buttons\">Abilita pulsanti del volume</string>\n    <string name=\"reader_fullscreen_summary\">Nascondi le barre di stato e di notifica</string>\n    <string name=\"check_for_new_chapters_disabled\">Il controllo di nuovi capitoli è disabilitato</string>\n    <string name=\"delete_read_chapters_auto\">Elimina i capitoli già letti automaticamente</string>\n    <string name=\"runs_on_app_start\">Esegui quando l\\'applicazione viene avviata</string>\n    <string name=\"split_by_translations\">Dividi per traduzioni</string>\n    <string name=\"split_by_translations_summary\">Mostra separatamente i capitoli con diverse traduzioni, invece che in un\\'unica lista</string>\n    <string name=\"unread\">Non letto</string>\n    <string name=\"show_pages_thumbs\">Mostra le anteprime delle pagine</string>\n    <string name=\"show_pages_thumbs_summary\">Abilita la tab \\\"Pagine\\\" nella schermata di dettaglio</string>\n    <string name=\"unsupported_backup_message\">Si prega di selezione un file backup di Kotatsu corretto</string>\n    <string name=\"hours_minutes_short\">%1$d h %2$d m</string>\n    <string name=\"fix\">Aggiustamenti</string>\n    <string name=\"missing_storage_permission\">Non sono presenti i permessi per accedere al manga nella memoria esterna</string>\n    <string name=\"webtoon_gaps\">Gap in modalità webtoon</string>\n    <string name=\"webtoon_gaps_summary\">Mostra gap verticali tra le pagine in modalità webotoon</string>\n    <string name=\"config_reset_confirm\">Ripristinare le impostazioni ai valori predefiniti? Questo procedimento è irreversibile.</string>\n    <string name=\"use_two_pages_landscape\">Usa il layout a due pagine con l\\'orientamento orizzontale (beta)</string>\n    <string name=\"error_no_data_received\">Non è stato ricevuto nessun dato dal server</string>\n    <string name=\"enable_source\">Attiva fonte</string>\n    <string name=\"less_frequently\">Meno frequente</string>\n    <string name=\"more_frequently\">Più frequente</string>\n    <string name=\"frequency_of_check\">Frequenza di controllo</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"_new\">Nuovi</string>\n    <string name=\"automatic\">Automatico</string>\n    <string name=\"all_languages\">Tutte le lingue</string>\n    <string name=\"delete_read_chapters\">Cancella capitoli letti</string>\n    <string name=\"manga_migration\">Migrazione manga</string>\n    <string name=\"migration_completed\">Migrazione completata</string>\n    <string name=\"no_chapters_deleted\">Nessun capitolo è stato cancellato</string>\n    <string name=\"day\">Giorno</string>\n    <string name=\"three_months\">Tre mesi</string>\n    <string name=\"empty_stats_text\">Non ci sono statistiche per il periodo selezionato</string>\n    <string name=\"ask_for_dest_dir_every_time\">Chiedi per la cartella di destinazione ogni volta</string>\n    <string name=\"pages_saving\">Salvando le pagine</string>\n    <string name=\"preferred_download_format\">Formato download preferito</string>\n    <string name=\"single_cbz_file\">File CBZ singolo</string>\n    <string name=\"multiple_cbz_files\">Molti file CBZ</string>\n    <string name=\"reading_stats\">Lettura statistiche</string>\n    <string name=\"remove_from_history\">Rimuovi dalla cronologia</string>\n    <string name=\"statistics\">Statistiche</string>\n    <string name=\"migrate\">Migrare</string>\n    <string name=\"alternatives\">Alternative</string>\n    <string name=\"image_server\">Server delle immagini preferito</string>\n    <string name=\"pin\">Fissa</string>\n    <string name=\"sources_pinned\">Fonti fissate</string>\n    <string name=\"source_pinned\">Fonte fissta</string>\n    <string name=\"recent_sources\">Fonti recenti</string>\n    <string name=\"clear_stats\">Cancella statistiche</string>\n    <string name=\"stats_cleared\">Statistiche cancellate</string>\n    <string name=\"clear_stats_confirm\">Vuoi veramente cancellare le statistiche di lettura? Questa azione non può essere annullata.</string>\n    <string name=\"week\">Settimana</string>\n    <string name=\"month\">Mese</string>\n    <string name=\"pages_read_s\">Pagine lette: %s</string>\n    <string name=\"disable_connectivity_check\">Disbilita controllo della connettività</string>\n    <string name=\"other_manga\">Altri Manga</string>\n    <string name=\"all_time\">Sempre</string>\n    <string name=\"disable_nsfw_notifications\">Disabilita notifiche NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">Disabilita le notifiche su fonti per adulti ed aggiornamenti di manga per adulti</string>\n    <string name=\"authors\">Autori</string>\n    <string name=\"default_page_save_dir\">Cartella di salvataggo predefinita di una pagina</string>\n    <string name=\"less_than_minute\">Meno di un minuto</string>\n    <string name=\"migrate_confirmation\">Il manga \\\"%1$s\\\" da \\\"%2$s\\\" sarà sostituito con \\\"%3$s\\\" da \\\"%4$s\\\" sia nella cronologia che nei preferiti (se presente)</string>\n    <string name=\"recent_queries\">Query recenti</string>\n    <string name=\"blocked_by_server_message\">Sei stato bloccato dal server. Prova ad usare una connessione differente (VPN, Proxy, ecc.)</string>\n    <string name=\"ignore_ssl_errors_summary\">Puoi disabilitare la verifica dei certificati SSL in caso incontri degli errori legati a SSL quando si tenta di accedere a una risorsa di rete. Questo può compromettera la tua sicurezza. È richiesto riavviare l\\'applicazione in seguto.</string>\n    <string name=\"disable_connectivity_check_summary\">Salta la verifica di connessione in caso di problemi con essa (es. andare in modalità offline pur essendo conessi ad una rete)</string>\n    <string name=\"screenshots_block_incognito\">Blocca in modalità incognito</string>\n    <string name=\"suggested_queries\">Query suggerite</string>\n    <string name=\"disable\">Disabilita</string>\n    <string name=\"sources_disabled\">Fonti disabilitate</string>\n    <string name=\"pin_navigation_ui\">Blocca UI di navigazione</string>\n    <string name=\"pin_navigation_ui_summary\">Non nascone la barra di navigazione e di ricerca durante lo scorrimento</string>\n    <string name=\"percent_read\">Percentuale di lettura</string>\n    <string name=\"percent_left\">Percentuale rimasta</string>\n    <string name=\"chapters_read\">Capitoli letti</string>\n    <string name=\"chapters_left\">Capitoli rimasti</string>\n    <string name=\"default_webtoon_zoom_out\">Riduzione dello zoom Webtoon predefinito</string>\n    <string name=\"crop_pages\">Ritaglia le pagine</string>\n    <string name=\"unpin\">Rimuovi</string>\n    <string name=\"source_unpinned\">Fonte rimossa</string>\n    <string name=\"sources_unpinned\">Fonti rimosse</string>\n    <string name=\"tracker_debug_info_summary\">Informazioni di debug sui controlli in background per i nuovi capitoli</string>\n    <string name=\"location\">Posizione</string>\n    <string name=\"chapters_deleted_pattern\">Rimosso %1$s, cancellato %2$s</string>\n    <string name=\"search_suggestions\">Suggerimenti per la ricerca</string>\n    <string name=\"tracker_debug_info\">Controllo del registro di nuovi capitoli</string>\n    <string name=\"scrobbler_auth_required\">Accedi a %s per continuare</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Non ci sono manga che corrispondono ai filtri selezionati</string>\n    <string name=\"unstable_feature\">Funzionalità instabile</string>\n    <string name=\"scrobbler_auth_intro\">Accedi per configurare l\\'integrazione con %s. Ciò ti consentirà di monitorare i progressi e lo stato della lettura dei manga</string>\n    <string name=\"invalid_proxy_configuration\">Configurazione proxy non valida</string>\n    <string name=\"show_quick_filters\">Mostra filtri rapidi</string>\n    <string name=\"show_quick_filters_summary\">Fornisce la possibilità di filtrare gli elenchi di manga in base a determinati parametri</string>\n    <string name=\"external_source\">Esterno/plugin</string>\n    <string name=\"connection_ok\">La connessione è OK</string>\n    <string name=\"invalid_server_address_message\">Indirizzo server non valido</string>\n    <string name=\"too_many_requests_message_retry\">Troppe richieste. Riprova dopo %s</string>\n    <string name=\"skip_all\">Salta tutto</string>\n    <string name=\"stuck\">Bloccato</string>\n    <string name=\"not_in_favorites\">Non presente nei preferiti</string>\n    <string name=\"plugin_incompatible\">Plugin incompatibile o errore interno. Assicurati di utilizzare l\\'ultima versione del plugin e di Kotatsu</string>\n    <string name=\"updated_long_ago\">Aggiornato molto tempo fa</string>\n    <string name=\"unpopular\">Impopolare</string>\n    <string name=\"low_rating\">Valutazione bassa</string>\n    <string name=\"sort_order_asc\">Ascendente</string>\n    <string name=\"sort_order_desc\">Discendente</string>\n    <string name=\"by_date\">Data</string>\n    <string name=\"popularity\">Popolarità</string>\n    <string name=\"retry\">Riprovare</string>\n    <string name=\"unstable_feature_summary\">Funziona sperimentale. Per evitare perdite di dati, si consiglia di effettuare un backup</string>\n    <string name=\"original_language\">Lingua originale</string>\n    <string name=\"year\">Anno</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"added_long_ago\">Aggiunto da molto tempo</string>\n    <string name=\"years\">Anni</string>\n    <string name=\"any\">Qualsiasi</string>\n    <string name=\"filter_search_warning\">Questa sorgente non supporta la ricerca con filtri. I tuoi filtri sono stati azzerati</string>\n    <string name=\"ask_every_time\">Chiedi ogni volta</string>\n    <string name=\"pages_saved\">Pagine salvate correttamente</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"content_type_novel\">Romanzo</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"recently_added\">Aggiunto recentemente</string>\n    <string name=\"popular_in_hour\">Popolare in questo momento</string>\n    <string name=\"content_type_image_set\">Collezione immagini</string>\n    <string name=\"destination_directory\">Cartella di destinazione</string>\n    <string name=\"plugin_incompatible_with_cause\">Errore del plugin: %s\\n• Verifica di utilizzare l\\'ultima versione del plugin e di Kotatsu</string>\n    <string name=\"popular_in_week\">Popolare questa settimana</string>\n    <string name=\"popular_in_year\">Popolare quest\\'anno</string>\n    <string name=\"demographics\">Demografici</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) rimpiazzato con \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"popular_in_month\">Popolare questo mese</string>\n    <string name=\"popular_today\">Popolare oggi</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"source_code\">Codice sorgente</string>\n    <string name=\"start_download\">Inizia download</string>\n    <string name=\"user_manual\">Manuale utente</string>\n    <string name=\"telegram_group\">Gruppo Telegram</string>\n    <string name=\"screen_orientation\">Orientamento schermo</string>\n    <string name=\"chapters_all\">Tutti</string>\n    <string name=\"save_manga_confirm\">Salvare il manga selezionato? Potrebbe utilizzare traffico dati e spazio nella memoria interna</string>\n    <string name=\"error_not_image\">Formato non valido: prevista un\\'immagine ma ricevuto %s</string>\n    <string name=\"download_added\">Download aggiunto</string>\n    <string name=\"more_options\">Più opzioni</string>\n    <string name=\"save_manga\">Salva manga</string>\n    <string name=\"genre\">Genere</string>\n    <string name=\"dont_allow\">Non consentire</string>\n    <string name=\"download_over_cellular\">Scarica tramite dati mobili</string>\n    <string name=\"download_cellular_confirm\">Consentire i download tramite dati mobili?</string>\n    <string name=\"allow_always\">Consenti sempre</string>\n    <string name=\"allow_once\">Contenti una sola volta</string>\n    <string name=\"access_denied_403\">Accesso non consentito (403)</string>\n    <string name=\"max_backups_count\">Numero massimo di backup</string>\n    <string name=\"portrait\">Verticale</string>\n    <string name=\"landscape\">Orizzontale</string>\n    <string name=\"error_image_format\">Formato immagine non supportato: %s</string>\n    <string name=\"sfw\">Contenuto non esplicito</string>\n    <string name=\"downloads_background\">Download in background</string>\n    <string name=\"download_new_chapters\">Scarica i nuovi capitoli</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga con capitoli scaricati</string>\n    <string name=\"fixing_manga\">Correggendo il manga</string>\n    <string name=\"manga_fix_prompt\">Questa funzione troverà sorgenti alternative per il manga selezionato. L\\'azione richiederà un po\\' di tempo e verrà effettuata in background</string>\n    <string name=\"fixed\">Corretto con successo</string>\n    <string name=\"no_fix_required\">Nessuna correzione per \\\"%s\\\"</string>\n    <string name=\"no_alternatives_found\">Nessuna alternativa trovata per \\\"%s\\\"</string>\n    <string name=\"chapter_selection_hint\">Puoi selezionare i capitoli da scaricare tramite un click prolungato nell\\'elemento nella lista dei capitoli.</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_one_shot\">One shot</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"content_type_artist_cg\">CG Artist</string>\n    <string name=\"content_type_game_cg\">CG Game</string>\n    <string name=\"debug\">Debug</string>\n    <string name=\"delete_old_backups\">Elimina i vecchi backup</string>\n    <string name=\"delete_old_backups_summary\">Elimina automaticamente i vecchi backup per liberare spazio nella memoria</string>\n    <string name=\"error_connection_reset\">Ripristino della connessione da parte dell\\'host remoto</string>\n    <string name=\"show_slider\">Mostra cursore</string>\n    <string name=\"incognito\">Incognito</string>\n    <string name=\"backup_tg_echo\">Messaggio di test</string>\n    <string name=\"backup_tg_id_not_set\">L\\'ID chat non è impostato</string>\n    <string name=\"telegram_chat_id\">ID chat di Telegram</string>\n    <string name=\"open_telegram_bot\">Apri il bot di Telegram</string>\n    <string name=\"handle_links\">Gestione link</string>\n    <string name=\"handle_links_summary\">Gestisci i link ai manga da applicazioni esterne (ad esempio, dal browser web). Potrebbe essere necessario abilitarlo manualmente nelle impostazioni di sistema dell\\'applicazione</string>\n    <string name=\"email\">Email</string>\n    <string name=\"clear_database\">Pulisci database</string>\n    <string name=\"clear_database_summary\">Elimina le informazioni sui manga che non vengono utilizzate</string>\n    <string name=\"send_backups_telegram\">Invia i backup a Telegram</string>\n    <string name=\"telegram_chat_id_summary\">Inserisci l\\'ID della chat dove inviare i backup</string>\n    <string name=\"open_telegram_bot_summary\">Premi per aprire la chat con Kotatsu Backup Bot</string>\n    <string name=\"translation\">Traduzione</string>\n    <string name=\"test_connection\">Connessione test</string>\n    <string name=\"backup_tg_check\">Verifica se l\\'API è funzionante</string>\n    <string name=\"author\">Autore</string>\n    <string name=\"source\">Sorgente</string>\n    <string name=\"captcha_required_message\">Questa sorgente richiede di risolvere un captcha per continuare</string>\n    <string name=\"rating\">Valutazione</string>\n    <string name=\"all_sources_enabled\">Tutte le fonti sono abilitate</string>\n    <string name=\"enable_all_sources\">Abilità tutte le fonti manga</string>\n    <string name=\"enable_all_sources_summary\">Tutte le fonti manga disponibili saranno abilitate permanentemente</string>\n    <string name=\"reader_info_bar_transparent\">Barra informazione del lettore trasparente</string>\n    <string name=\"restoring_backup\">Ripristinando il backup</string>\n    <string name=\"backup_restored_background\">Il backup verrà ripristinato in background</string>\n    <string name=\"reader_controls_in_bottom_bar\">Controlli del lettore nella barra in basso</string>\n    <string name=\"screen_rotation_locked\">Rotazione schermo bloccata</string>\n    <string name=\"screen_rotation_unlocked\">Rotazione schermo sbloccata</string>\n    <string name=\"chapters_and_pages\">Capitoli e pagine</string>\n    <string name=\"pages_slider\">Slider di cambio pagina</string>\n    <string name=\"simple\">Semplice</string>\n    <string name=\"search_everywhere\">Cerca ovunque</string>\n    <string name=\"badges_in_lists\">Badge in elenchi</string>\n    <string name=\"global_search\">Ricerca globale</string>\n    <string name=\"disable_captcha_notifications\">Disabilita notifiche captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Non riceverai notifiche sulla risoluzione dei CAPTCHA per questa fonte, ma ciò potrebbe interrompere le operazioni in background (controllo di nuovi capitoli, ottenimento di raccomandazioni, ecc.)</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Capitolo %2$s</string>\n    <string name=\"search_disabled_sources\">Ricerca tra le fonti disabilitate</string>\n    <string name=\"chapter_number\">Capitolo %s</string>\n    <string name=\"unnamed_chapter\">Capitolo senza nome</string>\n    <string name=\"error_details\">Dettagli errore</string>\n    <string name=\"error_disclaimer_manga\">Prova ad aprire il manga nel browser web per verificare che sia disponibile nella sua fonte.</string>\n    <string name=\"error_disclaimer_app_outdated\">Sembra che la tua versione di Kotatsu non sia aggiornata. Installa l\\'ultima versione per ottenere tutte le correzioni disponibili.</string>\n    <string name=\"error_disclaimer_report\">Puoi inviare una segnalazione di bug agli sviluppatori. Questo ci aiuterà a indagare e a risolvere il problema.</string>\n    <string name=\"link_to_manga_on_s\">Link al manga su %s</string>\n    <string name=\"link_to_manga_in_app\">Link al manga su Kotatsu</string>\n    <string name=\"clear_browser_data_summary\">Pulisci i dati del browser, come la cache e i cookie. Attenzione: l\\'autorizzazione nelle fonti manga potrebbe diventare non valida</string>\n    <string name=\"clear_browser_data\">Pulisci dati del browser</string>\n    <string name=\"no_write_permission_to_file\">Non ha l\\'autorizzazione a scrivere un file</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"include_disabled_sources\">Includi fonti disabilitate</string>\n    <string name=\"suggestions_disabled_sources_summary\">Mostra suggerimenti da tutte le fonti manga, incluse quelle disabilite</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">I manga per adulti non verranno mostrati nei suggerimenti. Questa opzione potrebbe non funzionare accuratamente con alcune fonti</string>\n    <string name=\"tags_warnings\">Evidenzia i generi pericolosi</string>\n    <string name=\"tags_warnings_summary\">Evidenzia i generi che potrebbero essere inappropriati per la maggior parte degli utenti</string>\n    <string name=\"error_non_file_uri\">Il percorso selezionato non può essere utilizzato perché non rappresenta un file o una cartella</string>\n    <string name=\"use_default_cover\">Usa copertina predefinita</string>\n    <string name=\"manga_override_hint\">Queste modifiche influenzeranno il modo in cui i manga vengono visualizzati nell\\'app</string>\n    <string name=\"pick_manga_page\">Scegli pagina manga</string>\n    <string name=\"pick_custom_file\">Scegli file personalizzato</string>\n    <string name=\"change_cover\">Cambia copertina</string>\n    <string name=\"page_switch_timer\">La pagina cambierà ogni ~%d secondi</string>\n    <string name=\"incognito_for_nsfw\">Modalità incognito per manga NSFW</string>\n    <string name=\"dont_ask_again\">Non chiedere più</string>\n    <string name=\"incognito_mode_hint_nsfw\">Questo manga potrebbe contenere contenuti per adulti. Vuoi utilizzare la modalità incognito?</string>\n    <string name=\"additional_action_required\">È necessaria un\\'azione aggiuntiva</string>\n    <string name=\"theme_name_expressive\">Espressivo (Test)</string>\n    <string name=\"hide_from_main_screen\">Nascondi dalla schermata principale</string>\n    <string name=\"changelog\">Changelog</string>\n    <string name=\"changelog_summary\">Cronologia delle modifiche delle versioni rilasciate di recente</string>\n    <string name=\"collapse\">Comprimi</string>\n    <string name=\"adblock_summary\">Blocca pubblicità nel browser incorporato (beta)</string>\n    <string name=\"adblock\">Blocca pubblicità nel browser</string>\n    <string name=\"expand\">Espandi</string>\n    <string name=\"collapse_long_description\">Comprimi descrizione lunga</string>\n    <string name=\"share_backup\">Condividi backup</string>\n    <string name=\"creating_backup\">Creazione del backup</string>\n    <string name=\"reader_navigation_inverted\">Inverti i controlli di navigazione</string>\n    <string name=\"reader_navigation_inverted_summary\">Inverti la direzione dei tasti del volume e dei tasti hardware direzionali (sinistra/su/giù/destra)</string>\n    <string name=\"reader_multitask\">Apri il lettore in un\\'attività separata</string>\n    <string name=\"reader_multitask_summary\">Ti consente di tenere aperti più lettori con manga diversi contemporaneamente</string>\n    <string name=\"book_effect\">Sfondo giallastro (filtro blu)</string>\n    <string name=\"local_storage_cleanup\">Pulizia dell\\'archiviazione locale</string>\n    <string name=\"packup_creation_failed\">Creazione del backup non riuscita</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"main_screen\">Schermata principale</string>\n    <string name=\"main_screen_fab\">Mostra il pulsante fluttuante Continua</string>\n    <string name=\"main_screen_fab_summary\">Permette di continuare la lettura con un solo tocco. Questo pulsante non apparirà in modalità in incognito o quando la cronologia è vuota</string>\n    <string name=\"error_corrupted_zip\">Archivio ZIP danneggato (%s)</string>\n    <string name=\"discord_rpc\">Discord Rich Presence</string>\n    <string name=\"discord_token\">Token Discord</string>\n    <string name=\"discord_rpc_summary\">Mostra il tuo stato di lettura su Discord</string>\n    <string name=\"obtain\">Ottieni</string>\n    <string name=\"discord_rpc_description\">Leggendo manga su Kotatsu - un’app per leggere manga</string>\n    <string name=\"reading_s\">Leggendo %s</string>\n    <string name=\"read_on_s\">Leggendo su %s</string>\n    <string name=\"invalid_token\">Token non valido: %s</string>\n    <string name=\"show_floating_control_button\">Mostra pulsante di controllo fluttuante</string>\n    <string name=\"discord_token_summary\">Inserisci il tuo token Discord per abilitare la Rich Presence</string>\n    <string name=\"discord_token_description\">Inserisci il tuo token Discord o clicca %s per ottenerlo utilizzando il browser</string>\n    <string name=\"discord_token_hint\">Incolla il tuo token Discord qui</string>\n    <string name=\"rpc_skip_nsfw_summary\">Non utilizzare RPC per contenuti per adulti</string>\n    <string name=\"unavailable\">Non disponibile</string>\n    <string name=\"manga_restricted_description\">Questo manga non è disponibile per la lettura da questa fonte. Prova a cercarlo in altre fonti o aprilo in un browser per maggiori informazioni</string>\n    <string name=\"no_chapters_in_manga\">Questo manga non contiene capitoli</string>\n    <string name=\"telegram_integration\">Integrazione con Telegram</string>\n    <string name=\"chapters_load_failed\">Errore nel caricamento della lista dei capitoli</string>\n    <string name=\"test_parser\">Testa fonte manga</string>\n    <string name=\"pull_to_prev_chapter\">Rilascia per aprire il capitolo precedente</string>\n    <string name=\"pull_to_next_chapter\">Rilascia per aprire il capitolo successivo</string>\n    <string name=\"pull_top_no_prev\">Nessun capitolo precedente</string>\n    <string name=\"pull_bottom_no_next\">Nessun capitolo successivo</string>\n    <string name=\"enable_pull_gesture_title\">Abilita gesto di scorrimento</string>\n    <string name=\"enable_pull_gesture_summary\">Abilita gesto di scorrimento per cambiare capitolo in webtoon</string>\n    <string name=\"two_page_scroll_sensitivity\">Sensibilità di scorrimento a due pagine</string>\n    <string name=\"saved_filters\">Filtri salvati</string>\n    <string name=\"enter_name\">Inserisci nome</string>\n    <string name=\"reader_chapter_toast\">Mostra popup di cambio capitolo</string>\n    <string name=\"reader_chapter_toast_summary\">Mostra un messaggio pop-up con un titolo del capitolo quando è cambiato</string>\n    <string name=\"rename\">Rinomina</string>\n    <string name=\"save_filter\">Salva filtro</string>\n    <string name=\"overwrite\">Sovrascrivi</string>\n    <string name=\"filter_overwrite_confirm\">Un filtro di nome \\\"%s\\\" esiste già. Vuoi sovrascriverlo?</string>\n    <string name=\"storage_and_network\">Memoria e rete</string>\n    <string name=\"create_or_restore_backup\">Crea o ripristina un backup</string>\n    <string name=\"data_removal\">Rimozione dei dati</string>\n    <string name=\"privacy\">Privacy</string>\n    <string name=\"source_broken_warning\">Questa fonte manga è stata contrassegnata come non funzionante. Alcune funzioni potrebbero non essere disponibili</string>\n    <string name=\"frequency_every_6_hours\">Ogni 6 ore</string>\n    <string name=\"download_default_directory\">Cartella predefinita per scaricare manga</string>\n    <string name=\"private_app_directory_warning\">Questa cartella e tutti i suoi dati verranno eliminati se disinstalli l\\'applicazione</string>\n    <string name=\"available_pattern\">%1$s disponibile</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-iw/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">פריט אחד</item>\n        <item quantity=\"two\">שתי פריטים</item>\n        <item quantity=\"other\">%1$d פריטים</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">פרק אחד חדש</item>\n        <item quantity=\"two\">שתי פרקים חדשים</item>\n        <item quantity=\"other\">%1$d פרקים חדשים</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">לפני חודש</item>\n        <item quantity=\"two\">לפני חודשיים</item>\n        <item quantity=\"other\">לפני %1$d חודשים</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">פרק אחד</item>\n        <item quantity=\"two\">שתי פרקים</item>\n        <item quantity=\"other\">%1$d פרקים</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">לפני דקה</item>\n        <item quantity=\"two\">לפני שתי דקות</item>\n        <item quantity=\"other\">לפני %1$d דקות</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">לפני שעה</item>\n        <item quantity=\"two\">לפני שעתיים</item>\n        <item quantity=\"other\">לפני %1$d שעות</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">לפני יום</item>\n        <item quantity=\"two\">לפני יומיים</item>\n        <item quantity=\"other\">לפני %1$d ימים</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">שעה</item>\n        <item quantity=\"two\">שעתיים</item>\n        <item quantity=\"other\">%1$d שעות</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">דקה</item>\n        <item quantity=\"two\">שתי דקות</item>\n        <item quantity=\"other\">%1$d דקות</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ja/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d 分前</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d 項目</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d 時間前</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d 日前</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d 章</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d 件の新しい章</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d ヶ月前</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d時間</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d分</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ja/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"history\">履歴</string>\n    <string name=\"loading_\">ロード中…</string>\n    <string name=\"chapter_d_of_d\">%1$d/%2$d 章</string>\n    <string name=\"share\">共有</string>\n    <string name=\"clear_history\">履歴を削除</string>\n    <string name=\"search\">検索</string>\n    <string name=\"search_manga\">マンガを検索</string>\n    <string name=\"close\">閉じる</string>\n    <string name=\"favourites\">お気に入り</string>\n    <string name=\"error_occurred\">エラーが発生しました</string>\n    <string name=\"details\">詳細</string>\n    <string name=\"chapters\">章</string>\n    <string name=\"list\">リスト</string>\n    <string name=\"detailed_list\">詳細リスト</string>\n    <string name=\"grid\">グリッド</string>\n    <string name=\"list_mode\">リストモード</string>\n    <string name=\"remote_sources\">マンガのソース</string>\n    <string name=\"try_again\">再試行</string>\n    <string name=\"nothing_found\">何も見つかりませんでした</string>\n    <string name=\"history_is_empty\">まだ履歴はありません</string>\n    <string name=\"read\">読む</string>\n    <string name=\"you_have_not_favourites_yet\">お気に入りの本はありません</string>\n    <string name=\"add_to_favourites\">お気に入りに追加</string>\n    <string name=\"add_new_category\">新しいカテゴリー</string>\n    <string name=\"add\">追加</string>\n    <string name=\"save\">保存</string>\n    <string name=\"create_shortcut\">ショートカットを作成</string>\n    <string name=\"share_s\">%s を共有する</string>\n    <string name=\"manga_downloading_\">ダウンロード中…</string>\n    <string name=\"processing_\">処理中…</string>\n    <string name=\"download_complete\">ダウンロード完了</string>\n    <string name=\"downloads\">ダウンロード</string>\n    <string name=\"by_name\">名前</string>\n    <string name=\"popular\">人気</string>\n    <string name=\"local_storage\">ローカルストレージ</string>\n    <string name=\"newest\">最新</string>\n    <string name=\"by_rating\">評価</string>\n    <string name=\"sort_order\">並べ替え</string>\n    <string name=\"follow_system\">システムに従う</string>\n    <string name=\"clear\">消去</string>\n    <string name=\"remove\">削除</string>\n    <string name=\"_s_deleted_from_local_storage\">「%s」がローカルストレージから削除されました</string>\n    <string name=\"save_page\">ページを保存</string>\n    <string name=\"page_saved\">保存しました</string>\n    <string name=\"share_image\">画像を共有</string>\n    <string name=\"_import\">インポート</string>\n    <string name=\"delete\">削除</string>\n    <string name=\"operation_not_supported\">この操作はサポートされていません</string>\n    <string name=\"no_description\">説明がありません</string>\n    <string name=\"clear_pages_cache\">ページのキャッシュをクリアする</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"light\">ライト</string>\n    <string name=\"filter\">絞り込み</string>\n    <string name=\"dark\">ダーク</string>\n    <string name=\"pages\">ページ</string>\n    <string name=\"theme\">テーマ</string>\n    <string name=\"network_error\">ネットワークエラー</string>\n    <string name=\"updated\">更新</string>\n    <string name=\"text_file_not_supported\">ZIP ファイルまたは CBZ ファイルを選択してください。</string>\n    <string name=\"standard\">標準</string>\n    <string name=\"webtoon\">ウェブトゥーン</string>\n    <string name=\"read_mode\">読み取りモード</string>\n    <string name=\"grid_size\">グリッドのサイズ</string>\n    <string name=\"search_on_s\">%s で検索</string>\n    <string name=\"delete_manga\">マンガを削除</string>\n    <string name=\"text_delete_local_manga\">デバイスから「%s」を完全に削除しますか？</string>\n    <string name=\"reader_settings\">リーダーの設定</string>\n    <string name=\"switch_pages\">ページを変更</string>\n    <string name=\"_continue\">続行</string>\n    <string name=\"error\">エラー</string>\n    <string name=\"clear_thumbs_cache\">サムネイルキャッシュをクリア</string>\n    <string name=\"search_history_cleared\">クリア</string>\n    <string name=\"internal_storage\">内部ストレージ</string>\n    <string name=\"domain\">ドメイン</string>\n    <string name=\"open_in_browser\">ブラウザーで開く</string>\n    <string name=\"notifications\">通知</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d/%2$d</string>\n    <string name=\"new_chapters\">新しい章</string>\n    <string name=\"download\">ダウンロード</string>\n    <string name=\"notifications_settings\">通知の設定</string>\n    <string name=\"notification_sound\">通知音</string>\n    <string name=\"light_indicator\">LED インジケータ</string>\n    <string name=\"vibration\">バイブレーション</string>\n    <string name=\"favourites_categories\">お気に入りのカテゴリー</string>\n    <string name=\"remove_category\">削除</string>\n    <string name=\"text_search_holder_secondary\">クエリを再定式化してみてください。</string>\n    <string name=\"text_history_holder_primary\">読んだ内容がここに表示されます</string>\n    <string name=\"text_history_holder_secondary\">«探索» セクションで読みたいものを見つけてください</string>\n    <string name=\"text_local_holder_primary\">最初に何かを保存してください</string>\n    <string name=\"manga_shelf\">本棚</string>\n    <string name=\"recent_manga\">最近</string>\n    <string name=\"pages_animation\">ページアニメーション</string>\n    <string name=\"manga_save_location\">ダウンロードフォルダ</string>\n    <string name=\"not_available\">利用不可</string>\n    <string name=\"cannot_find_available_storage\">使用可能なストレージがありません</string>\n    <string name=\"other_storage\">その他のストレージ</string>\n    <string name=\"done\">完了</string>\n    <string name=\"all_favourites\">全てのお気に入り</string>\n    <string name=\"read_later\">後で読む</string>\n    <string name=\"updates\">更新</string>\n    <string name=\"text_feed_holder\">あなたが読んでいるものの新しい章がここに示されています</string>\n    <string name=\"search_results\">の検索結果</string>\n    <string name=\"size_s\">サイズ:%s</string>\n    <string name=\"clear_updates_feed\">更新フィードをクリア</string>\n    <string name=\"updates_feed_cleared\">クリア</string>\n    <string name=\"update\">アップデート</string>\n    <string name=\"feed_will_update_soon\">フィードの更新はまもなく開始されます</string>\n    <string name=\"track_sources\">アップデートを確認</string>\n    <string name=\"dont_check\">チェックしない</string>\n    <string name=\"protect_application_summary\">Kotatsuを起動したときにパスワードを入力する</string>\n    <string name=\"repeat_password\">パスワードを繰り返す</string>\n    <string name=\"passwords_mismatch\">パスワードが違います</string>\n    <string name=\"about\">この本の詳細</string>\n    <string name=\"app_version\">現在のバージョン%s</string>\n    <string name=\"clear_search_history\">検索履歴をクリア</string>\n    <string name=\"external_storage\">外部ストレージ</string>\n    <string name=\"app_update_available\">新しいバージョンの Kotatsu が利用可能です</string>\n    <string name=\"text_empty_holder_primary\">ここは空っぽです…</string>\n    <string name=\"favourites_category_empty\">空のカテゴリー</string>\n    <string name=\"text_local_holder_secondary\">オンラインソースから保存するかファイルをインポートします。</string>\n    <string name=\"new_version_s\">新しいバージョン:%s</string>\n    <string name=\"rotate_screen\">画面を回転させる</string>\n    <string name=\"enter_password\">パスワードを入力してください</string>\n    <string name=\"wrong_password\">パスワードが間違っています</string>\n    <string name=\"protect_application\">アプリを保護する</string>\n    <string name=\"check_for_updates\">最新のアップデートを確認する</string>\n    <string name=\"no_update_available\">利用可能なアップデートはありません</string>\n    <string name=\"right_to_left\">右から左</string>\n    <string name=\"auth_complete\">承認済み</string>\n    <string name=\"auth_not_supported_by\">%sへのログインはサポートされていません</string>\n    <string name=\"state_finished\">完成</string>\n    <string name=\"state_ongoing\">進行中</string>\n    <string name=\"system_default\">デフォルト</string>\n    <string name=\"show_pages_numbers\">ナンバリングページ</string>\n    <string name=\"create_category\">新しいカテゴリー</string>\n    <string name=\"zoom_mode_fit_height\">高さを合わせる</string>\n    <string name=\"chapter_is_missing\">チャプターがありません</string>\n    <string name=\"text_clear_updates_feed_prompt\">すべての更新履歴を完全に消去しますか？</string>\n    <string name=\"auth_required\">このコンテンツを表示するにはサインインしてください</string>\n    <string name=\"file_not_found\">ファイルが見つかりません</string>\n    <string name=\"password_length_hint\">パスワードは4文字以上である必要があります</string>\n    <string name=\"just_now\">ちょうど今</string>\n    <string name=\"long_ago\">ずっと前</string>\n    <string name=\"backup_saved\">バックアップを保存</string>\n    <string name=\"group\">グループ</string>\n    <string name=\"clear_feed\">フィードをクリア</string>\n    <string name=\"restore_backup\">バックアップから復元</string>\n    <string name=\"confirm\">確認</string>\n    <string name=\"backup_information\">履歴とお気に入りのバックアップを作成して復元できます</string>\n    <string name=\"read_more\">続きを読む</string>\n    <string name=\"black_dark_theme\">ブラック</string>\n    <string name=\"data_restored_with_errors\">データは復元されましたがエラーが発生しました</string>\n    <string name=\"text_clear_search_history_prompt\">最近の検索クエリを全て完全に削除しますか？</string>\n    <string name=\"zoom_mode_fit_width\">幅を合わせる</string>\n    <string name=\"check_for_new_chapters\">新しいチャプターを探しています</string>\n    <string name=\"protect_application_subtitle\">アプリを起動するためのパスワードを入力してください</string>\n    <string name=\"create_backup\">データバックアップを作成</string>\n    <string name=\"captcha_solve\">解決しました</string>\n    <string name=\"tap_to_try_again\">タップして再試行してください</string>\n    <string name=\"captcha_required\">CAPTCHAが必要です</string>\n    <string name=\"default_s\">デフォルト:%s</string>\n    <string name=\"yesterday\">昨日</string>\n    <string name=\"about_app_translation\">このアプリを翻訳</string>\n    <string name=\"data_restored_success\">全てのデータが復元されました</string>\n    <string name=\"data_restored\">復元</string>\n    <string name=\"preparing_\">準備中…</string>\n    <string name=\"zoom_mode_keep_start\">開始時に維持</string>\n    <string name=\"backup_restore\">バックアップと復元</string>\n    <string name=\"reverse\">リバース</string>\n    <string name=\"reader_mode_hint\">選択した構成はこの漫画のために記憶されます</string>\n    <string name=\"clear_cookies\">クッキーを削除</string>\n    <string name=\"zoom_mode_fit_center\">フィットセンター</string>\n    <string name=\"text_clear_cookies_prompt\">全てのソースからログアウトされます</string>\n    <string name=\"exclude_nsfw_from_history\">NSFW漫画を履歴から除外する</string>\n    <string name=\"queued\">キュー</string>\n    <string name=\"cookies_cleared\">全てのCookieが削除されました</string>\n    <string name=\"tracker_warning\">一部のデバイスはシステムでの動作が異なり、バックグラウンドタスクが中断される可能性があります。</string>\n    <string name=\"genres\">ジャンル</string>\n    <string name=\"scale_mode\">スケールモード</string>\n    <string name=\"today\">今日</string>\n    <string name=\"about_app_translation_summary\">Kotatsuを翻訳する(Weblateのサイトに移動します)</string>\n    <string name=\"next\">次のページ</string>\n    <string name=\"silent\">サイレント</string>\n    <string name=\"sign_in\">サインイン</string>\n    <string name=\"welcome\">ようこそ</string>\n    <string name=\"black_dark_theme_summary\">AMOLEDスクリーンでさらに少ない電力を使用</string>\n    <string name=\"computing_\">計算中…</string>\n    <string name=\"screenshots_allow\">許可する</string>\n    <string name=\"screenshots_block_all\">常にブロック</string>\n    <string name=\"screenshots_policy\">スクリーンショットポリシー</string>\n    <string name=\"screenshots_block_nsfw\">NSFWでブロック</string>\n    <string name=\"suggestions\">提案</string>\n    <string name=\"suggestions_info\">すべてのデータは、このデバイス上でローカルでのみ分析され、どこにも送信されることはありません。</string>\n    <string name=\"suggestions_enable\">サジェスト機能を有効</string>\n    <string name=\"suggestions_summary\">あなたの好みに合わせて漫画を提案</string>\n    <string name=\"disabled\">無効</string>\n    <string name=\"text_suggestion_holder\">マンガを読み始めると、個人的な提案を受けることができます</string>\n    <string name=\"enabled\">有効</string>\n    <string name=\"exclude_nsfw_from_suggestions\">NSFWのマンガを提案しない</string>\n    <string name=\"reset_filter\">フィルターをリセット</string>\n    <string name=\"only_using_wifi\">Wi-Fiのみ使用</string>\n    <string name=\"never\">決して</string>\n    <string name=\"onboard_text\">漫画を読みたい言語を選択します。後で設定から変更することができます。</string>\n    <string name=\"always\">常に</string>\n    <string name=\"preload_pages\">ページのプリロード</string>\n    <string name=\"logged_in_as\">%sとしてログイン</string>\n    <string name=\"nsfw\">18歳以上</string>\n    <string name=\"various_languages\">さまざまな言語</string>\n    <string name=\"search_chapters\">チャプターを検索</string>\n    <string name=\"chapters_empty\">この漫画の章はありません</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"suggestions_updating\">更新のご提案</string>\n    <string name=\"appearance\">外観</string>\n    <string name=\"suggestions_excluded_genres\">ジャンルを除く</string>\n    <string name=\"suggestions_excluded_genres_summary\">サジェストで表示したくないジャンルを指定</string>\n    <string name=\"text_delete_local_manga_batch\">選択した項目をデバイスから完全に削除しますか？</string>\n    <string name=\"removal_completed\">削除が完了しました</string>\n    <string name=\"download_slowdown_summary\">IPアドレスのブロックを回避することができます</string>\n    <string name=\"local_manga_processing\">保存されたマンガの処理</string>\n    <string name=\"download_slowdown\">ダウンロードの速度低下</string>\n    <string name=\"chapters_will_removed_background\">チャプターはバックグラウンドで削除されます</string>\n    <string name=\"hide\">隠す</string>\n    <string name=\"new_sources_text\">新しいマンガソースが利用可能です</string>\n    <string name=\"check_new_chapters_title\">新着チャプターの確認とお知らせ</string>\n    <string name=\"show_notification_new_chapters_on\">読んでいるマンガの更新情報をお知らせします</string>\n    <string name=\"notifications_enable\">通知を有効にする</string>\n    <string name=\"show_notification_new_chapters_off\">通知はありませんが、新しいチャプターはリストでハイライト表示されます</string>\n    <string name=\"name\">名称</string>\n    <string name=\"edit\">編集</string>\n    <string name=\"edit_category\">カテゴリーを編集する</string>\n    <string name=\"empty_favourite_categories\">お気に入りのカテゴリーはありません</string>\n    <string name=\"bookmarks\">ブックマーク</string>\n    <string name=\"bookmark_removed\">ブックマーク削除</string>\n    <string name=\"undo\">元に戻す</string>\n    <string name=\"removed_from_history\">履歴から削除</string>\n    <string name=\"bookmark_add\">ブックマークの追加</string>\n    <string name=\"bookmark_remove\">ブックマークの削除</string>\n    <string name=\"bookmark_added\">ブックマークを追加</string>\n    <string name=\"dns_over_https\">HTTPS 経由の DNS</string>\n    <string name=\"detect_reader_mode\">リーダーモードの自動検出</string>\n    <string name=\"default_mode\">デフォルトモード</string>\n    <string name=\"detect_reader_mode_summary\">マンガがウェブトゥーンかどうかを自動判定</string>\n    <string name=\"disable_battery_optimization\">バッテリー最適化の無効化</string>\n    <string name=\"disable_battery_optimization_summary\">バックグラウンドの更新チェックを支援</string>\n    <string name=\"crash_text\">何か問題が発生しました。開発者にバグレポートを提出し、解決にご協力ください。</string>\n    <string name=\"send\">送信</string>\n    <string name=\"disable_all\">すべて無効にする</string>\n    <string name=\"appwidget_recent_description\">最近読んだ漫画</string>\n    <string name=\"use_fingerprint\">利用可能な場合、指紋認証を使用する</string>\n    <string name=\"appwidget_shelf_description\">お気に入りの漫画</string>\n    <string name=\"report\">報告</string>\n    <string name=\"status_reading\">読書</string>\n    <string name=\"status_re_reading\">再読込</string>\n    <string name=\"status_completed\">完了</string>\n    <string name=\"status_on_hold\">保留中</string>\n    <string name=\"tracking\">追跡</string>\n    <string name=\"logout\">ログアウト</string>\n    <string name=\"status_planned\">予定</string>\n    <string name=\"status_dropped\">ドロップ</string>\n    <string name=\"data_deletion\">データの削除</string>\n    <string name=\"show_reading_indicators_summary\">履歴とお気に入りに既読率を表示する</string>\n    <string name=\"clear_cookies_summary\">いくつかの問題の場合に助けることができる。すべての認証が無効になります</string>\n    <string name=\"show_reading_indicators\">読書の進行状況インジケーターを表示</string>\n    <string name=\"exclude_nsfw_from_history_summary\">NSFWとしてマークされたマンガは履歴に追加されず、進行状況は保存されません</string>\n    <string name=\"show_all\">すべて表示</string>\n    <string name=\"invalid_domain_message\">無効なドメイン</string>\n    <string name=\"select_range\">範囲を選択</string>\n    <string name=\"not_found_404\">コンテンツが見つからない、または削除された</string>\n    <string name=\"manage\">管理</string>\n    <string name=\"random\">ランダム</string>\n    <string name=\"categories_delete_confirm\">選択したお気に入りカテゴリを本当に削除してもよいですか？ \\nその中にあるマンガはすべて失われ、元に戻すことはできません。</string>\n    <string name=\"empty\">空</string>\n    <string name=\"explore\">探索</string>\n    <string name=\"exit_confirmation_summary\">アプリを終了するには、戻るを2回押してください</string>\n    <string name=\"saved_manga\">保存したマンガ</string>\n    <string name=\"no_chapters\">チャプターなし</string>\n    <string name=\"automatic_scroll\">自動スクロール</string>\n    <string name=\"importing_manga\">マンガのインポート</string>\n    <string name=\"email_enter_hint\">続行するにはメールアドレスを入力してください</string>\n    <string name=\"import_completed\">インポートが完了しました</string>\n    <string name=\"canceled\">キャンセル</string>\n    <string name=\"sync_title\">データの同期</string>\n    <string name=\"account_already_exists\">アカウントは既に存在します</string>\n    <string name=\"back\">戻る</string>\n    <string name=\"last_2_hours\">過去 2 時間</string>\n    <string name=\"no_bookmarks_yet\">ブックマークはまだありません</string>\n    <string name=\"sync\">同期</string>\n    <string name=\"pages_cache\">ページキャッシュ</string>\n    <string name=\"available\">利用可能</string>\n    <string name=\"clear_all_history\">すべての履歴を消去する</string>\n    <string name=\"history_cleared\">履歴が消去されました</string>\n    <string name=\"reorder\">並べ替え</string>\n    <string name=\"no_bookmarks_summary\">マンガを読みながらブックマークを作成することができます</string>\n    <string name=\"bookmarks_removed\">ブックマークを削除しました</string>\n    <string name=\"no_manga_sources\">マンガのソースがない</string>\n    <string name=\"no_manga_sources_text\">マンガのソースを有効にして、オンラインでマンガを読めるようにする</string>\n    <string name=\"confirm_exit\">もう一度戻るを押して終了します</string>\n    <string name=\"memory_usage_pattern\">%s -%s</string>\n    <string name=\"exit_confirmation\">退出確認</string>\n    <string name=\"storage_usage\">ストレージの使用状況</string>\n    <string name=\"other_cache\">その他のキャッシュ</string>\n    <string name=\"incognito_mode\">シークレットモード</string>\n    <string name=\"folder_with_images\">画像を含むフォルダ</string>\n    <string name=\"import_will_start_soon\">まもなくインポートが開始されます</string>\n    <string name=\"removed_from_favourites\">お気に入りから削除されました</string>\n    <string name=\"options\">オプション</string>\n    <string name=\"import_completed_hint\">ストレージから元のファイルを削除して、容量を節約することができます</string>\n    <string name=\"reader_info_pattern\">Ch.%1$d/%2$d Pg.%3$d/%4$d</string>\n    <string name=\"reader_info_bar\">リーダーで情報バーを表示する</string>\n    <string name=\"comics_archive\">コミックアーカイブ</string>\n    <string name=\"feed\">フィード</string>\n    <string name=\"manga_error_description_pattern\">エラーの詳細:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. &lt;a href=%2$s&gt;Webブラウザで漫画を開いてみて&lt;/a&gt;、そのソースで利用可能かどうか確認してください&lt;br&gt;2.&lt;a href=kotatsu://about&gt;最新版のこたつ&lt;/a&gt;&lt;br&gt;3.利用可能であれば、開発者にエラーレポートを送ってみてください。</string>\n    <string name=\"reader_control_ltr\">人間工学に基づいたリーダーコントロール</string>\n    <string name=\"history_shortcuts\">最近のマンガのショートカットを表示</string>\n    <string name=\"history_shortcuts_summary\">アプリケーションアイコンを長押しして最近のマンガを利用できるようにする</string>\n    <string name=\"reader_control_ltr_summary\">ページ切り替え方向をリーダーモードに調整しないでください。例えば、右キーを押すと常に次のページに切り替わります。このオプションはハードウェア入力デバイスにのみ影響します。</string>\n    <string name=\"color_correction\">色補正</string>\n    <string name=\"brightness\">輝度</string>\n    <string name=\"contrast\">コントラスト</string>\n    <string name=\"reset\">リセット</string>\n    <string name=\"text_unsaved_changes_prompt\">未保存の変更を保存または破棄しますか\\?</string>\n    <string name=\"discard\">破棄</string>\n    <string name=\"error_no_space_left\">デバイスに空き容量がありません</string>\n    <string name=\"reader_slider\">ページ切り替えスライダーを表示</string>\n    <string name=\"server_error\">サーバーサイドエラー (%1$d) です。後で再試行してください</string>\n    <string name=\"clear_new_chapters_counters\">新しいチャプターの情報も明確に</string>\n    <string name=\"network_unavailable\">ネットワークが利用できません</string>\n    <string name=\"network_unavailable_hint\">Wi-Fiまたはモバイルネットワークをオンにして、オンラインでマンガを読むことができます</string>\n    <string name=\"webtoon_zoom\">Webtoonズーム</string>\n    <string name=\"compact\">コンパクト</string>\n    <string name=\"theme_name_mamimi\">マミミ</string>\n    <string name=\"theme_name_kanade\">奏</string>\n    <string name=\"nothing_here\">ここには何もありません</string>\n    <string name=\"scrobbling_empty_hint\">読書の進捗状況を確認するには、マンガの詳細画面で「メニュー」→「追跡」を選択します。</string>\n    <string name=\"services\">サービス</string>\n    <string name=\"enable_logging_summary\">デバッグ目的でいくつかのアクションを記録します。 何をしているのか分からない場合はオンにしないでください</string>\n    <string name=\"theme_name_miku\">ミク</string>\n    <string name=\"user_agent\">ユーザー エージェント ヘッダー</string>\n    <string name=\"prefetch_content\">コンテンツのプリロード</string>\n    <string name=\"share_logs\">ログを共有</string>\n    <string name=\"allow_unstable_updates\">不安定な更新を許可</string>\n    <string name=\"allow_unstable_updates_summary\">不安定なビルドに関する通知を受け取る</string>\n    <string name=\"download_started\">ダウンロードが開始されました</string>\n    <string name=\"language\">言語</string>\n    <string name=\"source_disabled\">ソースが無効になっています</string>\n    <string name=\"mark_as_current\">現在としてマーク</string>\n    <string name=\"enable_logging\">ログ記録を有効にする</string>\n    <string name=\"show_suspicious_content\">疑わしいコンテンツを表示する</string>\n    <string name=\"theme_name_dynamic\">ダイナミック</string>\n    <string name=\"color_theme\">配色</string>\n    <string name=\"show_in_grid_view\">グリッドビューで表示</string>\n    <string name=\"theme_name_asuka\">アスカ</string>\n    <string name=\"theme_name_mion\">ミオン</string>\n    <string name=\"theme_name_rikka\">リッカ</string>\n    <string name=\"theme_name_sakura\">さくら</string>\n    <string name=\"settings_apply_restart_required\">この変更を適用するには、アプリケーションを再起動してください</string>\n    <string name=\"sources_reorder_tip\">項目をタップ＆ホールドして並び替えをすることができます</string>\n    <string name=\"comics_archive_import_description\">.cbzまたは.zipファイルを1つ以上選択することができ、各ファイルは別のマンガとして認識されます。</string>\n    <string name=\"got_it\">了解</string>\n    <string name=\"show_on_shelf\">棚に表示</string>\n    <string name=\"invert_colors\">色を反転させる</string>\n    <string name=\"enable\">有効</string>\n    <string name=\"no_thanks\">結構です</string>\n    <string name=\"clear_network_cache\">ネットワークキャッシュをクリアする</string>\n    <string name=\"downloads_removed\">ダウンロードは削除されました</string>\n    <string name=\"sync_host_description\">自分で用意した同期サーバーか、デフォルトのものを使用することができます。よく分からない場合は変更しないでください。</string>\n    <string name=\"pause\">一時停止</string>\n    <string name=\"downloads_cancelled\">ダウンロードがキャンセルされました</string>\n    <string name=\"web_view_unavailable\">WebViewが利用できません：WebView providerがインストールされているかどうかを確認してください</string>\n    <string name=\"downloaded\">ダウンロード済み</string>\n    <string name=\"images_proxy_title\">画像最適化プロキシ</string>\n    <string name=\"images_procy_description\">wsrv.nl サービスを使用して、トラフィック使用量を削減し、可能であれば画像の読み込みを高速化します</string>\n    <string name=\"folder_with_images_import_description\">アーカイブや画像のあるディレクトリを選択することができます。各アーカイブ（またはサブディレクトリ）は、1つのチャプターとして認識されます。</string>\n    <string name=\"find_similar\">類似したものを探す</string>\n    <string name=\"type\">タイプ</string>\n    <string name=\"address\">アドレス</string>\n    <string name=\"port\">ポート</string>\n    <string name=\"proxy\">プロキシ</string>\n    <string name=\"sync_settings\">同期の設定</string>\n    <string name=\"server_address\">サーバーアドレス</string>\n    <string name=\"speed\">速度</string>\n    <string name=\"ignore_ssl_errors\">SSLエラーを無視する</string>\n    <string name=\"mirror_switching\">ミラーを自動的に選択する</string>\n    <string name=\"mirror_switching_summary\">ミラーが利用可能な場合、エラー時にマンガ ソースのドメインを自動的に切り替える</string>\n    <string name=\"resume\">履歴書</string>\n    <string name=\"paused\">一時停止</string>\n    <string name=\"cancel_all\">全てキャンセル</string>\n    <string name=\"downloads_wifi_only\">Wi-Fi経由でのみダウンロード</string>\n    <string name=\"downloads_wifi_only_summary\">モバイルデータ通信への切り替え時にダウンロードを停止する</string>\n    <string name=\"suggestion_manga\">提案： %s</string>\n    <string name=\"suggestions_notifications_summary\">提案されたマンガの通知を表示することがあります</string>\n    <string name=\"more\">もっと見る</string>\n    <string name=\"cancel_all_downloads_confirm\">アクティブなダウンロードはすべてキャンセルされ、部分的にダウンロードされたデータは失われます</string>\n    <string name=\"remove_completed_downloads_confirm\">ダウンロード履歴は完全に削除されます</string>\n    <string name=\"text_downloads_list_holder\">ダウンロードはありません</string>\n    <string name=\"downloads_resumed\">ダウンロードが再開されました</string>\n    <string name=\"downloads_paused\">ダウンロードが一時停止されました</string>\n    <string name=\"suggestions_enable_prompt\">パーソナライズされた漫画の提案を受け取りますか？</string>\n    <string name=\"remove_completed\">削除が完了</string>\n    <string name=\"sync_auth_hint\">既存のアカウントにサインインするか、新規にアカウントを作成することができます</string>\n    <string name=\"invalid_value_message\">無効な値</string>\n    <string name=\"username\">ユーザー名</string>\n    <string name=\"password\">パスワード</string>\n    <string name=\"authorization_optional\">オーソライズ（オプション）</string>\n    <string name=\"invalid_port_number\">無効なポート番号です</string>\n    <string name=\"network\">ネットワーク</string>\n    <string name=\"data_and_privacy\">データとプライバシー</string>\n    <string name=\"restore_summary\">以前作成したバックアップを復元する</string>\n    <string name=\"webtoon_zoom_summary\">ウェブトゥーンモードでのズームインジェスチャを許可する</string>\n    <string name=\"show_pages_numbers_summary\">下隅にページ番号を表示する</string>\n    <string name=\"reader_info_bar_summary\">画面上部に現在時刻と読書の進行状況を表示する</string>\n    <string name=\"clear_source_cookies_summary\">指定されたドメインのクッキーのみをクリアします。ほとんどの場合、認証は無効になります</string>\n    <string name=\"download_option_all_chapters\">翻訳付きのすべての章%s</string>\n    <string name=\"download_option_whole_manga\">マンガ全体</string>\n    <string name=\"download_option_first_n_chapters\">最初%s</string>\n    <string name=\"download_option_next_unread_n_chapters\">次の未読%s</string>\n    <string name=\"download_option_all_unread\">すべての未読の章</string>\n    <string name=\"download_option_all_unread_b\">すべての未読チャプター (%s)</string>\n    <string name=\"download_option_manual_selection\">手動でチャプターを選択</string>\n    <string name=\"no_access_to_file\">このファイルまたはディレクトリにアクセスできません</string>\n    <string name=\"pick_custom_directory\">カスタムディレクトリを選択</string>\n    <string name=\"local_manga_directories\">ローカルマンガディレクトリ</string>\n    <string name=\"this_month\">今月</string>\n    <string name=\"description\">説明</string>\n    <string name=\"voice_search\">音声検索</string>\n    <string name=\"background\">バックグラウンド</string>\n    <string name=\"data_not_restored_text\">正しいバックアップファイルを選択しているか確認してください</string>\n    <string name=\"data_not_restored\">データを復元することが出来ませんでした</string>\n    <string name=\"suggestions_wifi_only_summary\">従量制課金ネットワーク接続を使用して提案を更新しないでください</string>\n    <string name=\"search_hint\">漫画のタイトル、ジャンル、またはソース名を入力してください</string>\n    <string name=\"progress\">進捗状況</string>\n    <string name=\"order_added\">追加</string>\n    <string name=\"show\">見せる</string>\n    <string name=\"related_manga\">関連漫画</string>\n    <string name=\"color_light\">ライト</string>\n    <string name=\"color_dark\">ダーク</string>\n    <string name=\"color_white\">ホワイト</string>\n    <string name=\"color_black\">ブラック</string>\n    <string name=\"tracker_wifi_only_summary\">従量制ネットワーク接続を使用して新しいチャプターをチェックしないでください</string>\n    <string name=\"manage_categories\">カテゴリーの管理</string>\n    <string name=\"captcha_required_summary\">%sを正しく動作させるには、キャプチャを解決する必要があります</string>\n    <string name=\"languages\">言語</string>\n    <string name=\"unknown\">不明</string>\n    <string name=\"in_progress\">進行状況</string>\n    <string name=\"error_corrupted_file\">無効なデータが返されたか、ファイルが破損しています</string>\n    <string name=\"related_manga_summary\">関連漫画のリストを表示します。場合によっては不正確または欠落している可能性があります</string>\n    <string name=\"advanced\">高度</string>\n    <string name=\"too_many_requests_message\">リクエストが多すぎます。 後でもう一度お試しください</string>\n    <string name=\"manga_list\">漫画一覧</string>\n    <string name=\"disable_nsfw\">NSFWを非表示にする</string>\n    <string name=\"on_device\">デバイス上</string>\n    <string name=\"items_limit_exceeded\">これ以上アイテムを追加出来ません</string>\n    <string name=\"directories\">ディレクトリ</string>\n    <string name=\"main_screen_sections\">メイン画面のセクション</string>\n    <string name=\"moved_to_top\">上部に移動しました</string>\n    <string name=\"to_top\">最上部へ移動</string>\n    <string name=\"zoom_in\">ズームイン</string>\n    <string name=\"reader_zoom_buttons_summary\">右下にズームコントロールボタンを表示するかどうか</string>\n    <string name=\"reader_zoom_buttons\">ズームボタンを表示</string>\n    <string name=\"zoom_out\">ズームアウト</string>\n    <string name=\"retry\">リトライ</string>\n    <string name=\"open_telegram_bot\">Telegramボットを開く</string>\n    <string name=\"send_backups_telegram\">Telegramにバックアップを送信</string>\n    <string name=\"test_connection\">テスト接続</string>\n    <string name=\"telegram_chat_id_summary\">バックアップを送信するチャットIDを入力してください</string>\n    <string name=\"open_telegram_bot_summary\">タップしてKotatsuバックアップボットを開く</string>\n    <string name=\"clear_database\">データベース消去</string>\n    <string name=\"clear_database_summary\">使われていないマンガの情報を削除する</string>\n    <string name=\"enable_all_sources\">すべての漫画ソースを有効にする</string>\n    <string name=\"enable_all_sources_summary\">利用可能なすべての漫画ソースを永久に有効にする</string>\n    <string name=\"all_sources_enabled\">すべてのソースが有効</string>\n    <string name=\"automatic\">自動</string>\n    <string name=\"invalid_server_address_message\">無効なサーバーアドレス</string>\n    <string name=\"text_empty_holder_secondary_filtered\">選択した条件に一致するマンガはありません</string>\n    <string name=\"email_password_enter_hint\">メールアドレスとパスワードを入力して、続行してください</string>\n    <string name=\"pull_top_no_prev\">前のチャプターがありません</string>\n    <string name=\"pull_bottom_no_next\">次のチャプターがありません</string>\n    <string name=\"too_many_requests_message_retry\">リクエストが多すぎます。%s 秒後に再度お試しください。</string>\n    <string name=\"keep_screen_on\">常に画面表示</string>\n    <string name=\"keep_screen_on_summary\">漫画を読んでいる間は画面をオフにしない</string>\n    <string name=\"enhanced_colors_summary\">バンドノイズを低減しますが、パフォーマンスへの影響が生じる場合があります</string>\n    <string name=\"enhanced_colors\">32bitカラーモード</string>\n    <string name=\"suggest_new_sources\">アプリ更新後に新しいソースを提案する</string>\n    <string name=\"suggest_new_sources_summary\">アプリケーション更新後に新しく追加されたソースを使用できるようにするかどうか確認する</string>\n    <string name=\"by_relevance\">関連性</string>\n    <string name=\"categories\">カテゴリ</string>\n    <string name=\"online_variant\">オンライン版</string>\n    <string name=\"periodic_backups\">定期的バックアップ</string>\n    <string name=\"backup_frequency\">バックアップ頻度</string>\n    <string name=\"pages_saved\">保存しました</string>\n    <string name=\"list_options\">リストオプション</string>\n    <string name=\"frequency_every_day\">毎日</string>\n    <string name=\"frequency_every_2_days\">２日毎に</string>\n    <string name=\"frequency_once_per_week\">週に一度</string>\n    <string name=\"frequency_twice_per_month\">月２回</string>\n    <string name=\"frequency_once_per_month\">月に一度</string>\n    <string name=\"periodic_backups_enable\">定期的なバックアップを有効にする</string>\n    <string name=\"backups_output_directory\">バックアップを保存するディレクトリ</string>\n    <string name=\"last_successful_backup\">最後の成功したバックアップ：%s</string>\n    <string name=\"content_type_manga\">マンガ</string>\n    <string name=\"content_type_hentai\">ヘンタイ</string>\n    <string name=\"content_type_comics\">コミック</string>\n    <string name=\"content_type_other\">その他</string>\n    <string name=\"sources_catalog\">ソースカタログ</string>\n    <string name=\"source_enabled\">ソースの有効化</string>\n    <string name=\"no_manga_sources_catalog_text\">このセクションには利用可能なソースがありません。あるいは、すでにすべて追加済みである可能性があります。\\n続報をお待ちください</string>\n    <string name=\"no_manga_sources_found\">お探しの漫画が見つかりませんでした</string>\n    <string name=\"catalog\">カタログ</string>\n    <string name=\"disable_nsfw_summary\">NSFWを無効化し、可能であれば成人向け漫画をリストから非表示にする</string>\n    <string name=\"state_paused\">ポーズ</string>\n    <string name=\"reader_optimize\">メモリ消費の削減(ベータ版)</string>\n    <string name=\"error_multiple_genres_not_supported\">このマンガソースでは、複数のジャンルによるフィルタリングはサポートされていません</string>\n    <string name=\"error_multiple_states_not_supported\">この漫画ソースでは、複数の状態によるフィルタリングはサポートされていません</string>\n    <string name=\"error_search_not_supported\">この漫画ソースでは検索機能はサポートされていません</string>\n    <string name=\"downloads_settings_info\">サーバー側のブロックに問題がある場合、ソース設定で各漫画ソースごとに個別にダウンロード速度制限を有効にできます</string>\n    <string name=\"skip\">スキップ</string>\n    <string name=\"grayscale\">グレースケール</string>\n    <string name=\"globally\">グローバル</string>\n    <string name=\"this_manga\">マンガ</string>\n    <string name=\"color_correction_apply_text\">これらの設定は、全体に適用することも、現在の漫画だけに適用することもできます。全体に適用した場合、個別の設定は上書きされません。</string>\n    <string name=\"apply\">適用</string>\n    <string name=\"error_filter_locale_genre_not_supported\">このソースでは、ジャンルとロケールの両方でフィルタリングすることはサポートされていません</string>\n    <string name=\"genres_search_hint\">ジャンル名を入力してください</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">ダウンロードに問題がある場合、これを試すと開始できるかもしれません</string>\n    <string name=\"welcome_text\">有効にしたいコンテンツソースを選択してください。設定画面で後から変更することも可能です。</string>\n    <string name=\"sync_auth\">アカウントを同期するためにログインしてください</string>\n    <string name=\"restore\">復元</string>\n    <string name=\"backup_date_\">バックアップ日時: %s</string>\n    <string name=\"state_upcoming\">近日公開</string>\n    <string name=\"genres_exclude\">除外するジャンル</string>\n    <string name=\"rating_safe\">安全</string>\n    <string name=\"rating_adult\">アダルト</string>\n    <string name=\"rating_suggestive\">示唆的</string>\n    <string name=\"default_tab\">既定のタブ</string>\n    <string name=\"mark_as_completed\">完了としてマークする</string>\n    <string name=\"mark_as_completed_prompt\">マークした漫画を完全に読み終えたものとしますか？\\n\\n警告：現在の読書進捗が消去されます。</string>\n    <string name=\"category_hidden_done\">このカテゴリはメイン画面から非表示になっており、メニュー → カテゴリ管理 からアクセスできます</string>\n    <string name=\"volume_\">%d巻</string>\n    <string name=\"incognito_mode_hint\">読書進捗は保存されません</string>\n    <string name=\"show_menu\">メニュー表示</string>\n    <string name=\"toggle_ui\">UIの 表示/非表示</string>\n    <string name=\"prev_chapter\">前のチャプター</string>\n    <string name=\"next_chapter\">次のチャプター</string>\n    <string name=\"prev_page\">前のページ</string>\n    <string name=\"next_page\">次のページ</string>\n    <string name=\"reader_actions_summary\">タップ可能な画面領域のアクションを設定する</string>\n    <string name=\"switch_pages_volume_buttons\">音量ボタンを有効化</string>\n    <string name=\"switch_pages_volume_buttons_summary\">ページの切り替えに、音量ボタンを使用します</string>\n    <string name=\"reader_navigation_inverted\">ナビゲーションコントロールを反転</string>\n    <string name=\"reader_navigation_inverted_summary\">音量ボタンと方向キー（左/上/下/右）の操作方向を入れ替える</string>\n    <string name=\"tap_action\">タップ時のアクション</string>\n    <string name=\"long_tap_action\">長押し時のアクション</string>\n    <string name=\"config_reset_confirm\">設定をデフォルト値に戻しますか？ この操作は元に戻せません。</string>\n    <string name=\"use_two_pages_landscape\">横向きレイアウトで2ページ構成を使用する（ベータ版）</string>\n    <string name=\"default_webtoon_zoom_out\">デフォルトのウェブトゥーンを縮小表示</string>\n    <string name=\"fullscreen_mode\">フルスクリーンモード</string>\n    <string name=\"reader_fullscreen_summary\">システムステータスとナビゲーションバーを非表示にする</string>\n    <string name=\"reading_time_estimation\">推定読了時間</string>\n    <string name=\"reading_time_estimation_summary\">時間見積もりは不正確な場合があります</string>\n    <string name=\"suggestions_unavailable_text\">提案機能は無効化されています</string>\n    <string name=\"check_for_new_chapters_disabled\">新章の確認は無効化されています</string>\n    <string name=\"show_labels_in_navbar\">ナビゲーションバーにラベルを表示する</string>\n    <string name=\"pages_saving\">ページを保存</string>\n    <string name=\"remove_from_history\">履歴から削除</string>\n    <string name=\"preferred_download_format\">推奨ダウンロード形式</string>\n    <string name=\"single_cbz_file\">単一のCBZファイル</string>\n    <string name=\"multiple_cbz_files\">複数のCBZファイル</string>\n    <string name=\"other_manga\">他のマンガ</string>\n    <string name=\"less_than_minute\">1分未満</string>\n    <string name=\"statistics\">統計</string>\n    <string name=\"clear_stats_confirm\">本当にすべての閲覧統計を消去しますか？この操作は元に戻せません。</string>\n    <string name=\"stats_cleared\">統計を消去した</string>\n    <string name=\"clear_stats\">統計を消去</string>\n    <string name=\"week\">週</string>\n    <string name=\"month\">月</string>\n    <string name=\"day\">日</string>\n    <string name=\"three_months\">三ヶ月</string>\n    <string name=\"empty_stats_text\">選択した期間の統計データはありません</string>\n    <string name=\"alternatives\">代替案</string>\n    <string name=\"migrate\">移行</string>\n    <string name=\"manga_migration\">マンガの移行</string>\n    <string name=\"migration_completed\">移行完了</string>\n    <string name=\"delete_read_chapters\">既読のチャプターを削除する</string>\n    <string name=\"no_chapters_deleted\">削除されたチャプターはありません</string>\n    <string name=\"delete_read_chapters_summary\">ローカルストレージから既読のチャプターを削除して空き容量を増やす</string>\n    <string name=\"delete_read_chapters_prompt\">これにより、ローカルストレージから既読としてマークされた全てのチャプターが完全に削除されます。後で再ダウンロードすることは可能ですが、インポートされたチャプターは永久に失われる可能性があります。</string>\n    <string name=\"delete_read_chapters_auto\">既読のチャプターを自動的に削除する</string>\n    <string name=\"runs_on_app_start\">kotatsuの起動時に実行</string>\n    <string name=\"split_by_translations_summary\">異なる翻訳のチャプターを、一つのリストではなく別々に表示する</string>\n    <string name=\"unread\">未読</string>\n    <string name=\"unsupported_source\">このマンガソースはサポートされていません</string>\n    <string name=\"show_pages_thumbs\">サムネイルを表示</string>\n    <string name=\"show_pages_thumbs_summary\">詳細画面で「ページ」タブを有効にする</string>\n    <string name=\"error_no_data_received\">サーバーからデータを受信できませんでした</string>\n    <string name=\"unsupported_backup_message\">kotatsuのバックアップファイルを選択してください。</string>\n    <string name=\"hours_short\">%d 時</string>\n    <string name=\"minutes_short\">%d 分</string>\n    <string name=\"seconds_short\">%d 秒</string>\n    <string name=\"hours_minutes_short\">%1$d 時 %2$d 分</string>\n    <string name=\"minutes_seconds_short\">%1$d 分 %2$d 秒</string>\n    <string name=\"fix\">修正</string>\n    <string name=\"missing_storage_permission\">外部ストレージ上のマンガへのアクセスが許可されていません</string>\n    <string name=\"webtoon_gaps\">ウェブトゥーンモードの空白部分</string>\n    <string name=\"webtoon_gaps_summary\">ウェブトゥーンモードでページ間の縦方向の空白を表示する</string>\n    <string name=\"enable_pull_gesture_title\">プルジェスチャーを有効にする</string>\n    <string name=\"enable_pull_gesture_summary\">プル操作でウェブトゥーンのチャプターを切り替える</string>\n    <string name=\"less_frequently\">あまり頻繁ではない</string>\n    <string name=\"frequency_of_check\">チェック頻度</string>\n    <string name=\"pin_navigation_ui\">ナビゲーションUIを固定</string>\n    <string name=\"pin_navigation_ui_summary\">スクロール時にナビゲーション バーと検索ビューを非表示にしない</string>\n    <string name=\"search_suggestions\">検索候補</string>\n    <string name=\"authors\">著者</string>\n    <string name=\"blocked_by_server_message\">サーバーによってブロックされています。別のネットワーク接続（VPN、プロキシなど）をお試しください。</string>\n    <string name=\"disable\">無効化</string>\n    <string name=\"ignore_ssl_errors_summary\">ネットワークリソースへのアクセス時にSSL関連の問題が発生した場合、SSL証明書の検証を無効にできます。これによりセキュリティに影響が生じる可能性があります。この設定変更後はkotatsuの再起動が必要です。</string>\n    <string name=\"disable_connectivity_check_summary\">接続確認に問題がある場合（例：ネットワークが接続されているにもかかわらずオフラインモードになる場合など）、接続確認をスキップしてください。</string>\n    <string name=\"disable_nsfw_notifications\">NSFW通知を無効にする</string>\n    <string name=\"disable_nsfw_notifications_summary\">NSFWマンガの更新に関する通知を表示しない</string>\n    <string name=\"all_languages\">すべての言語</string>\n    <string name=\"screenshots_block_incognito\">シークレットモード時にブロック</string>\n    <string name=\"crop_pages\">ページをトリミングする</string>\n    <string name=\"percent_read\">既読率</string>\n    <string name=\"plugin_incompatible\">互換性のないプラグインまたは内部エラーが発生しました。プラグインとKotatsuの最新バージョンを使用していることを確認してください。</string>\n    <string name=\"plugin_incompatible_with_cause\">プラグインエラー: %s\\nプラグインとKotatsuの最新バージョンを使用していることを確認してください</string>\n    <string name=\"connection_ok\">正常に接続できました</string>\n    <string name=\"invalid_proxy_configuration\">無効なプロキシ設定</string>\n    <string name=\"show_quick_filters\">クイックフィルターを表示</string>\n    <string name=\"sfw\">安全なコンテンツ(SFW)</string>\n    <string name=\"skip_all\">すべてスキップ</string>\n    <string name=\"updated_long_ago\">長らく更新されていません</string>\n    <string name=\"unpopular\">不人気</string>\n    <string name=\"low_rating\">低評価</string>\n    <string name=\"sort_order_asc\">上昇中</string>\n    <string name=\"sort_order_desc\">下降中</string>\n    <string name=\"by_date\">日付</string>\n    <string name=\"popularity\">人気</string>\n    <string name=\"scrobbler_auth_required\">%s にサインインして続行してください</string>\n    <string name=\"scrobbler_auth_intro\">%sとの連携を設定するにはサインインしてください。これにより、マンガの読了状況や進捗を追跡できるようになります。</string>\n    <string name=\"unstable_feature\">不安定な機能</string>\n    <string name=\"unstable_feature_summary\">本機能は試験的なものです。データ損失を防ぐため、必ずバックアップを取ってください。</string>\n    <string name=\"downloads_background\">バックグラウンドでのダウンロード</string>\n    <string name=\"download_new_chapters\">新しいチャプターをダウンロード</string>\n    <string name=\"manga_replaced\">マンガ「%1$s」(%2$s) が「%3$s」(%4$s) に置き換えられました</string>\n    <string name=\"fixing_manga\">マンガの修正</string>\n    <string name=\"fixed\">正常に修正されました</string>\n    <string name=\"no_fix_required\">「%s」に対する修正は不要です</string>\n    <string name=\"no_alternatives_found\">「%s」に対する修正は不要です</string>\n    <string name=\"manga_fix_prompt\">この機能は選択したマンガの代替ソースを検索します。処理には時間がかかり、バックグラウンドで実行されます。</string>\n    <string name=\"content_type_novel\">小説</string>\n    <string name=\"content_type_manhua\">マンガ</string>\n    <string name=\"content_type_manhwa\">マンガ</string>\n    <string name=\"recently_added\">最近追加された</string>\n    <string name=\"added_long_ago\">かなり前につけ加えられました</string>\n    <string name=\"demographic_shounen\">少年</string>\n    <string name=\"demographic_shoujo\">少女</string>\n    <string name=\"demographic_seinen\">青年</string>\n    <string name=\"demographic_josei\">女性</string>\n    <string name=\"filter_search_warning\">このソースはフィルター付き検索をサポートしていません。フィルターがクリアされました</string>\n    <string name=\"content_type_doujinshi\">同人誌</string>\n    <string name=\"debug\">デバッグ</string>\n    <string name=\"source_code\">ソースコード</string>\n    <string name=\"user_manual\">ユーザーマニュアル</string>\n    <string name=\"error_image_format\">サポートされていない画像形式: %s</string>\n    <string name=\"error_not_image\">無効な形式です: 画像が期待されましたが %s が入力されました</string>\n    <string name=\"start_download\">ダウンロード開始</string>\n    <string name=\"save_manga_confirm\">選択したマンガを保存しますか？通信量とディスク容量を消費する可能性があります</string>\n    <string name=\"save_manga\">マンガを保存</string>\n    <string name=\"genre\">ジャンル</string>\n    <string name=\"download_added\">ダウンロードに追加</string>\n    <string name=\"chapter_selection_hint\">チャプターリストの項目を長押しすると、ダウンロードするチャプターを選択できます。</string>\n    <string name=\"frequency_every_6_hours\">6時間毎に</string>\n    <string name=\"available_pattern\">%1$sが有効</string>\n    <string name=\"no_chapters_in_manga\">この漫画には利用可能なチャプターが含まれていません</string>\n    <string name=\"chapters_load_failed\">チャプターリストの読み込みに失敗</string>\n    <string name=\"telegram_integration\">Telegramで連携</string>\n    <string name=\"test_parser\">漫画のソースをテストする</string>\n    <string name=\"rename\">リネーム</string>\n    <string name=\"save_filter\">フィルターを保存</string>\n    <string name=\"overwrite\">上書き</string>\n    <string name=\"filter_overwrite_confirm\">「%s」という名前のフィルターが既に存在します。上書きしますか？</string>\n    <string name=\"storage_and_network\">ストレージとネットワーク</string>\n    <string name=\"create_or_restore_backup\">バックアップを作成または復元する</string>\n    <string name=\"data_removal\">データ削除</string>\n    <string name=\"privacy\">プライバシー</string>\n    <string name=\"source_broken_warning\">この漫画ソースは破損しているとマークされています。一部の機能が動作しない可能性があります</string>\n    <string name=\"download_default_directory\">漫画ダウンロードのデフォルトディレクトリ</string>\n    <string name=\"private_app_directory_warning\">アプリケーションをアンインストールすると、このディレクトリとすべてのデータが削除されます</string>\n    <string name=\"author\">著者</string>\n    <string name=\"rating\">評価</string>\n    <string name=\"source\">ソース</string>\n    <string name=\"translation\">翻訳</string>\n    <string name=\"captcha_required_message\">このソースを続行するにはキャプチャを解決する必要があります</string>\n    <string name=\"ask_every_time\">いつでも聞いて</string>\n    <string name=\"screen_orientation\">画面の向き</string>\n    <string name=\"portrait\">肖像画</string>\n    <string name=\"landscape\">風景</string>\n    <string name=\"by_name_reverse\">名前を反転</string>\n    <string name=\"content_rating\">コンテンツレーティング</string>\n    <string name=\"global_search\">グローバルサーチ</string>\n    <string name=\"disable_captcha_notifications\">Captcha通知を無効にする</string>\n    <string name=\"disable_captcha_notifications_summary\">このソースのCaptchaに関する通知は届きませんが、これによりバックグラウンド操作（新章の確認、おすすめ情報の取得など）が中断される可能性があります</string>\n    <string name=\"chapter_volume_number\">巻 %1$s 章 %2$s</string>\n    <string name=\"chapter_number\">章%s</string>\n    <string name=\"unnamed_chapter\">名前のない章</string>\n    <string name=\"search_disabled_sources\">無効化されたソースを検索する</string>\n    <string name=\"error_details\">エラーの詳細</string>\n    <string name=\"error_disclaimer_manga\">ウェブブラウザで漫画を開いて、そのソースで利用可能かどうかを確認してください。</string>\n    <string name=\"error_disclaimer_app_outdated\">お使いのKotatsuのバージョンが古くなっているようです。最新のバージョンをインストールして、利用可能なすべての修正プログラムを入手してください。</string>\n    <string name=\"error_disclaimer_report\">開発者へバグ報告を提出できます。これにより問題の調査と修正に役立ちます。</string>\n    <string name=\"link_to_manga_on_s\">%sの漫画へのリンク</string>\n    <string name=\"link_to_manga_in_app\">Kotatsuの中の漫画へのリンク</string>\n    <string name=\"clear_browser_data\">ブラウザのデータを消去する</string>\n    <string name=\"clear_browser_data_summary\">キャッシュやクッキーなどのブラウザデータを消去してください。警告：マンガソースでの認証が無効になる可能性があります</string>\n    <string name=\"no_write_permission_to_file\">ファイルへの書き込み権限がありません</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">成人向け漫画はおすすめに表示されません。このオプションは一部のソースでは正確に機能しない場合があります</string>\n    <string name=\"include_disabled_sources\">無効化されたソースを含める</string>\n    <string name=\"suggestions_disabled_sources_summary\">すべてのマンガソースからの提案を表示する（無効化されているものも含む）</string>\n    <string name=\"tags_warnings\">危険なジャンルを強調する</string>\n    <string name=\"tags_warnings_summary\">ほとんどのユーザーにとって不適切と思われるジャンルを強調表示する</string>\n    <string name=\"error_non_file_uri\">選択されたパスはファイルまたはディレクトリを指していないため使用できません</string>\n    <string name=\"manga_override_hint\">これらの変更は、アプリ内でのマンガの表示方法に影響を与えます</string>\n    <string name=\"use_default_cover\">デフォルトの表紙を使用する</string>\n    <string name=\"pick_manga_page\">漫画のページを選ぶ</string>\n    <string name=\"pick_custom_file\">カスタムファイルを選択</string>\n    <string name=\"change_cover\">カバーを変える</string>\n    <string name=\"page_switch_timer\">ページは約%d秒ごとに切り替わります</string>\n    <string name=\"dont_ask_again\">二度と聞くな</string>\n    <string name=\"incognito_mode_hint_nsfw\">この漫画には成人向けの内容が含まれている可能性があります。シークレットモードを使用しますか？</string>\n    <string name=\"incognito_for_nsfw\">成人向け漫画のシークレットモード</string>\n    <string name=\"additional_action_required\">追加の操作が必要です</string>\n    <string name=\"hide_from_main_screen\">メイン画面から非表示にする</string>\n    <string name=\"changelog\">変更履歴</string>\n    <string name=\"changelog_summary\">最近リリースされたバージョンの変更履歴</string>\n    <string name=\"collapse\">折りたたむ</string>\n    <string name=\"expand\">拡張する</string>\n    <string name=\"adblock\">ブラウザで広告をブロックする</string>\n    <string name=\"adblock_summary\">組み込みブラウザでの広告ブロック（ベータ版）</string>\n    <string name=\"collapse_long_description\">長い説明を折りたたむ</string>\n    <string name=\"creating_backup\">バックアップを作成する</string>\n    <string name=\"share_backup\">バックアップを共有する</string>\n    <string name=\"reader_multitask\">別のタスクでリーダーを開く</string>\n    <string name=\"reader_multitask_summary\">複数の異なる漫画を開いたまま、複数のリーダーを同時に保持できます</string>\n    <string name=\"theme_name_itsuka\">トピックス</string>\n    <string name=\"theme_name_totoro\">トトロ</string>\n    <string name=\"book_effect\">黄色がかった背景(青いフィルター)</string>\n    <string name=\"local_storage_cleanup\">ローカルストレージのクリーンアップ</string>\n    <string name=\"packup_creation_failed\">バックアップの作成に失敗しました</string>\n    <string name=\"main_screen\">メイン画面</string>\n    <string name=\"main_screen_fab\">フローティングの「続行」ボタンを表示</string>\n    <string name=\"main_screen_fab_summary\">ワンクリックで読み続けられます。このボタンはシークレットモード時や履歴が空の時には表示されません</string>\n    <string name=\"error_corrupted_zip\">破損したZIPアーカイブ(%s)</string>\n    <string name=\"discord_rpc\">Discord リッチプレゼンス</string>\n    <string name=\"discord_token\">Discordトークン</string>\n    <string name=\"discord_token_summary\">リッチプレゼンスを有効にするには、Discordトークンを入力してください</string>\n    <string name=\"discord_token_description\">Discord トークンを入力するか、%s をクリックしてブラウザーで取得します</string>\n    <string name=\"discord_token_hint\">ここにDiscordトークンを貼り付けてください</string>\n    <string name=\"discord_rpc_summary\">Discordで読み取りステータスを表示</string>\n    <string name=\"obtain\">アクセス</string>\n    <string name=\"discord_rpc_description\">Kotatsuで漫画を読む - 漫画リーダーアプリ</string>\n    <string name=\"reading_s\">読書 %s</string>\n    <string name=\"read_on_s\">%s で読む</string>\n    <string name=\"rpc_skip_nsfw_summary\">成人向けコンテンツに RPC を使用しないでください</string>\n    <string name=\"invalid_token\">無効なトークン: %s</string>\n    <string name=\"show_floating_control_button\">「続行」ボタンをフローティングで表示</string>\n    <string name=\"unavailable\">利用案内</string>\n    <string name=\"manga_restricted_description\">このマンガは、このソースで読み込むことはできません。 他のソースで検索するか、ブラウザで開くと、より多くの情報が得られます</string>\n    <string name=\"saved_filters\">保存フィルター</string>\n    <string name=\"chapters_grid_view\">グリッドビュー</string>\n    <string name=\"enter_name\">名前を入力してください</string>\n    <string name=\"nsfw_16\">16歳以上</string>\n    <string name=\"theme_name_expressive\">エクスプレス(テスト)</string>\n    <string name=\"pull_to_prev_chapter\">前の章を開く</string>\n    <string name=\"pull_to_next_chapter\">次の章を開く</string>\n    <string name=\"state_abandoned\">ドロップ</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">ロック スクリーンの回転</string>\n    <string name=\"manage_sources\">ソースの管理</string>\n    <string name=\"manual\">マニュアル</string>\n    <string name=\"available_d\">利用可能: %1$d</string>\n    <string name=\"reader_optimize_summary\">画面外ページの品質を下げ、メモリ使用量を削減する</string>\n    <string name=\"state\">ステータス</string>\n    <string name=\"error_filter_states_genre_not_supported\">両方のジャンルや状態のフィルタリングは、このソースではサポートされていません</string>\n    <string name=\"volume_unknown\">未知のボリューム</string>\n    <string name=\"vertical\">プロフィール</string>\n    <string name=\"last_read\">最近の投稿</string>\n    <string name=\"reader_actions\">リーダーの動作</string>\n    <string name=\"none\">なし</string>\n    <string name=\"two_page_scroll_sensitivity\">2ページスクロール感度</string>\n    <string name=\"ask_for_dest_dir_every_time\">毎回、目的のディレクトリを指定してください</string>\n    <string name=\"default_page_save_dir\">デフォルトページ保存ディレクトリ</string>\n    <string name=\"location\">アクセス</string>\n    <string name=\"reading_stats\">統計を読む</string>\n    <string name=\"all_time\">すべての時間</string>\n    <string name=\"pages_read_s\">ページの読み込み: %s</string>\n    <string name=\"migrate_confirmation\">「%2$s」から「%3$s」を「%4$s」に置き換えられます</string>\n    <string name=\"chapters_deleted_pattern\">%1$s を削除し、%2$s をクリア</string>\n    <string name=\"split_by_translations\">翻訳によるスプリット</string>\n    <string name=\"order_oldest\">最新記事</string>\n    <string name=\"long_ago_read\">昔読んだ</string>\n    <string name=\"enable_source\">ソースを有効にする</string>\n    <string name=\"last_used\">最後の使用</string>\n    <string name=\"show_updated\">更新情報</string>\n    <string name=\"more_frequently\">より頻繁に</string>\n    <string name=\"recent_queries\">最近の質問</string>\n    <string name=\"suggested_queries\">提案されたクエリ</string>\n    <string name=\"sources_disabled\">無効なソース</string>\n    <string name=\"disable_connectivity_check\">接続チェックを無効にする</string>\n    <string name=\"tracker_debug_info\">新しいチャプターログの確認</string>\n    <string name=\"tracker_debug_info_summary\">新規チャプター向けバックグラウンドチェックに関するデバッグ情報</string>\n    <string name=\"_new\">新しい</string>\n    <string name=\"image_server\">優先画像サーバ</string>\n    <string name=\"pin\">ピン</string>\n    <string name=\"unpin\">ピンを外す</string>\n    <string name=\"source_pinned\">ソース ピン留め</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-jv/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d glintir</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d bagéan</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d bagéyan</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d waktu kepungkur</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d jam kepungkur</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d dinten ingkang rumiyin</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d sasi ingkang rumiyin</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d tabuh</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d menit</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-jv/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"demographic_kodomo\">bocah-bocah</string>\n    <string name=\"local_storage\">Panyimpenan tlatah</string>\n    <string name=\"favourites\">Seneng</string>\n    <string name=\"history\">Babadan</string>\n    <string name=\"error_occurred\">Ana kesalahan</string>\n    <string name=\"network_error\">kelepatan jaringan</string>\n    <string name=\"details\">rinci</string>\n    <string name=\"chapters\">Jilid</string>\n    <string name=\"list\">dhaptar</string>\n    <string name=\"detailed_list\">dhaptar rinci</string>\n    <string name=\"grid\">wewacan</string>\n    <string name=\"list_mode\">modus dhaptar</string>\n    <string name=\"settings\">Pathokan</string>\n    <string name=\"remote_sources\">Sumber Manga</string>\n    <string name=\"loading_\">Ngewrat…</string>\n    <string name=\"computing_\">Ngitung…</string>\n    <string name=\"close\">Tudhung</string>\n    <string name=\"try_again\">Jajal Maneh</string>\n    <string name=\"retry\">Baleni</string>\n    <string name=\"clear_history\">Busek riwayat</string>\n    <string name=\"nothing_found\">Mboten dipunpanggihaken</string>\n    <string name=\"history_is_empty\">Ora ana babad maneh</string>\n    <string name=\"read\">Moco</string>\n    <string name=\"you_have_not_favourites_yet\">Durung ana kang disenengi</string>\n    <string name=\"add_to_favourites\">favoritkan puniki</string>\n    <string name=\"add_new_category\">Kategori anyar</string>\n    <string name=\"add\">Nambah</string>\n    <string name=\"save\">Dekek</string>\n    <string name=\"share\">Edumaken</string>\n    <string name=\"create_shortcut\">Damel pintasan</string>\n    <string name=\"share_s\">Edumaken %s</string>\n    <string name=\"search\">Luru</string>\n    <string name=\"search_manga\">Golek komik</string>\n    <string name=\"manga_downloading_\">Ngunduh…</string>\n    <string name=\"processing_\">Mroses…</string>\n    <string name=\"download_complete\">Diundhuh</string>\n    <string name=\"downloads\">Unggah</string>\n    <string name=\"by_name\">Jeneng</string>\n    <string name=\"popular\">Kondhang</string>\n    <string name=\"updated\">Dianyari</string>\n    <string name=\"newest\">Paling anyar</string>\n    <string name=\"by_rating\">Bijine</string>\n    <string name=\"sort_order\">Urutaken</string>\n    <string name=\"filter\">Milah</string>\n    <string name=\"theme\">Intining</string>\n    <string name=\"light\">Padhang</string>\n    <string name=\"dark\">Peteng</string>\n    <string name=\"follow_system\">Dereki sistem</string>\n    <string name=\"pages\">Kaca</string>\n    <string name=\"standard\">Pajeging</string>\n    <string name=\"read_mode\">Mode waos</string>\n    <string name=\"test_parser\">Mriksa asal</string>\n    <string name=\"telegram_integration\">Panyawijining Telegram</string>\n    <string name=\"clear_pages_cache\">Resiki singgahan platar</string>\n    <string name=\"clear\">Resiki</string>\n    <string name=\"remove\">Busek</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" dibusek saka panyimpenan</string>\n    <string name=\"save_page\">Dekek Plataran</string>\n    <string name=\"page_saved\">Plataran dipundekek</string>\n    <string name=\"pages_saved\">Plataran dipundekek</string>\n    <string name=\"share_image\">Edumaken citro</string>\n    <string name=\"_import\">Pundhut barang jawi</string>\n    <string name=\"delete\">Busek</string>\n    <string name=\"operation_not_supported\">tindakan menika mboten dipunsengkuyung</string>\n    <string name=\"text_file_not_supported\">Milih antawis berkas zip utawi cbz.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-kk/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d минут бұрын</item>\n        <item quantity=\"other\">%1$d минут бұрын</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d елемент</item>\n        <item quantity=\"other\">%1$d елемент</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d тарау</item>\n        <item quantity=\"other\">%1$d тарау</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d жаңа тарау</item>\n        <item quantity=\"other\">%1$d жаңа тарау</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d ай бұрын</item>\n        <item quantity=\"other\">%1$d ай бұрын</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d күн бұрын</item>\n        <item quantity=\"other\">%1$d күн бұрын</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d сағат бұрын</item>\n        <item quantity=\"other\">%1$d сағат бұрын</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d сағат</item>\n        <item quantity=\"other\">Нөл , екі, үш, төрт, бес, алты, жеті, сегіз, тоғыз, он</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d минут</item>\n        <item quantity=\"other\">%1$d минут</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-kk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"text_search_holder_secondary\">Сұрауыңызды қайталап көріңіз.</string>\n    <string name=\"text_history_holder_secondary\">«Шолу» бөлімінен не оқуға болатынын табыңыз.</string>\n    <string name=\"text_local_holder_secondary\">Файлдан импорттаңыз не онлайн каталогтан бірдеңе сақтаңыз.</string>\n    <string name=\"other_storage\">Басқа бума</string>\n    <string name=\"new_version_s\">Жаңа нұсқа: %s</string>\n    <string name=\"size_s\">Өлшемі: %s</string>\n    <string name=\"clear_updates_feed\">Жаңарту легін тазалау</string>\n    <string name=\"updates_feed_cleared\">Тазаланды</string>\n    <string name=\"rotate_screen\">Экранды айналдыру</string>\n    <string name=\"update\">Жаңарту</string>\n    <string name=\"feed_will_update_soon\">Жаңарту жақын арада басталады</string>\n    <string name=\"new_chapters\">Жаңа тараулар</string>\n    <string name=\"local_storage\">Құрылғыда</string>\n    <string name=\"history\">Тарих</string>\n    <string name=\"chapters\">Тарау</string>\n    <string name=\"detailed_list\">Егжей-тегжейлі тізім</string>\n    <string name=\"list_mode\">Тізім түрі</string>\n    <string name=\"remote_sources\">Манга дереккөзі</string>\n    <string name=\"loading_\">Жүктеліп жатыр…</string>\n    <string name=\"computing_\">Есептеу…</string>\n    <string name=\"favourites\">Таңдаулы</string>\n    <string name=\"network_error\">Желі қатесі</string>\n    <string name=\"details\">Дерек</string>\n    <string name=\"grid\">Кесте</string>\n    <string name=\"try_again\">Қайта көру</string>\n    <string name=\"clear_history\">Тарихты тазалау</string>\n    <string name=\"read\">Оқу</string>\n    <string name=\"add_new_category\">Жаңа санат</string>\n    <string name=\"add\">Қосу</string>\n    <string name=\"save\">Сақтау</string>\n    <string name=\"share\">Бөлісу</string>\n    <string name=\"create_shortcut\">Таңбаша жасау</string>\n    <string name=\"share_s\">%s бөлісу</string>\n    <string name=\"search\">Іздеу</string>\n    <string name=\"manga_downloading_\">Жүктеліп жатыр…</string>\n    <string name=\"processing_\">Үдерісте…</string>\n    <string name=\"download_complete\">Жүктелді</string>\n    <string name=\"by_name\">Атауы</string>\n    <string name=\"popular\">Танымал</string>\n    <string name=\"updated\">Жаңарған</string>\n    <string name=\"newest\">Жаңа</string>\n    <string name=\"by_rating\">Рейтиң</string>\n    <string name=\"sort_order\">Сұрыптау реті</string>\n    <string name=\"filter\">Сүзгі</string>\n    <string name=\"theme\">Кейіп</string>\n    <string name=\"dark\">Қараңғы</string>\n    <string name=\"light\">Ақшыл</string>\n    <string name=\"follow_system\">Жүйедегідей</string>\n    <string name=\"pages\">Беттер</string>\n    <string name=\"clear\">Тазалау</string>\n    <string name=\"remove\">Жою</string>\n    <string name=\"_s_deleted_from_local_storage\">«%s» құрылғыдан жойылды</string>\n    <string name=\"save_page\">Бетті сақтау</string>\n    <string name=\"page_saved\">Бет сақталды</string>\n    <string name=\"share_image\">Суретті бөлісу</string>\n    <string name=\"_import\">Импорт</string>\n    <string name=\"no_description\">Сипаттамасы жоқ</string>\n    <string name=\"text_file_sizes\">Б|кБ|МБ|ГБ|ТБ</string>\n    <string name=\"standard\">Стандарт</string>\n    <string name=\"webtoon\">Уебтүн</string>\n    <string name=\"grid_size\">Кесте өлшемі</string>\n    <string name=\"search_on_s\">%s-те іздеу</string>\n    <string name=\"delete_manga\">Манганы жою</string>\n    <string name=\"reader_settings\">Оқыманы баптау</string>\n    <string name=\"switch_pages\">Парақтау</string>\n    <string name=\"clear_thumbs_cache\">Нобай кәшін тазалау</string>\n    <string name=\"search_history_cleared\">Тазаланды</string>\n    <string name=\"internal_storage\">Ішкі жады</string>\n    <string name=\"external_storage\">Сыртқы жады</string>\n    <string name=\"domain\">Дәмейін</string>\n    <string name=\"app_update_available\">Жаңа нұсқа қолжетімді</string>\n    <string name=\"open_in_browser\">Уеб браузер арқылы ашу</string>\n    <string name=\"notifications\">Мәлімдеме</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d ішіндегі %2$d қосулы</string>\n    <string name=\"download\">Жүктеп алу</string>\n    <string name=\"notifications_settings\">Мәлімдемені баптау</string>\n    <string name=\"notification_sound\">Мәлімдеме дыбысы</string>\n    <string name=\"light_indicator\">LED индикаттау</string>\n    <string name=\"vibration\">Діріл</string>\n    <string name=\"favourites_categories\">Таңдаулы санаты</string>\n    <string name=\"remove_category\">Жою</string>\n    <string name=\"text_empty_holder_primary\">Мына жер бос екен…</string>\n    <string name=\"text_history_holder_primary\">Бұ жерде оқығаныңыз тұрады</string>\n    <string name=\"text_local_holder_primary\">Алдымен бірдеңе сақтаңыз</string>\n    <string name=\"manga_shelf\">Сөре</string>\n    <string name=\"recent_manga\">Соңғы</string>\n    <string name=\"pages_animation\">Бет анимасасы</string>\n    <string name=\"manga_save_location\">Жүктеу бумасы</string>\n    <string name=\"not_available\">Қолжетімсіз</string>\n    <string name=\"cannot_find_available_storage\">Қолжетімді бума жоқ</string>\n    <string name=\"done\">Дайын</string>\n    <string name=\"all_favourites\">Таңдаулылар</string>\n    <string name=\"favourites_category_empty\">Бос санат</string>\n    <string name=\"read_later\">Кейінірек оқу</string>\n    <string name=\"updates\">Жаңартулар</string>\n    <string name=\"text_feed_holder\">Оқып жүргеніңіздің жаңа тараулары осы жерде болады</string>\n    <string name=\"search_results\">Іздеу нәтижесі</string>\n    <string name=\"list\">Тізім</string>\n    <string name=\"error_occurred\">Қате орын алды</string>\n    <string name=\"settings\">Баптау</string>\n    <string name=\"chapter_d_of_d\">%2$d ішіндегі %1$d тарау</string>\n    <string name=\"close\">Жабу</string>\n    <string name=\"nothing_found\">Түк табылмады</string>\n    <string name=\"you_have_not_favourites_yet\">Әзірге таңдаулы жоқ</string>\n    <string name=\"history_is_empty\">Әзірге тарих жоқ</string>\n    <string name=\"search_manga\">Манга іздеу</string>\n    <string name=\"add_to_favourites\">Таңдаулыға</string>\n    <string name=\"downloads\">Жүктелген</string>\n    <string name=\"operation_not_supported\">Қолжетімсіз амал</string>\n    <string name=\"delete\">Жою</string>\n    <string name=\"text_file_not_supported\">CBZ не ZIP пішімде таңдаңыз.</string>\n    <string name=\"clear_pages_cache\">Бет кәшін тазалау</string>\n    <string name=\"read_mode\">Оқу режімі</string>\n    <string name=\"text_delete_local_manga\">“%s” құрылғыдан біржолата жоямысыз?</string>\n    <string name=\"_continue\">Жалғастыру</string>\n    <string name=\"error\">Қате</string>\n    <string name=\"clear_search_history\">Іздеу тарихын тазалау</string>\n    <string name=\"track_sources\">Жаңартуларды қарау</string>\n    <string name=\"wrong_password\">Қате құпиясөз</string>\n    <string name=\"protect_application\">Қолданбаны қорғау</string>\n    <string name=\"passwords_mismatch\">Құпиясөз бірдей емес</string>\n    <string name=\"app_version\">%s нұсқа</string>\n    <string name=\"check_for_updates\">Жаңартуды тексеру</string>\n    <string name=\"right_to_left\">Оңнан солға</string>\n    <string name=\"zoom_mode_fit_height\">Биіктігіне қарай қою</string>\n    <string name=\"zoom_mode_fit_width\">Еніне қарай қою</string>\n    <string name=\"zoom_mode_keep_start\">Өзгертпеу</string>\n    <string name=\"black_dark_theme\">Қара</string>\n    <string name=\"black_dark_theme_summary\">AMOLED экранда азырақ қуат жейді</string>\n    <string name=\"create_backup\">Сақтық көшірме жасау</string>\n    <string name=\"restore_backup\">Сақтық көшірмеден қалыпқа келтіру</string>\n    <string name=\"file_not_found\">Файл табылмады</string>\n    <string name=\"data_restored_success\">Түгел дерек қалыпқа келді</string>\n    <string name=\"backup_information\">Таңдаулы мен тарихтың сақтық көшірмесін жасап, оны қалпына келтіре аласыз</string>\n    <string name=\"just_now\">Жаңа ғана</string>\n    <string name=\"yesterday\">Кеше</string>\n    <string name=\"long_ago\">Бұрын</string>\n    <string name=\"group\">Топтау</string>\n    <string name=\"today\">Бүгін</string>\n    <string name=\"tap_to_try_again\">Қайталап көру</string>\n    <string name=\"reader_mode_hint\">Таңдалған пішімдеу осы маңга үшін сақталады</string>\n    <string name=\"silent\">Дыбыссыз</string>\n    <string name=\"captcha_required\">CAPTCHA өтіңіз</string>\n    <string name=\"captcha_solve\">Өту</string>\n    <string name=\"clear_cookies\">Кукиді тазалау</string>\n    <string name=\"cookies_cleared\">Куки файлдары жойылды</string>\n    <string name=\"reverse\">Керісінше</string>\n    <string name=\"sign_in\">Кіру</string>\n    <string name=\"auth_required\">Бұны көру үшін тіркелгіге кіріңіз</string>\n    <string name=\"default_s\">Әдепкі: %s</string>\n    <string name=\"chapter_is_missing\">Тарау жоқ</string>\n    <string name=\"about_app_translation_summary\">Қолданбаны аудару</string>\n    <string name=\"about_app_translation\">Аудару</string>\n    <string name=\"auth_not_supported_by\">%s кіру қолжетімсіз</string>\n    <string name=\"state_finished\">Аяқталған</string>\n    <string name=\"text_clear_updates_feed_prompt\">Жаңарту тарихын толықтау тазартайық па\\?</string>\n    <string name=\"no_update_available\">Жаңарту жоқ</string>\n    <string name=\"backup_restore\">Сақтық көшірме мен қалпына келтіру</string>\n    <string name=\"dont_check\">Тексермеу</string>\n    <string name=\"enter_password\">Құпиясөзді енгізіңіз</string>\n    <string name=\"about\">Қолданба туралы</string>\n    <string name=\"create_category\">Жаңа санат</string>\n    <string name=\"data_restored\">Қалыпқа келді</string>\n    <string name=\"preparing_\">Дайындау…</string>\n    <string name=\"data_restored_with_errors\">Дерек қалыпқа келсе де, сәл қате шығып қалды</string>\n    <string name=\"exclude_nsfw_from_history\">ҰЯТСЫЗ манганы тарихта көрсетпеу</string>\n    <string name=\"state_ongoing\">Шығып жатыр</string>\n    <string name=\"show_pages_numbers\">Беттерді нөмірлеу</string>\n    <string name=\"system_default\">Әдепкі</string>\n    <string name=\"suggestions_summary\">Ұнайды ма деген маңганы ұсыну</string>\n    <string name=\"screenshots_block_nsfw\">ҰЯТСЫЗ үшін бөгеу</string>\n    <string name=\"suggestions_enable\">Ұсынымды қосу</string>\n    <string name=\"check_for_new_chapters\">Жаңа тарау іздеу</string>\n    <string name=\"auth_complete\">Сәтті тіркелдіңіз</string>\n    <string name=\"text_clear_cookies_prompt\">Түгел дереккөзден шығып кетесіз</string>\n    <string name=\"genres\">Түрлер</string>\n    <string name=\"screenshots_policy\">Скриншот саясаты</string>\n    <string name=\"suggestions\">Ұсыным</string>\n    <string name=\"screenshots_allow\">Рұқсат беру</string>\n    <string name=\"screenshots_block_all\">Әрқашан бөгеу</string>\n    <string name=\"text_suggestion_holder\">Жеке ұсыныс алу үшін маңга оқып бастаңыз</string>\n    <string name=\"disabled\">Өшірулі</string>\n    <string name=\"exclude_nsfw_from_suggestions\">ҰЯТСЫЗ маңга ұсынбау</string>\n    <string name=\"enabled\">Қосулы</string>\n    <string name=\"protect_application_summary\">Қолданбаны қосқанда құпиясөз сұрау</string>\n    <string name=\"repeat_password\">Құпиясөзді қайталаңыз</string>\n    <string name=\"zoom_mode_fit_center\">Ортаға қою</string>\n    <string name=\"scale_mode\">Өлшеу режімі</string>\n    <string name=\"text_clear_search_history_prompt\">Соңғы іздеу тарихын толықтай жоямыз ба\\?</string>\n    <string name=\"welcome\">Қош келдіңіз</string>\n    <string name=\"backup_saved\">Сақтау көшірмесі дайын</string>\n    <string name=\"tracker_warning\">Кейбір құрылғылардың жүйесі бөлек, одан аялық тапсырмалар бұзылуы мүмкін.</string>\n    <string name=\"read_more\">Толығырақ оқу</string>\n    <string name=\"queued\">Кезекте</string>\n    <string name=\"next\">Келесі</string>\n    <string name=\"protect_application_subtitle\">Қолданбаға кіру үшін құпиясөз енгізіңіз</string>\n    <string name=\"confirm\">Растау</string>\n    <string name=\"password_length_hint\">Құпиясөзде 4, не одан көп таңба болу керек</string>\n    <string name=\"status_re_reading\">Қайталап оқып жатырмын</string>\n    <string name=\"detect_reader_mode\">Оқу режімін өздігінен анықтау</string>\n    <string name=\"tracking\">Бақылау</string>\n    <string name=\"email_enter_hint\">Жалғастыру үшін email поштаңызды жазыңыз</string>\n    <string name=\"disable_all\">Бәрін өшіру</string>\n    <string name=\"clear_feed\">Лекті тазалау</string>\n    <string name=\"chapters_empty\">Бұл маңгада тарау жоқ</string>\n    <string name=\"clear_all_history\">Түгел тарихты тазалау</string>\n    <string name=\"preload_pages\">Беттерді алдынала жүктей беру</string>\n    <string name=\"data_deletion\">Деректі жою</string>\n    <string name=\"show_reading_indicators\">Оқу прогрессін көрсету</string>\n    <string name=\"local_manga_processing\">Сақталған маңгаңыз үдерісте</string>\n    <string name=\"show_notification_new_chapters_off\">Мәлімдеме алмайсыз, бірақ жаңа тараулар тізімде көрсетіліп тұрады</string>\n    <string name=\"show_notification_new_chapters_on\">Оқып жүрген маңгаңыздың жаңаруы туралы мәлімдеме алып тұрасыз</string>\n    <string name=\"status_reading\">Оқып жүрмін</string>\n    <string name=\"various_languages\">Түрлі тіл</string>\n    <string name=\"removal_completed\">Жойылды</string>\n    <string name=\"edit\">Өңдеу</string>\n    <string name=\"removed_from_history\">Тарихтан жойылды</string>\n    <string name=\"crash_text\">Ақау пайда болды. Жөндеу үшін әзірлеушіге шағым жіберіңізші.</string>\n    <string name=\"detect_reader_mode_summary\">Маңга уебтүн бе екенін өздігінен анықтап береді</string>\n    <string name=\"appwidget_recent_description\">Соңғы оқыған маңгаңыз</string>\n    <string name=\"appearance\">Кейіп</string>\n    <string name=\"bookmark_remove\">Бетбелгіні алып тастау</string>\n    <string name=\"disable_battery_optimization_summary\">Аяда жаңарту іздеуді көмектеседі</string>\n    <string name=\"status_on_hold\">Кейінге қалған</string>\n    <string name=\"last_2_hours\">Соңғы 2 сағат</string>\n    <string name=\"name\">Атау</string>\n    <string name=\"edit_category\">Санатты өңдеу</string>\n    <string name=\"bookmark_removed\">Бетбелгі жойылды</string>\n    <string name=\"select_range\">Ауқымын таңдау</string>\n    <string name=\"suggestions_excluded_genres_summary\">Көргіңіз келмейтін жанрды таңдаңыз</string>\n    <string name=\"only_using_wifi\">Тек Wi-Fi арқылы</string>\n    <string name=\"back\">Кері</string>\n    <string name=\"dns_over_https\">HTTPS үстінен DNS</string>\n    <string name=\"sync_title\">Дерегіңізді үйлестіріңіз</string>\n    <string name=\"appwidget_shelf_description\">Таңдаулы маңгаңыз</string>\n    <string name=\"send\">Жіберу</string>\n    <string name=\"bookmark_add\">Бетбелгілеу</string>\n    <string name=\"new_sources_text\">Жаңа маңга дереккөзі қолжетімді</string>\n    <string name=\"check_new_chapters_title\">Жаңа тарау барын тексеріп, сол туралы мәлімдеу</string>\n    <string name=\"logged_in_as\">%s деп тіркелгенсіз</string>\n    <string name=\"suggestions_info\">Деректің бәрі тек осы құрылғы аясында қаралып, ешқайда жіберілмейді.</string>\n    <string name=\"history_cleared\">Тарих тазарды</string>\n    <string name=\"undo\">Қайтару</string>\n    <string name=\"download_slowdown_summary\">IP мекенжайыңыздың бұғатқа түспеуіне көмектеседі</string>\n    <string name=\"text_delete_local_manga_batch\">Таңдалғанды құрылғыдан жоямысыз\\?</string>\n    <string name=\"report\">Шағым</string>\n    <string name=\"download_slowdown\">Жүктеп алуды баяулату</string>\n    <string name=\"bookmark_added\">Бетбелгі қойылды</string>\n    <string name=\"sync\">Үйлестіру</string>\n    <string name=\"search_chapters\">Тарау табу</string>\n    <string name=\"always\">Әрқашан</string>\n    <string name=\"suggestions_excluded_genres\">Жанрды шектеу</string>\n    <string name=\"canceled\">Доғарылды</string>\n    <string name=\"account_already_exists\">Бұндай тіркелгі бос емес</string>\n    <string name=\"hide\">Жасыру</string>\n    <string name=\"exclude_nsfw_from_history_summary\">ҰЯТСЫЗ деген маңганы оқығаныңыз тарихыңызда сақталмайды</string>\n    <string name=\"show_reading_indicators_summary\">Таңдаулы мен тарихта оқылған туынды пайызын көрсету</string>\n    <string name=\"use_fingerprint\">Қолжетімді болса биометрика қолдану</string>\n    <string name=\"onboard_text\">Маңганы қай тілде оқығыңыз келетінін таңдаңыз. Кейінірек баптауда өзгертіп ала аласыз.</string>\n    <string name=\"suggestions_updating\">Ұсынысты жаңарту</string>\n    <string name=\"percent_string_pattern\">%%%1$s</string>\n    <string name=\"chapters_will_removed_background\">Тараулар аяда жойылады</string>\n    <string name=\"default_mode\">Әдепкі режім</string>\n    <string name=\"logout\">Шығу</string>\n    <string name=\"status_completed\">Аяқталған</string>\n    <string name=\"reset_filter\">Сүзгіні тазарту</string>\n    <string name=\"status_dropped\">Тастап кеткен</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"notifications_enable\">Мәлімдеме қосу</string>\n    <string name=\"never\">Ешқашан</string>\n    <string name=\"disable_battery_optimization\">Қуат оңтайлығын өшіру</string>\n    <string name=\"status_planned\">Жоспарланған</string>\n    <string name=\"bookmarks\">Бетбелгі</string>\n    <string name=\"show_all\">Бәрін көрсету</string>\n    <string name=\"empty_favourite_categories\">Таңдаулы санатыңыз жоқ</string>\n    <string name=\"invalid_domain_message\">Қате дәмейін</string>\n    <string name=\"languages\">Тілдер</string>\n    <string name=\"zoom_in\">Ұлғайту</string>\n    <string name=\"captcha_required_summary\">%s дұрыс істеуі үшін captcha өтіңіз</string>\n    <string name=\"download_option_all_unread\">Түгел оқылмаған тарау</string>\n    <string name=\"frequency_every_day\">Күнде</string>\n    <string name=\"download_started\">Жүктеп алу басталды</string>\n    <string name=\"categories\">Санат</string>\n    <string name=\"progress\">Прогресс</string>\n    <string name=\"cancel_all\">Бәрін доғару</string>\n    <string name=\"sync_host_description\">Өзіңіздің үйлестіру сербірін я әдепкісін таңдай аласыз. Не істеп жатқаныңызды түсінбеңіз баспаңыз.</string>\n    <string name=\"error_corrupted_file\">Қайта жарамсыз дерек қосылды яки файыл сынған</string>\n    <string name=\"pick_custom_directory\">Жеке каталогты таңдау</string>\n    <string name=\"no_chapters\">Тарау жоқ</string>\n    <string name=\"list_options\">Тізімді реттеу</string>\n    <string name=\"related_manga_summary\">Байланыс маңга тізімін көрсету. Кейде тізім қате я мүлде болмауы мүмкін</string>\n    <string name=\"remove_completed_downloads_confirm\">Жүктеу тарихыңыз тазарады</string>\n    <string name=\"theme_name_dynamic\">Динамикалық</string>\n    <string name=\"reader_zoom_buttons_summary\">Ұлғайту батырмасын астыңғы оң жақта көрсету я көрсетпеу</string>\n    <string name=\"pages_cache\">Бет кәші</string>\n    <string name=\"reset\">Арылту</string>\n    <string name=\"tracker_wifi_only_summary\">Шектеулі желі қосылымы болса жаңа тарау қолжетімдігін тексермеу</string>\n    <string name=\"order_added\">Қосылды</string>\n    <string name=\"enable_logging\">Логтауды қосу</string>\n    <string name=\"on_device\">Құрылғыда</string>\n    <string name=\"password\">Құпиясөз</string>\n    <string name=\"download_option_whole_manga\">Маңганы толықтай</string>\n    <string name=\"settings_apply_restart_required\">Өзгерту іске қосылуы үшін қолданбаны өшіріп қосыңыз</string>\n    <string name=\"source_disabled\">Дереккөз өшіп жатыр</string>\n    <string name=\"backup_frequency\">Сақтық көшірмесінің жиілігі</string>\n    <string name=\"data_and_privacy\">Дерек пен құпиялық</string>\n    <string name=\"clear_cookies_summary\">Қате болса көмектесе алады. Түгел тіркелгінің күші жойылады</string>\n    <string name=\"enable_logging_summary\">Кей әрекетті түзеуге сақтау қою. Не екенін білмесеңіз қоспаңыз</string>\n    <string name=\"clear_source_cookies_summary\">Осы дәмейіннің кукиін тазалау. Көп жағдайда тіркелгіден шығылып кетеді</string>\n    <string name=\"history_shortcuts\">Соңғы маңганың таңбашасын көрсету</string>\n    <string name=\"downloads_wifi_only_summary\">Ұялы желіге көшкенде жүктеп алуды тоқтату</string>\n    <string name=\"suggest_new_sources\">Қолданбаны жаңартқан соң жаңа дереккөз ұсыну</string>\n    <string name=\"import_completed\">Импорт аяқталды</string>\n    <string name=\"user_agent\">UserAgent басы</string>\n    <string name=\"ignore_ssl_errors\">SSL қатеге мән бермеу</string>\n    <string name=\"reader_info_bar\">Оқымада ақпар тақтасын көрсету</string>\n    <string name=\"periodic_backups_enable\">Сақтық көшірмесін кезең-кезеңімен жасауды қосу</string>\n    <string name=\"server_address\">Сербір адресі</string>\n    <string name=\"moved_to_top\">Үстіге жылжыды</string>\n    <string name=\"explore\">Қарау</string>\n    <string name=\"find_similar\">Ұқсасын табу</string>\n    <string name=\"storage_usage\">Жадты қолдану</string>\n    <string name=\"data_not_restored_text\">Дұрыс сақтық көшірме файылын таңдағаныңызды тексеріңіз</string>\n    <string name=\"theme_name_sakura\">Сакура</string>\n    <string name=\"unknown\">Белгісіз</string>\n    <string name=\"in_progress\">Оқылып жатыр</string>\n    <string name=\"download_option_manual_selection\">Тарауды таңдап шығу</string>\n    <string name=\"enhanced_colors_summary\">Түс ыдырауын азайтады, бірақ өнімділікке әсер ете алады</string>\n    <string name=\"importing_manga\">Маңга импорттау</string>\n    <string name=\"pause\">Үзіліс</string>\n    <string name=\"clear_new_chapters_counters\">Жаңа тарау ақпарын да жою</string>\n    <string name=\"nothing_here\">Мұнда түк жоқ</string>\n    <string name=\"remove_completed\">Дайынын жою</string>\n    <string name=\"items_limit_exceeded\">Елемент сыймайды</string>\n    <string name=\"frequency_every_2_days\">Екі күн сайын</string>\n    <string name=\"suggestions_notifications_summary\">Анда-санда мәлімдеме арқылы маңга ұсынып тұру</string>\n    <string name=\"invalid_value_message\">Жарамсыз өлшем</string>\n    <string name=\"downloads_cancelled\">Жүктеп алу доғарылды</string>\n    <string name=\"webtoon_zoom\">Уебтүнді ұлғайту</string>\n    <string name=\"theme_name_miku\">Мику</string>\n    <string name=\"data_not_restored\">Дерек қалыпқа келмеді</string>\n    <string name=\"directories\">Каталог</string>\n    <string name=\"local_manga_directories\">Жергілікті маңга тізімі</string>\n    <string name=\"manage_categories\">Санатты реттеу</string>\n    <string name=\"scrobbling_empty_hint\">Оқу прогрессін бақылау үшін маңга ақпар экранындағы «Мәзірге» өтіп, «Бақылау» батырмасын басыңыз.</string>\n    <string name=\"color_light\">Жарық</string>\n    <string name=\"web_view_unavailable\">WebView қолжетімсіз: WebView провайдері орнатылғанын тексеріп көріңіз</string>\n    <string name=\"port\">Порт</string>\n    <string name=\"not_found_404\">Контент табылмады, жойылған-мыс</string>\n    <string name=\"got_it\">Ұқтым</string>\n    <string name=\"type\">Түрі</string>\n    <string name=\"search_hint\">Маңга атын, жанрын я дереккөз атауын жазыңыз</string>\n    <string name=\"frequency_once_per_week\">Апта сайын</string>\n    <string name=\"description\">Сипаттама</string>\n    <string name=\"periodic_backups\">Сақтық көшірмесін кезең-кезеңімен жасау</string>\n    <string name=\"reader_zoom_buttons\">Ұлғайту батырмасын көрсету</string>\n    <string name=\"sources_reorder_tip\">Ретін өзгерту үшін елементті басып тұрыңыз</string>\n    <string name=\"resume\">Жалғастыру</string>\n    <string name=\"server_error\">Сербір қуып тұр (%1$d). Сәлден соң қайталап көріңіз</string>\n    <string name=\"images_proxy_title\">Суретті оңтайлау проксиі</string>\n    <string name=\"network_unavailable_hint\">Маңганы онлайн оқу үшін Wi-Fi-ды я ұялы желіні қосыңыз</string>\n    <string name=\"no_manga_sources\">Маңға дереккөзі жоқ</string>\n    <string name=\"username\">Қолданушы аты</string>\n    <string name=\"frequency_twice_per_month\">Екі апта сайын</string>\n    <string name=\"prefetch_content\">Алдын-ала жүктей беру</string>\n    <string name=\"main_screen_sections\">Басты экран бөлімдері</string>\n    <string name=\"confirm_exit\">Шығу үшін тағы бір рет басыңыз</string>\n    <string name=\"advanced\">Қосымша баптау</string>\n    <string name=\"sync_settings\">Үйлестіру баптауы</string>\n    <string name=\"online_variant\">Онлайн нұсқа</string>\n    <string name=\"download_option_all_unread_b\">Түгел оқылмаған тарау (%s)</string>\n    <string name=\"authorization_optional\">Кіру (міндетті емес)</string>\n    <string name=\"color_dark\">Күңгірт</string>\n    <string name=\"other_cache\">Басқа кәш</string>\n    <string name=\"show_suspicious_content\">Күмәнді контентті көрсету</string>\n    <string name=\"reader_info_bar_summary\">Экранның үстінгі жағында уақыт пен оқу прогрессін көрсету</string>\n    <string name=\"allow_unstable_updates_summary\">Тұрақсыз нұсқа туралы мәлімдеме алу</string>\n    <string name=\"comics_archive_import_description\">Бір не одан көп .cbz я .zip файлын таңдай аласыз, әр файыл бөлек маңга болып анықталады.</string>\n    <string name=\"downloads_paused\">Жүктеп алу тоқтап қалды</string>\n    <string name=\"too_many_requests_message\">Тым көп сұрату. Біраздан соң қайталап көріңіз</string>\n    <string name=\"downloads_wifi_only\">Wi-Fi арқылы ғана жүктеу</string>\n    <string name=\"cancel_all_downloads_confirm\">Жүктеліп жатқанның бәрі жойылып, жартылай жүктелгендер жоғалып кетеді</string>\n    <string name=\"by_relevance\">Өзектілігі</string>\n    <string name=\"related_manga\">Ұқсас маңга</string>\n    <string name=\"discard\">Сақтамау</string>\n    <string name=\"saved_manga\">Сақталған маңга</string>\n    <string name=\"manga_error_description_pattern\">Қате мәліметі:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Дереккөзде бар ма екенін тексеру үшін &lt;a href=%2$s&gt;маңганы уеб браузер арқылы&lt;/a&gt; ашып көріңіз&lt;br&gt;2. &lt;a href=kotatsu://about&gt;Kotatsu-ның ең соңғы нұсқасын&lt;/a&gt;&lt;br&gt;3 қолданып отырсыз ба, тексеріп көріңіз. Мүмкін болса: әзірлеушіге қате туралы шағып жіберіңіз.</string>\n    <string name=\"state_abandoned\">Тасталған</string>\n    <string name=\"history_shortcuts_summary\">Қолданба таңбасын ұзақ басып тұрғанда соңғы маңганы қолжетімді ету</string>\n    <string name=\"automatic_scroll\">Өздігінен парақтау</string>\n    <string name=\"download_option_first_n_chapters\">Бірінші %s</string>\n    <string name=\"keep_screen_on\">Экранды сөндірмеу</string>\n    <string name=\"paused\">Тоқтап тұр</string>\n    <string name=\"text_downloads_list_holder\">Жүктеп алғаныңыз жоқ</string>\n    <string name=\"color_correction\">Түс реттеу</string>\n    <string name=\"invalid_port_number\">Порттың нөмірі қате</string>\n    <string name=\"suggestions_wifi_only_summary\">Шектеулі желі қосылымы болса ұсынысты жаңартпау</string>\n    <string name=\"webtoon_zoom_summary\">Уебтүн режімінде ұлғайту ымына рұқсат ету</string>\n    <string name=\"categories_delete_confirm\">Таңдаулы санатты шынымен жойғыңыз келе ме? \\nІшіндегі бар маңга жойылады, сосын оны қайтарып ала алмайсыз.</string>\n    <string name=\"frequency_once_per_month\">Ай сайын</string>\n    <string name=\"reader_info_pattern\">%1$d/%2$d-тарау %3$d/%4$d-бет</string>\n    <string name=\"contrast\">Көреғарлық</string>\n    <string name=\"network\">Желі</string>\n    <string name=\"reader_slider\">Парақтау жүгірткісін көрсету</string>\n    <string name=\"no_manga_sources_text\">Онлайн оқу үшін маңга дереккөзін қосыңыз</string>\n    <string name=\"downloaded\">Жүктеп алынған</string>\n    <string name=\"options\">Басқа</string>\n    <string name=\"services\">Қызмет</string>\n    <string name=\"suggestions_enable_prompt\">Сізге арналған маңга ұсынысын алғыңыз келе ме\\?</string>\n    <string name=\"exit_confirmation\">Шығуды растау</string>\n    <string name=\"comics_archive\">Комикс мұрағаты</string>\n    <string name=\"more\">Тағы</string>\n    <string name=\"theme_name_asuka\">Асука</string>\n    <string name=\"address\">Адресі</string>\n    <string name=\"import_will_start_soon\">Импорт қазір басталады</string>\n    <string name=\"compact\">Жинақы</string>\n    <string name=\"enhanced_colors\">32-биттік түс режімі</string>\n    <string name=\"folder_with_images_import_description\">Мұрағат я сурет каталогын таңдай аласыз. Әр мұрағат (яки ішкі каталог) бір тарау деп анықталады.</string>\n    <string name=\"reorder\">Ретін өзгерту</string>\n    <string name=\"background\">Ая</string>\n    <string name=\"feed\">Лек</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"downloads_removed\">Жүктеп алғаныңыз жойылды</string>\n    <string name=\"no_access_to_file\">Бұл файлға я каталогқа рұқсатыңыз жоқ</string>\n    <string name=\"mark_as_current\">Қазіргі деп белгілеу</string>\n    <string name=\"random\">Кездейсоқ</string>\n    <string name=\"mirror_switching\">Айнаны өздігінен таңдау</string>\n    <string name=\"restore_summary\">Бұған дейін жасалған сақтық көшірмесін қалпына келтіру</string>\n    <string name=\"show_pages_numbers_summary\">Бет нөмірін төменгі жақта көрсету</string>\n    <string name=\"show_in_grid_view\">Кесте қып көрсету</string>\n    <string name=\"zoom_out\">Кішірейту</string>\n    <string name=\"keep_screen_on_summary\">Маңга оқып отырғанда экранды сөндірмеу</string>\n    <string name=\"download_option_next_unread_n_chapters\">Келесі оқылмаған %s</string>\n    <string name=\"clear_network_cache\">Желі кәшін тазалау</string>\n    <string name=\"voice_search\">Дауыс іздеуі</string>\n    <string name=\"enable\">Қосу</string>\n    <string name=\"import_completed_hint\">Орын үнемдеу үшін түпнұсқа файлды құрылғыдан жойып тастай аласыз</string>\n    <string name=\"theme_name_rikka\">Рикка</string>\n    <string name=\"reader_control_ltr_summary\">Оң жақты я оң жақ батырманы басқан сайын бет ауысады</string>\n    <string name=\"language\">Тіл</string>\n    <string name=\"incognito_mode\">Инкогнито режімі</string>\n    <string name=\"no_bookmarks_summary\">Оқып жатқан маңгаға бетбелгі жасай аласыз</string>\n    <string name=\"images_procy_description\">Трафик қолдануды азайтып, сурет жүктеп алуды тездету үшін wsrv.nl қызметін қолданыңыз</string>\n    <string name=\"theme_name_mamimi\">Мамими</string>\n    <string name=\"manage\">Реттеу</string>\n    <string name=\"manga_list\">Маңга тізімі</string>\n    <string name=\"reader_control_ltr\">Оқыманы эргономді басқару</string>\n    <string name=\"disable_nsfw\">ҰЯТСЫЗ-ды өшіру</string>\n    <string name=\"last_successful_backup\">Соңғы сақтық көшірме: %s</string>\n    <string name=\"available\">Қолжетімді</string>\n    <string name=\"color_white\">Ақ</string>\n    <string name=\"network_unavailable\">Желі қолжетімсіз</string>\n    <string name=\"empty\">Бос</string>\n    <string name=\"downloads_resumed\">Жүктеп алу жалғасты</string>\n    <string name=\"text_unsaved_changes_prompt\">Өзгерісті сақтайсыз ба\\?</string>\n    <string name=\"folder_with_images\">Суреті бар бума</string>\n    <string name=\"to_top\">Үстіге</string>\n    <string name=\"show\">Көрсету</string>\n    <string name=\"show_on_shelf\">Сөреде көрсету</string>\n    <string name=\"allow_unstable_updates\">Тұрақсыз жаңартуды орнатуға рұқсат беру</string>\n    <string name=\"sync_auth_hint\">Жаңа тіркелгі аша аласыз я жаңасын жасай аласыз</string>\n    <string name=\"theme_name_kanade\">Канаде</string>\n    <string name=\"backups_output_directory\">Сақтық көшірмесінің каталогы</string>\n    <string name=\"invert_colors\">Түстерді терістету</string>\n    <string name=\"color_theme\">Түс схемасы</string>\n    <string name=\"brightness\">Ашықтық</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"exit_confirmation_summary\">Шығып кету үшін «Кері» батырмасын екі рет басыңыз</string>\n    <string name=\"bookmarks_removed\">Бетбелгілер жойылды</string>\n    <string name=\"theme_name_mion\">Мион</string>\n    <string name=\"mirror_switching_summary\">Дереккөз дәмейнінің қатесі пайда болып, айнасы қолжетімді болса, өздігінен соған ауыстыру</string>\n    <string name=\"no_bookmarks_yet\">Бетбелгі жоқ</string>\n    <string name=\"no_thanks\">Жоқ, рақмет</string>\n    <string name=\"suggest_new_sources_summary\">Қолданбаның жаңа нұсқасында пайда болған дереккөзді ұсыну</string>\n    <string name=\"share_logs\">Логты бөлісу</string>\n    <string name=\"speed\">Жылдамдық</string>\n    <string name=\"download_option_all_chapters\">%s аударған түгел тарау</string>\n    <string name=\"suggestion_manga\">Ұсыныс: %s</string>\n    <string name=\"color_black\">Қара</string>\n    <string name=\"removed_from_favourites\">Таңдаулыдан жойылды</string>\n    <string name=\"this_month\">Осы ай</string>\n    <string name=\"proxy\">Прокси</string>\n    <string name=\"error_no_space_left\">Жадта бос орын қалмады</string>\n    <string name=\"sources_catalog\">Дереккөз каталогы</string>\n    <string name=\"content_type_manga\">Маңга</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_hentai\">Хентай</string>\n    <string name=\"content_type_comics\">Комикс</string>\n    <string name=\"catalog\">Каталог</string>\n    <string name=\"manage_sources\">Маңга дереккөзі</string>\n    <string name=\"no_manga_sources_found\">Іздеуіңіз бойынша дереккөз табылмады</string>\n    <string name=\"lock_screen_rotation\">Экран бұрылуын бұғаттау</string>\n    <string name=\"manual\">Қолмен реттеу</string>\n    <string name=\"source_enabled\">Дереккөз қосылған-ды</string>\n    <string name=\"disable_nsfw_summary\">ҰЯТСЫЗ маңга дереккөзін өшіріп, тізімнен жасырып тастау</string>\n    <string name=\"no_manga_sources_catalog_text\">Әзірге мына жерде қолжетімді дереккөз жоқ. Жаңарту күтіңіз</string>\n    <string name=\"available_d\">Қолжетімді: %1$d</string>\n    <string name=\"content_type_other\">Басқа</string>\n    <string name=\"skip\">Өткізіп жіберу</string>\n    <string name=\"email_password_enter_hint\">Жалғастыру үшін email поштаңыз бен құпиясөзіңізді жазыңыз</string>\n    <string name=\"state_paused\">Кідіртілген</string>\n    <string name=\"reader_optimize\">Жадты жұмсауды үнемдеу (beta)</string>\n    <string name=\"reader_optimize_summary\">Жадта көп орын алмау үшін көрінбей тұрған беттердің сапасын азайту</string>\n    <string name=\"state\">Күйі</string>\n    <string name=\"error_search_not_supported\">Бұл дереккөзде іздеу мүмкіндігі жоқ</string>\n    <string name=\"error_multiple_states_not_supported\">Бұл дереккөзде бірнеше күй бойынша сүзуге болмайды</string>\n    <string name=\"grayscale\">Сұр реңк</string>\n    <string name=\"color_correction_apply_text\">Бұл баптауды түгеліне я тек осы маңгаға қолдануға болады. Түгеліне қолдансаңыз, кейбір дара баптау алдын ала белгілі болмайды.</string>\n    <string name=\"apply\">Қолдану</string>\n    <string name=\"globally\">Ауқымды</string>\n    <string name=\"this_manga\">Бұл маңга</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Бұл дереккөзде жанр бойынша және локал файлдар бойынша сүзуге болмайды</string>\n    <string name=\"error_multiple_genres_not_supported\">Бұл дереккөзде бірнеше жанр бойынша сүзуге болмайды</string>\n    <string name=\"genres_exclude\">Жанр алып тастау</string>\n    <string name=\"rating_safe\">Қауіпсіз</string>\n    <string name=\"chapters_grid_view\">Кесте түрі</string>\n    <string name=\"error_filter_states_genre_not_supported\">Бұл дереккөзде жанр мен күні бойынша сүзу жоқ</string>\n    <string name=\"genres_search_hint\">Жанр атауын тере бастаңыз</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Жүктеу дұрыс басталмай жүрсе көмектесе алады</string>\n    <string name=\"restore\">Қалпына келтіру</string>\n    <string name=\"backup_date_\">Сақтық көшірме күні: %s</string>\n    <string name=\"state_upcoming\">Жақында</string>\n    <string name=\"by_name_reverse\">Теріс ат</string>\n    <string name=\"content_rating\">Контент рейтиңі</string>\n    <string name=\"prev_chapter\">Алдыңғы тарау</string>\n    <string name=\"next_chapter\">Келесі тарау</string>\n    <string name=\"prev_page\">Алдыңғы бет</string>\n    <string name=\"switch_pages_volume_buttons\">Дыбыс батырмасын қосу</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Дыбыс батырмасы арқылы парақтау</string>\n    <string name=\"tap_action\">Басқандағы әрекет</string>\n    <string name=\"long_tap_action\">Басып тұрғандағы әрекет</string>\n    <string name=\"none\">Түк</string>\n    <string name=\"welcome_text\">Қандай дереккөз қосқыңыз келетінін таңдаңыз. Кейін баптап алуға болады</string>\n    <string name=\"next_page\">Келесі бет</string>\n    <string name=\"reader_actions\">Оқымадағы әрекет</string>\n    <string name=\"sync_auth\">Синхрондау тіркелгісіне кіру</string>\n    <string name=\"config_reset_confirm\">Әдепкі баптауға қайтайық па? Әрекетті қайтаруға болмайды.</string>\n    <string name=\"retry\">Қайталау</string>\n    <string name=\"pages_saved\">Беттер сақталды</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Сүзіп алғаныңызға сай маңга жоқ</string>\n    <string name=\"nsfw_16\">16+</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-km/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-km/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"favourites\">ចំណូលចិត្ត</string>\n    <string name=\"history\">ប្រវត្តិអាន</string>\n    <string name=\"chapters\">ជំពូក</string>\n    <string name=\"close\">បិទ</string>\n    <string name=\"local_storage\">ផ្ទុកទុកក្នុងទូរស័ព្ទ</string>\n    <string name=\"error_occurred\">មានកំហុសកើតឡើង</string>\n    <string name=\"network_error\">កំហុសបណ្ដាញ</string>\n    <string name=\"details\">ព័ត៌មានលម្អិត</string>\n    <string name=\"list\">រាយបញ្ជី</string>\n    <string name=\"detailed_list\">លំអិតបញ្ជី</string>\n    <string name=\"grid\">ក្រឡា</string>\n    <string name=\"list_mode\">រាយបញ្ជី</string>\n    <string name=\"settings\">ការកំណត់</string>\n    <string name=\"remote_sources\">ប្រភពម៉ងហ្គា</string>\n    <string name=\"loading_\">កំពុងលូដ…</string>\n    <string name=\"computing_\">កំពុងគណនា…</string>\n    <string name=\"chapter_d_of_d\">ជំពូក%1$dនៃ%2$d</string>\n    <string name=\"try_again\">សាកល្បងម្តងទៀត</string>\n    <string name=\"clear_history\">លុប​ ប្រវត្តិអាន</string>\n    <string name=\"nothing_found\">រកអ្វីមិនឃើញ</string>\n    <string name=\"history_is_empty\">មិនទាន់មានប្រវត្តិអាន</string>\n    <string name=\"read\">អាន</string>\n    <string name=\"you_have_not_favourites_yet\">មិនទាន់មានចំណូលចិត្ត</string>\n    <string name=\"add_to_favourites\">ដាក់ចូលចំណូលចិត្ត</string>\n    <string name=\"add\">រាប់បញ្ចូល</string>\n    <string name=\"save\">រក្សាទុក</string>\n    <string name=\"share\">ចែករំលែក</string>\n    <string name=\"processing_\">កំពុងដំណើរការ…</string>\n    <string name=\"download_complete\">បានទាញយក</string>\n    <string name=\"share_s\">ចែករំលែក%s</string>\n    <string name=\"search\">ស្វែងរក</string>\n    <string name=\"search_manga\">ស្វែងរកមេនហ្គារ</string>\n    <string name=\"manga_downloading_\">កំពុងទាញយក…</string>\n    <string name=\"by_name\">ឈ្មោះ</string>\n    <string name=\"popular\">ពេញនិយម</string>\n    <string name=\"updated\">បានធ្វើបច្ចុប្បន្នភាព</string>\n    <string name=\"add_new_category\">ប្រភេទថ្មី</string>\n    <string name=\"downloads\">ការទាញយក</string>\n    <string name=\"filter\">តម្រង</string>\n    <string name=\"theme\">ប្រភេទពណ័</string>\n    <string name=\"follow_system\">ប្រព័ន្ធតាមដាន</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" ត្រូវបានលុបចេញពីការផ្ទុកទុក</string>\n    <string name=\"save_page\">រក្សាទុកទំព័រ</string>\n    <string name=\"operation_not_supported\">ប្រតិបត្តិការនេះមិនបានគាំទ្រទេ</string>\n    <string name=\"no_description\">គ្មានការពន្យល់</string>\n    <string name=\"clear_pages_cache\">សំអាត់ទិន្នន័យទំព័រផ្ទុកបណ្តោះអាសន្ន</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"text_file_not_supported\">សូមជ្រើសរើសឯកសារ ZIP ឬ CBZ</string>\n    <string name=\"remove_category\">ដកចេញ</string>\n    <string name=\"done\">រួចរាល់</string>\n    <string name=\"update\">ធ្វើបច្ចុប្បន្នភាព</string>\n    <string name=\"about\">អំពី</string>\n    <string name=\"black_dark_theme\">ខ្មៅ</string>\n    <string name=\"just_now\">អំបាញ់មិញ</string>\n    <string name=\"yesterday\">ម្សិលមិញ</string>\n    <string name=\"long_ago\">ពីមុនមក</string>\n    <string name=\"today\">ថ្ងៃនេះ</string>\n    <string name=\"silent\">ស្ថាត់</string>\n    <string name=\"confirm\">យល់ព្រម</string>\n    <string name=\"dark\">ងងឹត</string>\n    <string name=\"newest\">ថ្មីបំផុត</string>\n    <string name=\"by_rating\">វាយតម្លៃ</string>\n    <string name=\"light\">ភ្លឺ</string>\n    <string name=\"clear\">លុបសំអាត់</string>\n    <string name=\"sort_order\">តម្រៀបលំដាប់</string>\n    <string name=\"remove\">លុបចេញ</string>\n    <string name=\"_continue\">បន្ត</string>\n    <string name=\"webtoon\">វេបធូន</string>\n    <string name=\"page_saved\">បានរក្សារទុក</string>\n    <string name=\"_import\">នាំចូល</string>\n    <string name=\"pages\">ទំព័រ</string>\n    <string name=\"standard\">ស្តង់ដារ</string>\n    <string name=\"share_image\">ចែករំលែករូបភាព</string>\n    <string name=\"delete\">លុប</string>\n    <string name=\"error\">កាំង</string>\n    <string name=\"download\">ទាញយក</string>\n    <string name=\"next\">តទៀត</string>\n    <string name=\"welcome\">សូមស្វាគមន៍</string>\n    <string name=\"address\">អាស័យដ្ឋាន</string>\n    <string name=\"delete_manga\">លុបម៉េងហ្កា</string>\n    <string name=\"text_delete_local_manga\">លុប\\\"%s\\\"ជាអចិន្ត្រៃយ៍ពីឧបករណ៍?</string>\n    <string name=\"reader_settings\">ការកំណត់ការអាន</string>\n    <string name=\"create_shortcut\">បង្កើតផ្លូវកាត់…</string>\n    <string name=\"read_mode\">អាន</string>\n    <string name=\"grid_size\">ទំហំក្រឡា</string>\n    <string name=\"search_on_s\">រកលើ%s</string>\n    <string name=\"switch_pages\">ប្តូរទំព័រ</string>\n    <string name=\"open_in_browser\">បើកនៅក្នុងវេបប្រោសឹរ</string>\n    <string name=\"notifications\">ជូនដំណឹង</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$dនៃ%2$dលើ</string>\n    <string name=\"notifications_settings\">ការកំណត់សេចក្តីជូនដំណឹង</string>\n    <string name=\"notification_sound\">សម្លេងជូនដំណឹង</string>\n    <string name=\"light_indicator\">សញ្ញាបង្ហាញអិលអ៉ីដ៉ី​</string>\n    <string name=\"clear_thumbs_cache\">សម្អាត់តមណែលឃែស</string>\n    <string name=\"clear_search_history\">សម្អាត់ប្រវិត្តស្វែងរក</string>\n    <string name=\"search_history_cleared\">សម្អាត់រួចរាល់</string>\n    <string name=\"internal_storage\">ឃ្លាំងទិន្នន័យផ្ទៃក្នុង</string>\n    <string name=\"external_storage\">ឃ្លាំងទិន្នន័យផ្ទៃក្រៅ</string>\n    <string name=\"domain\">ដូមេន</string>\n    <string name=\"app_update_available\">មានកំណែថ្មីនៃកម្មវិធី</string>\n    <string name=\"new_chapters\">ជំពូកថ្មី</string>\n    <string name=\"vibration\">រំញ័រ</string>\n    <string name=\"text_empty_holder_secondary_filtered\">មិនមានមេនហ្កាដែលត្រូវគ្នានឹងតម្រងដែលអ្នកបានជ្រើសរើសទេ</string>\n    <string name=\"text_history_holder_secondary\">រកអ្វីដែលត្រូវអាននៅក្នុងផ្នែក \\\"រុករក\\\"</string>\n    <string name=\"favourites_categories\">ប្រភេទដែលចូលចិត្ត</string>\n    <string name=\"text_empty_holder_primary\">វាដូចជាទទេនៅទីនេះ…</string>\n    <string name=\"text_search_holder_secondary\">ព្យាយាមកែទម្រង់សំណួរ។</string>\n    <string name=\"text_history_holder_primary\">អ្វីដែលអ្នកអាននឹងត្រូវបានបង្ហាញនៅទីនេះ</string>\n    <string name=\"manga_shelf\">ធ្នើ</string>\n    <string name=\"recent_manga\">ថ្មីៗ</string>\n    <string name=\"text_local_holder_primary\">រក្សាទុកអ្វីមួយជាមុនសិន</string>\n    <string name=\"text_local_holder_secondary\">រក្សាទុកអ្វីមួយពីកាតាឡុកអនឡាញ ឬនាំវាចូលពីឯកសារ។</string>\n    <string name=\"pages_animation\">ចលនា</string>\n    <string name=\"sfw\">សម្រាប់គ្រប់វ័យ</string>\n    <string name=\"retry\">ព្យាយាមម្តងទៀត</string>\n    <string name=\"skip_all\">រំលងទាំងអស់</string>\n    <string name=\"stuck\">ជាប់</string>\n    <string name=\"external_source\">ខាងក្រៅ/កម្មវិធីជំនួយ</string>\n    <string name=\"chapters_left\">ជំពូកដែលនៅសល់</string>\n    <string name=\"chapters_read\">ជំពូកបានអាន</string>\n    <string name=\"percent_left\">ភាគរយនៅសល់</string>\n    <string name=\"percent_read\">ភាគរយបានអាន</string>\n    <string name=\"recent_sources\">ប្រភពថ្មីៗ</string>\n    <string name=\"sources_unpinned\">ដក​ខ្ទាស់​ប្រភពទាំងអស់</string>\n    <string name=\"source_unpinned\">ដក​ខ្ទាស់​ប្រភព</string>\n    <string name=\"source_pinned\">ខ្ទាស់​ប្រភព</string>\n    <string name=\"sources_pinned\">ខ្ទាស់​ប្រភពទាំងអស់</string>\n    <string name=\"unpin\">ដកខ្ទាស់</string>\n    <string name=\"pin\">ដាក់ខ្ទស់</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ko/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-ko/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"sort_order\">정렬 기준</string>\n    <string name=\"_import\">불러오기</string>\n    <string name=\"network_error\">네트워크 오류</string>\n    <string name=\"list\">목록</string>\n    <string name=\"save\">저장</string>\n    <string name=\"share\">공유하기</string>\n    <string name=\"share_s\">%s 공유</string>\n    <string name=\"search\">검색하기</string>\n    <string name=\"internal_storage\">내부 저장소</string>\n    <string name=\"external_storage\">외부 저장소</string>\n    <string name=\"domain\">도메인</string>\n    <string name=\"app_update_available\">새 버전이 존재합니다</string>\n    <string name=\"open_in_browser\">웹 브라우저에서 열기</string>\n    <string name=\"notifications\">알림</string>\n    <string name=\"light_indicator\">LED 표시</string>\n    <string name=\"vibration\">진동</string>\n    <string name=\"remove_category\">지우기</string>\n    <string name=\"text_search_holder_secondary\">쿼리를 재구성하십시오.</string>\n    <string name=\"text_history_holder_secondary\">«탐색» 섹션에서 읽을 내용 찾기</string>\n    <string name=\"pages_animation\">페이지 전환 효과</string>\n    <string name=\"cannot_find_available_storage\">사용 가능한 저장소 없음</string>\n    <string name=\"done\">완료</string>\n    <string name=\"favourites_category_empty\">빈 카테고리</string>\n    <string name=\"updates\">업데이트</string>\n    <string name=\"new_version_s\">새 버전: %s</string>\n    <string name=\"clear_updates_feed\">업데이트 피드 지우기</string>\n    <string name=\"local_storage\">내장 메모리</string>\n    <string name=\"favourites\">즐겨찾기</string>\n    <string name=\"remove\">지우기</string>\n    <string name=\"settings\">설정</string>\n    <string name=\"loading_\">불러오는 중…</string>\n    <string name=\"close\">닫기</string>\n    <string name=\"try_again\">다시 시도</string>\n    <string name=\"you_have_not_favourites_yet\">즐겨찾기가 비어있음</string>\n    <string name=\"filter\">필터링</string>\n    <string name=\"light\">밝게</string>\n    <string name=\"dark\">어둡게</string>\n    <string name=\"pages\">페이지</string>\n    <string name=\"read\">지금 읽기</string>\n    <string name=\"by_name\">이름 순</string>\n    <string name=\"popular\">인기 순</string>\n    <string name=\"chapter_d_of_d\">%2$d화 중 %1$d화</string>\n    <string name=\"downloads\">다운로드</string>\n    <string name=\"by_rating\">평점 순</string>\n    <string name=\"save_page\">페이지 저장</string>\n    <string name=\"page_saved\">저장됨</string>\n    <string name=\"share_image\">이미지 공유하기</string>\n    <string name=\"text_file_not_supported\">ZIP 혹은 CBZ 파일을 선택하세요.</string>\n    <string name=\"delete_manga\">만화 제거</string>\n    <string name=\"nothing_found\">결과 없음</string>\n    <string name=\"add_to_favourites\">즐겨찾기 추가</string>\n    <string name=\"download_complete\">다운로드 완료</string>\n    <string name=\"add_new_category\">새 카테고리</string>\n    <string name=\"search_manga\">만화를 검색하세요</string>\n    <string name=\"manga_downloading_\">다운로드 중…</string>\n    <string name=\"processing_\">처리중…</string>\n    <string name=\"updated\">최근 업데이트 순</string>\n    <string name=\"newest\">최근 발간 순</string>\n    <string name=\"follow_system\">시스템 설정</string>\n    <string name=\"delete\">지우기</string>\n    <string name=\"text_file_sizes\">바이트|kB|MB|GB|TB</string>\n    <string name=\"clear_pages_cache\">페이지 캐시 지우기</string>\n    <string name=\"read_mode\">읽기 모드</string>\n    <string name=\"grid_size\">격자 크기</string>\n    <string name=\"search_on_s\">%s에서 검색</string>\n    <string name=\"text_delete_local_manga\">장치에서 \\\"%s\\\"를 영구적으로 삭제하시겠습니까\\?</string>\n    <string name=\"switch_pages\">페이지 전환</string>\n    <string name=\"webtoon\">웹툰</string>\n    <string name=\"clear_search_history\">검색 기록 지우기</string>\n    <string name=\"reader_settings\">읽기 모드</string>\n    <string name=\"clear_thumbs_cache\">썸네일 캐시 지우기</string>\n    <string name=\"error\">오류</string>\n    <string name=\"favourites_categories\">즐겨찾기 카테고리</string>\n    <string name=\"download\">다운로드</string>\n    <string name=\"notifications_settings\">알림 설정</string>\n    <string name=\"notification_sound\">알림음</string>\n    <string name=\"text_history_holder_primary\">읽은 내용이 여기에 표시됩니다</string>\n    <string name=\"not_available\">사용할 수 없음</string>\n    <string name=\"all_favourites\">모든 즐겨찾기</string>\n    <string name=\"read_later\">나중에 읽기</string>\n    <string name=\"search_results\">검색 결과</string>\n    <string name=\"size_s\">크기: %s</string>\n    <string name=\"computing_\">계산중…</string>\n    <string name=\"clear\">지우기</string>\n    <string name=\"text_local_holder_primary\">먼저 아무거나 저장해보세요</string>\n    <string name=\"text_feed_holder\">여기서 읽고 있는 만화의 새로운 챕터들을 확인할 수 있습니다</string>\n    <string name=\"protect_application\">앱 잠금 활성화</string>\n    <string name=\"protect_application_summary\">Kotatsu를 실행할 때마다 비밀번호 묻기</string>\n    <string name=\"check_for_updates\">업데이트 확인하기</string>\n    <string name=\"right_to_left\">오른쪽에서 왼쪽</string>\n    <string name=\"zoom_mode_fit_center\">가운데 맞춤</string>\n    <string name=\"no_update_available\">가능한 업데이트 없음</string>\n    <string name=\"zoom_mode_fit_height\">세로 맞춤</string>\n    <string name=\"tap_to_try_again\">탭해서 재시도</string>\n    <string name=\"restore_backup\">백업한 데이터 복원하기</string>\n    <string name=\"create_backup\">백업하기</string>\n    <string name=\"data_restored\">복원됨</string>\n    <string name=\"preparing_\">준비중…</string>\n    <string name=\"welcome\">환영합니다</string>\n    <string name=\"auth_complete\">인증됨</string>\n    <string name=\"about_app_translation\">번역</string>\n    <string name=\"about_app_translation_summary\">이 앱을 번역하기</string>\n    <string name=\"screenshots_block_nsfw\">성인 컨텐츠에서만 차단</string>\n    <string name=\"screenshots_allow\">항상 허용</string>\n    <string name=\"exclude_nsfw_from_suggestions\">성인 만화(NSFW)는 추천하지 않기</string>\n    <string name=\"disabled\">비활성화됨</string>\n    <string name=\"chapters_empty\">이 만화는 챕터로 나눠져 있지 않습니다</string>\n    <string name=\"search_chapters\">챕터 찾아보기</string>\n    <string name=\"chapters_will_removed_background\">챕터들이 백그라운드에서 제거됩니다</string>\n    <string name=\"download_slowdown_summary\">IP 차단을 회피할 수 있게 합니다</string>\n    <string name=\"check_new_chapters_title\">새로운 챕터가 나오면 알려주기</string>\n    <string name=\"standard\">스탠다드</string>\n    <string name=\"text_local_holder_secondary\">온라인 소스 혹은 직접 파일을 불러와 저장하기.</string>\n    <string name=\"suggestions_info\">모든 데이터는 이 장치에서 로컬로 분석되며 아무데도 전송되지 않습니다.</string>\n    <string name=\"suggestions_summary\">당신의 선호도를 바탕으로 만화를 추천합니다</string>\n    <string name=\"bookmark_add\">북마크에 추가</string>\n    <string name=\"bookmark_remove\">북마크 제거</string>\n    <string name=\"bookmarks\">북마크</string>\n    <string name=\"data_deletion\">데이터 삭제</string>\n    <string name=\"no_manga_sources\">만화 소스 사이트 없음</string>\n    <string name=\"history\">최근에 본 만화</string>\n    <string name=\"error_occurred\">오류 발생</string>\n    <string name=\"details\">세부정보</string>\n    <string name=\"chapters\">챕터</string>\n    <string name=\"detailed_list\">자세한 목록</string>\n    <string name=\"list_mode\">설정</string>\n    <string name=\"grid\">그리드</string>\n    <string name=\"remote_sources\">만화 소스</string>\n    <string name=\"clear_history\">기록 삭제</string>\n    <string name=\"add\">추가</string>\n    <string name=\"history_is_empty\">아직 기록이 없습니다</string>\n    <string name=\"create_shortcut\">바로가기 추가…</string>\n    <string name=\"theme\">테마</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\"가 로컬 저장소에서 삭제됨</string>\n    <string name=\"operation_not_supported\">지원되지 않는 동작입니다</string>\n    <string name=\"no_description\">설명없음</string>\n    <string name=\"_continue\">계속</string>\n    <string name=\"search_history_cleared\">삭제됨</string>\n    <string name=\"new_chapters\">새로운 챕터</string>\n    <string name=\"text_empty_holder_primary\">이 항목은 비어있는 것 같습니다…</string>\n    <string name=\"recent_manga\">최근에 추가된</string>\n    <string name=\"manga_save_location\">다운로드를 저장하기 위한 폴더</string>\n    <string name=\"manga_shelf\">책장</string>\n    <string name=\"other_storage\">기타 저장소</string>\n    <string name=\"updates_feed_cleared\">삭제됨</string>\n    <string name=\"update\">업데이트</string>\n    <string name=\"feed_will_update_soon\">피드가 곧 업데이트 됩니다</string>\n    <string name=\"dont_check\">확인하지 않기</string>\n    <string name=\"enter_password\">비밀번호를 입력하세요</string>\n    <string name=\"wrong_password\">잘못된 비밀번호</string>\n    <string name=\"rotate_screen\">화면 회전</string>\n    <string name=\"track_sources\">업데이트 확인하기</string>\n    <string name=\"repeat_password\">비밀번호 재입력</string>\n    <string name=\"passwords_mismatch\">일치하지 않는 비밀번호</string>\n    <string name=\"app_version\">%s 버전</string>\n    <string name=\"about\">정보</string>\n    <string name=\"create_category\">새로운 카테고리</string>\n    <string name=\"scale_mode\">확대 설정</string>\n    <string name=\"zoom_mode_fit_width\">가로 맞춤</string>\n    <string name=\"black_dark_theme\">검은색</string>\n    <string name=\"black_dark_theme_summary\">AMOLED 화면에서의 전력소모를 줄입니다</string>\n    <string name=\"backup_restore\">백업 및 복원</string>\n    <string name=\"file_not_found\">파일을 찾을 수 없음</string>\n    <string name=\"data_restored_success\">모든 데이터 복원됨</string>\n    <string name=\"data_restored_with_errors\">데이터가 복원되었지만 오류가 존재합니다</string>\n    <string name=\"backup_information\">선호작이나 읽은 기록들을 백업 및 복원할 수 있습니다</string>\n    <string name=\"yesterday\">어제</string>\n    <string name=\"long_ago\">오래전</string>\n    <string name=\"group\">그룹</string>\n    <string name=\"today\">오늘</string>\n    <string name=\"captcha_required\">CAPTCHA 설정이 필요합니다</string>\n    <string name=\"captcha_solve\">해결됨</string>\n    <string name=\"clear_cookies\">쿠키 삭제</string>\n    <string name=\"zoom_mode_keep_start\">첫 칸에 맞춤</string>\n    <string name=\"silent\">무음</string>\n    <string name=\"reader_mode_hint\">선택된 설정값이 항상 이 만화에 적용됩니다</string>\n    <string name=\"cookies_cleared\">모든 쿠키가 삭제되었습니다</string>\n    <string name=\"text_clear_updates_feed_prompt\">모든 업데이트 기록을 영구적으로 삭제하시겠습니까\\?</string>\n    <string name=\"clear_feed\">피드 정리</string>\n    <string name=\"check_for_new_chapters\">새로운 챕터 확인하기</string>\n    <string name=\"sign_in\">로그인</string>\n    <string name=\"auth_required\">로그인이 필요합니다</string>\n    <string name=\"default_s\">기본값: %s</string>\n    <string name=\"next\">다음</string>\n    <string name=\"protect_application_subtitle\">비밀번호를 입력하세요</string>\n    <string name=\"confirm\">확인</string>\n    <string name=\"password_length_hint\">최소 4자리 이상의 비밀번호를 입력해 주세요</string>\n    <string name=\"backup_saved\">백업 저장됨</string>\n    <string name=\"tracker_warning\">몇몇 기기들은 시스템이 백그라운드 작업을 방해할 수 있습니다.</string>\n    <string name=\"text_clear_search_history_prompt\">모든 검색 기록을 영구적으로 삭제 하시겠어요\\?</string>\n    <string name=\"read_more\">더 읽기</string>\n    <string name=\"queued\">대기열</string>\n    <string name=\"text_clear_cookies_prompt\">모든 사이트에서 로그아웃됩니다</string>\n    <string name=\"genres\">장르</string>\n    <string name=\"state_finished\">완료됨</string>\n    <string name=\"system_default\">기본</string>\n    <string name=\"exclude_nsfw_from_history\">기록에서 성인 만화(NSFW) 제외하기</string>\n    <string name=\"chapter_is_missing\">찾으시는 챕터가 존재하지 않습니다</string>\n    <string name=\"auth_not_supported_by\">%s에 로그인은 지원되지 않습니다</string>\n    <string name=\"screenshots_policy\">스크린샷 규칙</string>\n    <string name=\"screenshots_block_all\">항상 차단</string>\n    <string name=\"suggestions\">추천</string>\n    <string name=\"suggestions_enable\">추천 켜기</string>\n    <string name=\"text_suggestion_holder\">아무 만화나 읽어보세요 당신의 기록을 바탕으로 개인화된 추천 만화를 제공합니다</string>\n    <string name=\"enabled\">활성화됨</string>\n    <string name=\"reset_filter\">필터 초기화</string>\n    <string name=\"only_using_wifi\">와이파이에 연결된 경우에만</string>\n    <string name=\"always\">항상</string>\n    <string name=\"onboard_text\">무슨 언어의 만화를 읽을지 선택하세요. 나중에 설정에서 이를 변경할 수 있습니다.</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"preload_pages\">페이지 미리 로드하기</string>\n    <string name=\"logged_in_as\">%s로 로그인 됨</string>\n    <string name=\"appearance\">외관</string>\n    <string name=\"suggestions_excluded_genres\">제외할 장르 선택</string>\n    <string name=\"suggestions_excluded_genres_summary\">추천 목록에서 보고 싶지 않은 장르를 특정합니다</string>\n    <string name=\"text_delete_local_manga_batch\">선택된 항목을 기기에서 영구히 제거하시겠어요\\?</string>\n    <string name=\"removal_completed\">제거 완료</string>\n    <string name=\"suggestions_updating\">추천 항목 업데이트 중</string>\n    <string name=\"canceled\">취소됨</string>\n    <string name=\"name\">이름</string>\n    <string name=\"disable_battery_optimization\">배터리 최적화 해제하기</string>\n    <string name=\"disable_battery_optimization_summary\">백그라운드에서의 업데이트 확인이 안드로이드 시스템에 의해 중지되지 않습니다</string>\n    <string name=\"send\">보내기</string>\n    <string name=\"use_fingerprint\">가능할 경우 지문 사용</string>\n    <string name=\"select_range\">범위 선택</string>\n    <string name=\"history_cleared\">기록 삭제됨</string>\n    <string name=\"random\">랜덤</string>\n    <string name=\"not_found_404\">컨텐츠를 찾을 수 없거나 제거되었습니다</string>\n    <string name=\"incognito_mode\">사생활 보호 모드</string>\n    <string name=\"no_chapters\">챕터 없음</string>\n    <string name=\"automatic_scroll\">자동 스크롤</string>\n    <string name=\"reader_info_bar\">리더 안에서 만화 정보 보여주기</string>\n    <string name=\"feed\">피드</string>\n    <string name=\"show_notification_new_chapters_on\">읽고 있는 만화의 업데이트 알림을 수신할 수 있게 됩니다</string>\n    <string name=\"show_notification_new_chapters_off\">알림은 받지 못하지만 여전히 새로운 챕터들이 목록 상에서 강조 표시되어 보여집니다</string>\n    <string name=\"notifications_enable\">알림 활성화</string>\n    <string name=\"edit\">수정</string>\n    <string name=\"edit_category\">카테고리 수정</string>\n    <string name=\"tracking\">새로운 소식 추적</string>\n    <string name=\"empty_favourite_categories\">선호 표시된 카테고리 없음</string>\n    <string name=\"logout\">로그아웃</string>\n    <string name=\"bookmark_removed\">북마크 제거됨</string>\n    <string name=\"bookmark_added\">북마크 추가됨</string>\n    <string name=\"undo\">실행취소</string>\n    <string name=\"removed_from_history\">기록에서 지우기</string>\n    <string name=\"dns_over_https\">DNS over HTTPS 활성화</string>\n    <string name=\"default_mode\">기본 모드</string>\n    <string name=\"detect_reader_mode_summary\">왭툰 자동 감지</string>\n    <string name=\"crash_text\">오류. 저희가 고칠 수 있게 버그 정보를 보내주세요.</string>\n    <string name=\"status_planned\">계획됨</string>\n    <string name=\"status_reading\">읽는 중</string>\n    <string name=\"status_re_reading\">다시 읽는 중</string>\n    <string name=\"status_completed\">완료됨</string>\n    <string name=\"disable_all\">모두 비활성화</string>\n    <string name=\"appwidget_shelf_description\">선호작 목록에 존재하는 만화</string>\n    <string name=\"appwidget_recent_description\">최근에 본 만화</string>\n    <string name=\"report\">제보하기</string>\n    <string name=\"show_reading_indicators\">읽기 진행 상황 표시</string>\n    <string name=\"exclude_nsfw_from_history_summary\">성인 만화는 읽은 기록에 포함되지 않으며 읽기 진행 상황은 저장되지 않습니다</string>\n    <string name=\"show_all\">모두 보여주기</string>\n    <string name=\"invalid_domain_message\">유효하지 않은 도메인</string>\n    <string name=\"clear_all_history\">모든 기록 삭제</string>\n    <string name=\"last_2_hours\">지난 2시간</string>\n    <string name=\"manage\">관리</string>\n    <string name=\"no_bookmarks_yet\">아직 추가된 븍마크가 없습니다</string>\n    <string name=\"no_bookmarks_summary\">만화를 읽는 도중 북마크를 추가할 수 있습니다</string>\n    <string name=\"bookmarks_removed\">북마크 제거됨</string>\n    <string name=\"no_manga_sources_text\">만화를 읽기 위해 만화 소스 사이트를 활성화 하세요</string>\n    <string name=\"categories_delete_confirm\">정말 선택된 즐겨 찾는 카테고리를 삭제하시겠습니까? \\n해당 카테고리의 모든 만화가 손실되며 취소할 수 없습니다.</string>\n    <string name=\"empty\">비어있음</string>\n    <string name=\"confirm_exit\">뒤로가기 버튼을 다시 눌러 나가기</string>\n    <string name=\"exit_confirmation_summary\">뒤로가기 버튼을 두 번 눌러 앱을 종료할 수 있습니다</string>\n    <string name=\"exit_confirmation\">앱 종료 확인</string>\n    <string name=\"saved_manga\">저장된 만화</string>\n    <string name=\"pages_cache\">페이지 캐시</string>\n    <string name=\"other_cache\">기타 캐시</string>\n    <string name=\"storage_usage\">저장소 사용량</string>\n    <string name=\"memory_usage_pattern\">%s -%s</string>\n    <string name=\"removed_from_favourites\">즐겨찾기 목록에서 제거됨</string>\n    <string name=\"reader_info_pattern\">챕터.%1$d/%2$d 페이지.%3$d/%4$d</string>\n    <string name=\"comics_archive\">코믹스 모음</string>\n    <string name=\"import_completed\">가져오기 완료</string>\n    <string name=\"import_completed_hint\">본 파일을 제거함으로써 저장소 공간을 아낄 수 있습니다</string>\n    <string name=\"import_will_start_soon\">가져오기가 곧 시작됩니다</string>\n    <string name=\"history_shortcuts\">최근 추가된 만화 바로가기 보여주기</string>\n    <string name=\"hide\">숨기기</string>\n    <string name=\"new_sources_text\">새로운 만화 소스 사이트 사용 가능</string>\n    <string name=\"account_already_exists\">계정이 이미 존재합니다</string>\n    <string name=\"back\">뒤로가기</string>\n    <string name=\"sync\">동기화</string>\n    <string name=\"sync_title\">데이터 동기화 하기</string>\n    <string name=\"email_enter_hint\">이메일을 입력하여 계속</string>\n    <string name=\"download_slowdown\">다운로드 속도 늦추기</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d 의%2$d 에</string>\n    <string name=\"invalid_port_number\">잘못된 포트 번호</string>\n    <string name=\"authorization_optional\">권한 부여(선택 사항)</string>\n    <string name=\"password\">비밀번호</string>\n    <string name=\"show_pages_numbers_summary\">하단 모서리에 페이지 번호 표시</string>\n    <string name=\"reader_info_bar_summary\">화면 상단에 현재 시간 및 읽기 진행률 표시</string>\n    <string name=\"data_and_privacy\">데이터 및 개인정보 보호</string>\n    <string name=\"network\">네트워크</string>\n    <string name=\"images_proxy_title\">이미지 최적화 프록시</string>\n    <string name=\"images_procy_description\">사용 wsrv.nl 가능한 경우 트래픽 사용량을 줄이고 이미지 로딩 속도를 높이는 서비스</string>\n    <string name=\"username\">사용자 이름</string>\n    <string name=\"web_view_unavailable\">WebView를 사용할 수 없음: WebView 공급자가 설치되어 있는지 확인하십시오</string>\n    <string name=\"show_pages_numbers\">페이지 번호 매기기</string>\n    <string name=\"never\">절대</string>\n    <string name=\"clear_network_cache\">네트워크 캐시 지우기</string>\n    <string name=\"invalid_value_message\">잘못된 값</string>\n    <string name=\"just_now\">방금</string>\n    <string name=\"detect_reader_mode\">자동 감지 리더 모드</string>\n    <string name=\"explore\">탐색</string>\n    <string name=\"options\">옵션</string>\n    <string name=\"importing_manga\">만화 가져오기</string>\n    <string name=\"services\">서비스</string>\n    <string name=\"clear_source_cookies_summary\">지정된 도메인에 대해서만 쿠키를 지웁니다. 대부분의 경우 인증이 무효화됩니다</string>\n    <string name=\"history_shortcuts_summary\">응용 프로그램 아이콘을 길게 눌러 최신 만화를 사용할 수 있도록 합니다</string>\n    <string name=\"reorder\">재 주문</string>\n    <string name=\"folder_with_images\">이미지가 있는 폴더</string>\n    <string name=\"download_option_all_unread_b\">읽지 않은 모든 챕터(%s)</string>\n    <string name=\"download_option_all_unread\">읽지 않은 모든 챕터</string>\n    <string name=\"download_option_all_chapters\">%s 번역이 있는 모든 챕터</string>\n    <string name=\"color_correction\">색보정</string>\n    <string name=\"reader_control_ltr\">인체공학적 리더 컨트롤</string>\n    <string name=\"brightness\">명도</string>\n    <string name=\"local_manga_directories\">지역 만화 디렉토리</string>\n    <string name=\"no_access_to_file\">이 파일 또는 디렉터리에 대한 액세스 권한이 없습니다</string>\n    <string name=\"various_languages\">다양한 언어</string>\n    <string name=\"local_manga_processing\">저장된 만화 처리</string>\n    <string name=\"show_reading_indicators_summary\">기록 및 즐겨찾기에서 읽은 비율 표시</string>\n    <string name=\"reader_control_ltr_summary\">오른쪽 가장자리를 탭하거나 오른쪽 키를 누르면 항상 다음 페이지로 전환됩니다</string>\n    <string name=\"contrast\">차이</string>\n    <string name=\"reset\">초기화</string>\n    <string name=\"text_unsaved_changes_prompt\">저장되지 않은 변경 사항을 저장하거나 삭제하시겠습니까\\?</string>\n    <string name=\"manga_error_description_pattern\">오류 세부정보:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. &lt;a href=%2$s&gt;웹 브라우저에서 만화를 열어&lt;/a&gt; 소스에서 사용할 수 있는지 확인하세요&lt;br&gt;2. &lt;a href=kotatsu://about&gt;최신 버전의 Kotatsu&lt;/a&gt;&lt;br&gt;를 사용하고 있는지 확인하세요.3. 사용 가능한 경우 개발자에게 오류 보고서를 보냅니다.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ldrtl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"breadcrumbs_separator\" translatable=\"false\"><![CDATA[\" < \"]]></string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-lt/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d naujas skyrius</item>\n        <item quantity=\"few\">%1$d nauji skyriai</item>\n        <item quantity=\"other\">%1$d naujų skyrių</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d pranešimas</item>\n        <item quantity=\"few\">%1$d pranešimai</item>\n        <item quantity=\"other\">%1$d pranešimų</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">prieš %1$d min</item>\n        <item quantity=\"few\">prieš %1$d min</item>\n        <item quantity=\"other\">prieš %1$d min</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d skyrius</item>\n        <item quantity=\"few\">%1$d skyriai</item>\n        <item quantity=\"other\">%1$d skyrių</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d val</item>\n        <item quantity=\"few\">%1$d val</item>\n        <item quantity=\"other\">%1$d val</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d min</item>\n        <item quantity=\"few\">%1$d min</item>\n        <item quantity=\"other\">%1$d min</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">prieš %1$d val</item>\n        <item quantity=\"few\">prieš %1$d val</item>\n        <item quantity=\"other\">prieš %1$d val</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">prieš %1$d dieną</item>\n        <item quantity=\"few\">prieš %1$d dienas</item>\n        <item quantity=\"other\">prieš %1$d dienų</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">prieš %1$d mėn</item>\n        <item quantity=\"few\">prieš %1$d mėn</item>\n        <item quantity=\"other\">prieš %1$d mėn</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-lt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"chapter_d_of_d\">%1$d skyrius iš %2$d</string>\n    <string name=\"close\">Uždaryti</string>\n    <string name=\"try_again\">Bandykite dar kartą</string>\n    <string name=\"clear_history\">Išvalyti istoriją</string>\n    <string name=\"nothing_found\">Nieko nerasta</string>\n    <string name=\"history_is_empty\">Istorijos dar nėra</string>\n    <string name=\"read\">Skaityti</string>\n    <string name=\"you_have_not_favourites_yet\">Mėgstamiausių dar nėra</string>\n    <string name=\"add_new_category\">Nauja kategorija</string>\n    <string name=\"add\">Pridėti</string>\n    <string name=\"save\">Išsaugoti</string>\n    <string name=\"search\">Paieška</string>\n    <string name=\"computing_\">Skaičiuojama…</string>\n    <string name=\"new_chapters\">Nauji skyriai</string>\n    <string name=\"local_storage\">Vietos saugykla</string>\n    <string name=\"favourites\">Favoritai</string>\n    <string name=\"history\">Istorija</string>\n    <string name=\"error_occurred\">Atsitiko klaida</string>\n    <string name=\"network_error\">Tinklo klaida</string>\n    <string name=\"details\">Detalės</string>\n    <string name=\"chapters\">Skyriai</string>\n    <string name=\"list\">Sąrašas</string>\n    <string name=\"detailed_list\">Detalus sąrašas</string>\n    <string name=\"grid\">Tinklelis</string>\n    <string name=\"settings\">Nustatymai</string>\n    <string name=\"loading_\">Kraunasi…</string>\n    <string name=\"search_manga\">Ieškoti manga</string>\n    <string name=\"remote_sources\">Manga šaltiniai</string>\n    <string name=\"manga_downloading_\">Atsisiunčiama…</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-lv/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-lv/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"details\">Detaļas</string>\n    <string name=\"list\">Saraksts</string>\n    <string name=\"grid\">Kastes</string>\n    <string name=\"list_mode\">Saraksta veids</string>\n    <string name=\"settings\">Iestatījumi</string>\n    <string name=\"remote_sources\">Manga avoti</string>\n    <string name=\"chapters\">Daļas</string>\n    <string name=\"computing_\">Darbojas…</string>\n    <string name=\"read\">Lasīt</string>\n    <string name=\"local_storage\">Vietējā krātuve</string>\n    <string name=\"history\">Vēsture</string>\n    <string name=\"network_error\">Interneta kļūda</string>\n    <string name=\"error_occurred\">Notika kļūda</string>\n    <string name=\"close\">Aizvērt</string>\n    <string name=\"detailed_list\">Detalizēts saraksts</string>\n    <string name=\"loading_\">Lādējas…</string>\n    <string name=\"try_again\">Atkārtot</string>\n    <string name=\"clear_history\">Iztukšot vēsturi</string>\n    <string name=\"nothing_found\">Neko neatradu</string>\n    <string name=\"add_to_favourites\">Man patīk</string>\n    <string name=\"history_is_empty\">Nav vēstures</string>\n    <string name=\"add_new_category\">Jauna nodaļa</string>\n    <string name=\"you_have_not_favourites_yet\">Nekas vēl nav paticis</string>\n    <string name=\"favourites\">Patīkošie</string>\n    <string name=\"chapter_d_of_d\">%1$d. daļa no %2$d</string>\n    <string name=\"add\">Pievienot</string>\n    <string name=\"save\">Saglabāt</string>\n    <string name=\"share\">Kopīgot</string>\n    <string name=\"newest\">Jaunākie</string>\n    <string name=\"delete\">Izdzēst</string>\n    <string name=\"internal_storage\">Iekšējā krātuve</string>\n    <string name=\"new_chapters\">Jaunas daļas</string>\n    <string name=\"remove_category\">Noņemt</string>\n    <string name=\"text_local_holder_secondary\">Saglabā kaut ko no interneta kataloga vai importē to no faila.</string>\n    <string name=\"rotate_screen\">Pagriezt ekrānu</string>\n    <string name=\"track_sources\">Meklēt atjauninājumus</string>\n    <string name=\"check_for_updates\">Meklēt atjauninājumus</string>\n    <string name=\"create_category\">Jauna kategorija</string>\n    <string name=\"black_dark_theme\">Melns</string>\n    <string name=\"black_dark_theme_summary\">Tērē mazāk enerģiju AMOLED ekrānos</string>\n    <string name=\"preparing_\">Gatavojas…</string>\n    <string name=\"data_restored_success\">Visi dati ir atjaunoti</string>\n    <string name=\"data_restored_with_errors\">Dati tika atjaunoti ar kļūdām</string>\n    <string name=\"yesterday\">Vakar</string>\n    <string name=\"today\">Šodien</string>\n    <string name=\"reader_mode_hint\">Izvēlētais sakārtojums tiks izmantots šajā mangā</string>\n    <string name=\"create_shortcut\">Izveidot saīsni…</string>\n    <string name=\"share_s\">Kopīgot %s</string>\n    <string name=\"search\">Meklēt</string>\n    <string name=\"search_manga\">Meklēt manga</string>\n    <string name=\"manga_downloading_\">Lejupielādē…</string>\n    <string name=\"processing_\">Pārstrādā…</string>\n    <string name=\"by_name\">Nosaukums</string>\n    <string name=\"popular\">Populāri</string>\n    <string name=\"updated\">Atjaunoti</string>\n    <string name=\"sort_order\">Atdales kārtība</string>\n    <string name=\"filter\">Filtrs</string>\n    <string name=\"theme\">Izskats</string>\n    <string name=\"light\">Gaišs</string>\n    <string name=\"dark\">Tumšs</string>\n    <string name=\"follow_system\">Līdzīgi sistēmai</string>\n    <string name=\"text_file_not_supported\">Ņem vai nu ZIP, vai CBZ failu.</string>\n    <string name=\"operation_not_supported\">Šī darbība nav atbalstīta</string>\n    <string name=\"clear_pages_cache\">Attīrīt lapas sīkdatnes</string>\n    <string name=\"no_description\">Nav apraksta</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"grid_size\">Kastu izmērs</string>\n    <string name=\"standard\">Parastais</string>\n    <string name=\"webtoon\">\\\"Webtoon\\\"</string>\n    <string name=\"read_mode\">Lasīšanas režīms</string>\n    <string name=\"switch_pages\">Iet caur lapām</string>\n    <string name=\"_continue\">Turpināt</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d no %2$d iespējotas</string>\n    <string name=\"notifications\">Paziņojumi</string>\n    <string name=\"download\">Lejupielādēt</string>\n    <string name=\"vibration\">Vibrācija</string>\n    <string name=\"favourites_categories\">Mīļākās kategorijas</string>\n    <string name=\"text_history_holder_primary\">Tas, ko tu lasi, būs šeit</string>\n    <string name=\"text_history_holder_secondary\">Ja nav ko lasīt, vari kaut ko atrast \\\"Meklēšana\\\" nodalījumā</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Neeksistē manga, kas atbilstu tevis atlasītajiem filtriem</string>\n    <string name=\"text_local_holder_primary\">Vispirms saglabā kaut ko</string>\n    <string name=\"manga_shelf\">Skapis</string>\n    <string name=\"pages_animation\">Lapas animācija</string>\n    <string name=\"manga_save_location\">Lejupielāžu mape</string>\n    <string name=\"not_available\">Nav pieejams</string>\n    <string name=\"cannot_find_available_storage\">Nav pieejama krātuve</string>\n    <string name=\"other_storage\">Cita krātuve</string>\n    <string name=\"done\">Pabeigts</string>\n    <string name=\"all_favourites\">Visi patīkošie</string>\n    <string name=\"favourites_category_empty\">Tukša ketegorija</string>\n    <string name=\"read_later\">Lasīt vēlāk</string>\n    <string name=\"feed_will_update_soon\">Drīz atjaunināsies</string>\n    <string name=\"dont_check\">Nemeklēt</string>\n    <string name=\"enter_password\">Ievadi paroli</string>\n    <string name=\"wrong_password\">Nepareiza parole</string>\n    <string name=\"protect_application\">Pasargāt aplikāciju</string>\n    <string name=\"protect_application_summary\">Prasīt paroli, kad atver Kotatsu</string>\n    <string name=\"repeat_password\">Atkārto paroli</string>\n    <string name=\"passwords_mismatch\">Nesakrīt paroles</string>\n    <string name=\"about\">Par</string>\n    <string name=\"no_update_available\">Nav jaunu atjauninājumu</string>\n    <string name=\"right_to_left\">Labi-kreisi</string>\n    <string name=\"zoom_mode_fit_height\">Ietilpt augstumā</string>\n    <string name=\"retry\">Atkārtot</string>\n    <string name=\"download_complete\">Lejupielādēts</string>\n    <string name=\"downloads\">Lejupielādes</string>\n    <string name=\"by_rating\">Vērtējums</string>\n    <string name=\"pages\">Lapas</string>\n    <string name=\"clear\">Attīrīt</string>\n    <string name=\"remove\">Noņemt</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" izdzēsts no vietējās krātuves</string>\n    <string name=\"save_page\">Saglabāt lapu</string>\n    <string name=\"page_saved\">Saglabāta</string>\n    <string name=\"share_image\">Kopīgot attēlu</string>\n    <string name=\"_import\">Importēt</string>\n    <string name=\"search_on_s\">Meklēt %sā</string>\n    <string name=\"delete_manga\">Izdzēst mangu</string>\n    <string name=\"text_delete_local_manga\">Pilnībā izdzēst \\\"%s\\\" no ierīces?</string>\n    <string name=\"reader_settings\">Lasīšanas iestatījumi</string>\n    <string name=\"error\">Kļūda</string>\n    <string name=\"clear_thumbs_cache\">Attīrīt vāka attēlu sīkdatnes</string>\n    <string name=\"clear_search_history\">Attīrīt meklēšanas vēsturi</string>\n    <string name=\"search_history_cleared\">Attīrīts</string>\n    <string name=\"external_storage\">Ārējā krātuve</string>\n    <string name=\"domain\">Domēns</string>\n    <string name=\"app_update_available\">Ir pieejama jauna aplikācijas versija</string>\n    <string name=\"open_in_browser\">Atvērt pārlūkprogrammā</string>\n    <string name=\"notifications_settings\">Paziņojumu iestatījumi</string>\n    <string name=\"notification_sound\">Paziņojuma skaņa</string>\n    <string name=\"light_indicator\">LED rādītājs</string>\n    <string name=\"text_empty_holder_primary\">Šeit ir vientuļi…</string>\n    <string name=\"text_search_holder_secondary\">Pamēģini pārformulēt meklējumu.</string>\n    <string name=\"recent_manga\">Nesenie</string>\n    <string name=\"updates\">Atjauninājumi</string>\n    <string name=\"text_feed_holder\">Jaunas daļas no tā, ko tu lasi parādās šeit</string>\n    <string name=\"search_results\">Meklēšanas rezultāti</string>\n    <string name=\"new_version_s\">Jauna versija: %s</string>\n    <string name=\"size_s\">Izmērs: %s</string>\n    <string name=\"clear_updates_feed\">Attīrīt atjauninājumus</string>\n    <string name=\"updates_feed_cleared\">Attīrīts</string>\n    <string name=\"update\">Atjaunināt</string>\n    <string name=\"app_version\">Versija %s</string>\n    <string name=\"scale_mode\">Centrojums</string>\n    <string name=\"zoom_mode_fit_center\">Ietilpt centrā</string>\n    <string name=\"zoom_mode_fit_width\">Ietilpt platumā</string>\n    <string name=\"zoom_mode_keep_start\">Atstāt sākumā</string>\n    <string name=\"backup_restore\">Dublēt un atjaunot</string>\n    <string name=\"create_backup\">Dublēt datus</string>\n    <string name=\"restore_backup\">Atjaunot no dublējuma</string>\n    <string name=\"data_restored\">Atjaunots</string>\n    <string name=\"file_not_found\">Fails nav atrasts</string>\n    <string name=\"backup_information\">Tu vari dublēt savu vēsturi un patīokšos un tos atjaunot</string>\n    <string name=\"just_now\">Šobrīd</string>\n    <string name=\"long_ago\">Aiz trejdeviņiem gadiem</string>\n    <string name=\"group\">Grupa</string>\n    <string name=\"tap_to_try_again\">Uzspied lai mēģinātu atkal</string>\n    <string name=\"silent\">Klusums</string>\n    <string name=\"captcha_required\">Vajadzīgs izpildīt CAPTCHA</string>\n    <string name=\"captcha_solve\">Risināt</string>\n    <string name=\"clear_cookies\">Attīrīt sīkdatnes</string>\n    <string name=\"cookies_cleared\">Visas sīkdatnes noņemtas</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-lzh/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d 項</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">新 %1$d 章</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d 章</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d 分前</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d 時前</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d 日前</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d 月前</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d 時</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d 分</item>\n        <item quantity=\"other\"></item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-lzh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"list\">列</string>\n    <string name=\"history\">史</string>\n    <string name=\"error_occurred\">有誤</string>\n    <string name=\"network_error\">網絡有誤</string>\n    <string name=\"details\">詳</string>\n    <string name=\"chapters\">章</string>\n    <string name=\"detailed_list\">詳列</string>\n    <string name=\"grid\">格</string>\n    <string name=\"list_mode\">列示</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"computing_\">計算中……</string>\n    <string name=\"chapter_d_of_d\">%2$d章之%1$d</string>\n    <string name=\"close\">閉</string>\n    <string name=\"clear_history\">除史</string>\n    <string name=\"nothing_found\">無所獲</string>\n    <string name=\"history_is_empty\">未有舊錄</string>\n    <string name=\"read\">閲</string>\n    <string name=\"remove\">除</string>\n    <string name=\"you_have_not_favourites_yet\">未有所藏</string>\n    <string name=\"add_to_favourites\">錄為所藏</string>\n    <string name=\"add\">增</string>\n    <string name=\"save\">存</string>\n    <string name=\"create_shortcut\">增設捷徑</string>\n    <string name=\"search\">尋</string>\n    <string name=\"by_name\">名</string>\n    <string name=\"pages\">頁</string>\n    <string name=\"clear\">除</string>\n    <string name=\"share_s\">共享 %s</string>\n    <string name=\"share\">共享</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ml/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d ഒരു പുതിയ അദ്ധ്യായം</item>\n        <item quantity=\"other\">%1$d ഒരുപാട് പുതിയ അധ്യായങ്ങൾ</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d മിനിറ്റ്</item>\n        <item quantity=\"other\">%1$d മിനിറ്റ്</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ml/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"history\">ചരിത്രം</string>\n    <string name=\"chapters\">അദ്ധ്യായങ്ങൾ</string>\n    <string name=\"settings\">ക്രമീകരണങ്ങൾ</string>\n    <string name=\"favourites\">പ്രിയപ്പെട്ടവ</string>\n    <string name=\"details\">വിശദാംശങ്ങൾ</string>\n    <string name=\"detailed_list\">വിശദമായ ലിസ്റ്റ്</string>\n    <string name=\"chapter_d_of_d\">%2$d-ൻ്റെ %1$d അധ്യായം</string>\n    <string name=\"try_again\">വീണ്ടും ശ്രമിക്കുക</string>\n    <string name=\"you_have_not_favourites_yet\">ഇതുവരെ പ്രിയപ്പെട്ടവയൊന്നുമില്ല</string>\n    <string name=\"add_to_favourites\">ഇത് പ്രിയപ്പെട്ടതാക്കുക</string>\n    <string name=\"add_new_category\">പുതിയ വിഭാഗം</string>\n    <string name=\"add\">ചേർക്കുക</string>\n    <string name=\"share\">പങ്കിടുക</string>\n    <string name=\"share_s\">%s പങ്കിടുക</string>\n    <string name=\"search\">തിരയുക</string>\n    <string name=\"no_description\">വിവരണമില്ല</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"search_on_s\">%s-ൽ തിരയുക</string>\n    <string name=\"_continue\">തുടരുക</string>\n    <string name=\"not_available\">ലഭ്യമല്ല</string>\n    <string name=\"protect_application_summary\">കൊട്ടാറ്റ്സു ആരംഭിക്കുമ്പോൾ പാസ്‌വേഡ് ചോദിക്കുക</string>\n    <string name=\"repeat_password\">പാസ്‌വേഡ് ആവർത്തിക്കുക</string>\n    <string name=\"passwords_mismatch\">പൊരുത്തപ്പെടാത്ത പാസ്‌വേഡുകൾ</string>\n    <string name=\"password_length_hint\">പാസ്‌വേഡിൽ കുറഞ്ഞത് നാലു അക്ഷരങ്ങൾ ഉണ്ടായിരിക്കണം</string>\n    <string name=\"welcome\">സ്വാഗതം</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ms/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d bab baharu</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d bab</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d minit lalu</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d jam lalu</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d hari lalu</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d bulan lalu</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d item</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d jam</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d minit</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ms/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"enter_password\">Masukkan kata laluan</string>\n    <string name=\"protect_application_summary\">Minta kata laluan ketika memulakan Kotatsu</string>\n    <string name=\"repeat_password\">Ulang kata laluan</string>\n    <string name=\"zoom_mode_fit_center\">Muat di tengah</string>\n    <string name=\"create_category\">Kategori baharu</string>\n    <string name=\"scale_mode\">Mod skala</string>\n    <string name=\"zoom_mode_fit_height\">Muat ke ketinggian</string>\n    <string name=\"favourites\">Kegemaran</string>\n    <string name=\"history\">Sejarah</string>\n    <string name=\"network_error\">Ralat rangkaian</string>\n    <string name=\"details\">Perincian</string>\n    <string name=\"chapters\">Bab</string>\n    <string name=\"list\">Senarai</string>\n    <string name=\"detailed_list\">Senarai terperinci</string>\n    <string name=\"grid\">Grid</string>\n    <string name=\"list_mode\">Mod senarai</string>\n    <string name=\"settings\">Tetapan</string>\n    <string name=\"remote_sources\">Sumber Manga</string>\n    <string name=\"loading_\">Memuat…</string>\n    <string name=\"computing_\">Mengira…</string>\n    <string name=\"chapter_d_of_d\">Bab %1$d daripada %2$d</string>\n    <string name=\"close\">Tutup</string>\n    <string name=\"try_again\">Cuba semula</string>\n    <string name=\"clear_history\">Padam sejarah</string>\n    <string name=\"history_is_empty\">Tiada sejarah lagi</string>\n    <string name=\"read\">Baca</string>\n    <string name=\"add_new_category\">Kategori baharu</string>\n    <string name=\"add\">Tambah</string>\n    <string name=\"save\">Simpan</string>\n    <string name=\"share\">Kongsi</string>\n    <string name=\"create_shortcut\">Buat pintasan</string>\n    <string name=\"search\">Cari</string>\n    <string name=\"search_manga\">Cari manga</string>\n    <string name=\"manga_downloading_\">Memuat turun…</string>\n    <string name=\"download_complete\">Dimuat turun</string>\n    <string name=\"downloads\">Muat turun</string>\n    <string name=\"by_name\">Nama</string>\n    <string name=\"popular\">Populer</string>\n    <string name=\"updated\">Dikemaskini</string>\n    <string name=\"newest\">Terbaharu</string>\n    <string name=\"by_rating\">Peringkat</string>\n    <string name=\"sort_order\">Turutan isihan</string>\n    <string name=\"filter\">Tapis</string>\n    <string name=\"light\">Terang</string>\n    <string name=\"dark\">Gelap</string>\n    <string name=\"follow_system\">Ikut sistem</string>\n    <string name=\"pages\">Muka surat</string>\n    <string name=\"clear\">Kosongkan</string>\n    <string name=\"remove\">Buang</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" dibuang daripada storan tempatan</string>\n    <string name=\"save_page\">Simpan muka surat</string>\n    <string name=\"share_image\">Kongsi imej</string>\n    <string name=\"_import\">Import</string>\n    <string name=\"text_file_not_supported\">Pilih antara fail ZIP atau CBZ.</string>\n    <string name=\"no_description\">Tiada huraian</string>\n    <string name=\"clear_pages_cache\">Kosongkan cache muka surat</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"_continue\">Sambung</string>\n    <string name=\"error\">Ralat</string>\n    <string name=\"clear_search_history\">Kosongkan sejarah carian</string>\n    <string name=\"search_history_cleared\">Dikosongkan</string>\n    <string name=\"internal_storage\">Storan dalaman</string>\n    <string name=\"external_storage\">Storan luaran</string>\n    <string name=\"app_update_available\">Sebuah versi baru apl tersedia</string>\n    <string name=\"open_in_browser\">Buka dalam pelayar web</string>\n    <string name=\"notifications\">Notifikasi</string>\n    <string name=\"new_chapters\">Bab baharu</string>\n    <string name=\"notification_sound\">Suara pemberitahuan</string>\n    <string name=\"light_indicator\">Penunjuk LED</string>\n    <string name=\"vibration\">Getaran</string>\n    <string name=\"remove_category\">Buang</string>\n    <string name=\"text_history_holder_primary\">Apa yang anda baca akan dipaparkan di sini</string>\n    <string name=\"text_local_holder_primary\">Simpan sesuatu dahulu</string>\n    <string name=\"text_local_holder_secondary\">Simpan sesuatu daripada katalog dalam talian atau import daripada fail.</string>\n    <string name=\"manga_shelf\">Rak</string>\n    <string name=\"recent_manga\">Terbaharu</string>\n    <string name=\"cannot_find_available_storage\">Tiada storan tersedia</string>\n    <string name=\"other_storage\">Storan lain</string>\n    <string name=\"done\">Tamat</string>\n    <string name=\"favourites_category_empty\">Kategori kosong</string>\n    <string name=\"read_later\">Baca kemudian</string>\n    <string name=\"search_results\">Hasil carian</string>\n    <string name=\"track_sources\">Cari kemas kini</string>\n    <string name=\"protect_application\">Lindung apl</string>\n    <string name=\"passwords_mismatch\">Kata laluan tidak padan</string>\n    <string name=\"about\">Tentang</string>\n    <string name=\"app_version\">Versi %s</string>\n    <string name=\"check_for_updates\">Semak kemas kini</string>\n    <string name=\"no_update_available\">Tiada kemas kini tersedia</string>\n    <string name=\"right_to_left\">Kanan-ke-kiri</string>\n    <string name=\"zoom_mode_fit_width\">Muat ke kelebaran</string>\n    <string name=\"zoom_mode_keep_start\">Kekalkan di permulaan</string>\n    <string name=\"black_dark_theme\">Hitam</string>\n    <string name=\"black_dark_theme_summary\">Menggunakan kurang kuasa pada skrin AMOLED</string>\n    <string name=\"backup_restore\">Penyandaran dan pemulihan</string>\n    <string name=\"create_backup\">Buat penyandaran data</string>\n    <string name=\"restore_backup\">Pulihkan daripada sandaran</string>\n    <string name=\"data_restored\">Dipulihkan</string>\n    <string name=\"preparing_\">Bersedia…</string>\n    <string name=\"file_not_found\">Fail tidak ditemui</string>\n    <string name=\"data_restored_success\">Semua data telah dipulih</string>\n    <string name=\"data_restored_with_errors\">Data telah dipulih, namun terdapat ralat</string>\n    <string name=\"just_now\">Sebentar tadi</string>\n    <string name=\"yesterday\">Semalam</string>\n    <string name=\"long_ago\">Lama dahulu</string>\n    <string name=\"group\">Kumpul</string>\n    <string name=\"today\">Hari ini</string>\n    <string name=\"tap_to_try_again\">Ketik untuk cuba semula</string>\n    <string name=\"silent\">Senyap</string>\n    <string name=\"captcha_required\">CAPTCHA diperlukan</string>\n    <string name=\"captcha_solve\">Selesai</string>\n    <string name=\"clear_cookies\">Kosongkan kuki</string>\n    <string name=\"cookies_cleared\">Semua kuki telah dibuang</string>\n    <string name=\"clear_feed\">Kosongkan siaran</string>\n    <string name=\"text_clear_updates_feed_prompt\">Kosongkan semua sejarah kemas kini selama-lamanya?</string>\n    <string name=\"check_for_new_chapters\">Semak untuk bab baharu</string>\n    <string name=\"reverse\">Songsang</string>\n    <string name=\"sign_in\">Log masuk</string>\n    <string name=\"auth_required\">Log masuk untuk melihat kandungan ini</string>\n    <string name=\"next\">Seterusnya</string>\n    <string name=\"protect_application_subtitle\">Masukkan kata laluan untuk memulakan apl dengan</string>\n    <string name=\"confirm\">Sah</string>\n    <string name=\"password_length_hint\">Kata laluan mesti mempunyai 4 aksara atau lebih</string>\n    <string name=\"welcome\">Selamat Datang</string>\n    <string name=\"read_more\">Baca lagi</string>\n    <string name=\"auth_complete\">Diizinkan</string>\n    <string name=\"auth_not_supported_by\">Log masuk pada %s tidak disokong</string>\n    <string name=\"text_clear_cookies_prompt\">Anda akan dilog keluar daripada semua sumber</string>\n    <string name=\"genres\">Genre</string>\n    <string name=\"state_finished\">Selesai</string>\n    <string name=\"state_ongoing\">Sedang berlangsung</string>\n    <string name=\"system_default\">Lalai</string>\n    <string name=\"exclude_nsfw_from_history\">Kecualikan manga NSFW daripada sejarah</string>\n    <string name=\"show_pages_numbers\">Muka surat bernombor</string>\n    <string name=\"screenshots_policy\">Polisi tangkapan skrin</string>\n    <string name=\"screenshots_allow\">Benarkan</string>\n    <string name=\"screenshots_block_nsfw\">Sekat pada NSFW</string>\n    <string name=\"screenshots_block_all\">Sentiasa sekat</string>\n    <string name=\"suggestions\">Pengesyoran</string>\n    <string name=\"suggestions_enable\">Bolehkan pengesyoran</string>\n    <string name=\"suggestions_summary\">Syor manga berdasarkan keutamaan</string>\n    <string name=\"text_suggestion_holder\">Mula baca manga dan anda akan mendapat pengesyoran peribadi</string>\n    <string name=\"enabled\">Dibolehkan</string>\n    <string name=\"disabled\">Dilumpuhkan</string>\n    <string name=\"reset_filter\">Set semula tapisan</string>\n    <string name=\"never\">Jangan</string>\n    <string name=\"only_using_wifi\">Hanya pada Wi-Fi</string>\n    <string name=\"always\">Sentiasa</string>\n    <string name=\"preload_pages\">Pramuat muka surat</string>\n    <string name=\"logged_in_as\">Dilog masuk sebagai %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Pelbagai bahasa</string>\n    <string name=\"search_chapters\">Cari bab</string>\n    <string name=\"chapters_empty\">Tiada bab dalam manga ini</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Penampilan</string>\n    <string name=\"suggestions_updating\">Pengemaskinian pengesyoran</string>\n    <string name=\"suggestions_excluded_genres_summary\">Tentukan genre yang anda tidak mahu lihat dalam pengesyoran</string>\n    <string name=\"text_delete_local_manga_batch\">Buang item yang dipilih daripada peranti selama-lamanya?</string>\n    <string name=\"removal_completed\">Pembuangan selesai</string>\n    <string name=\"local_manga_processing\">Pemprosesan manga disimpan</string>\n    <string name=\"canceled\">Dibatalkan</string>\n    <string name=\"account_already_exists\">Akaun telahpun wujud</string>\n    <string name=\"back\">Kembali</string>\n    <string name=\"sync\">Penyegerakkan</string>\n    <string name=\"sync_title\">Segerakkan data anda</string>\n    <string name=\"email_enter_hint\">Masukkan email anda untuk menyambung</string>\n    <string name=\"hide\">Sembunyi</string>\n    <string name=\"new_sources_text\">Sumber manga baharu tersedia</string>\n    <string name=\"check_new_chapters_title\">Semak untuk bab baharu dan beritahu mengenainya</string>\n    <string name=\"show_notification_new_chapters_on\">Anda akan menerima pemberitahuan mengenai kemas kini manga yang anda sedang baca</string>\n    <string name=\"show_notification_new_chapters_off\">Anda tidak akan menerima pemberitahuan namun bab baharu akan ditonjolkan dalam senarai</string>\n    <string name=\"edit_category\">Sunting kategori</string>\n    <string name=\"tracking\">Penjejakan</string>\n    <string name=\"empty_favourite_categories\">Tiada kategori kegemaran</string>\n    <string name=\"logout\">Log keluar</string>\n    <string name=\"bookmark_add\">Tambah penanda</string>\n    <string name=\"bookmark_remove\">Buang penanda</string>\n    <string name=\"bookmark_removed\">Penanda dibuang</string>\n    <string name=\"bookmark_added\">Penanda ditambah</string>\n    <string name=\"undo\">Buat asal</string>\n    <string name=\"removed_from_history\">Dibuang daripada sejarah</string>\n    <string name=\"dns_over_https\">DNS atas HTTPS</string>\n    <string name=\"default_mode\">Mod lalai</string>\n    <string name=\"detect_reader_mode\">Autokesan mod pembaca</string>\n    <string name=\"disable_battery_optimization\">Lumpuhkan pengoptimuman bateri</string>\n    <string name=\"disable_battery_optimization_summary\">Membantu penyemakan kemas kini dalam latar belakang</string>\n    <string name=\"send\">Hantar</string>\n    <string name=\"status_planned\">Dirancang</string>\n    <string name=\"status_reading\">Dibaca</string>\n    <string name=\"status_re_reading\">Dibaca semula</string>\n    <string name=\"status_completed\">Selesai</string>\n    <string name=\"status_on_hold\">Dijeda</string>\n    <string name=\"status_dropped\">Dihentikan</string>\n    <string name=\"disable_all\">Lumpuhkan semua</string>\n    <string name=\"use_fingerprint\">Guna cap jari sekiranya tersedia</string>\n    <string name=\"appwidget_shelf_description\">Manga daripada kegemaran anda</string>\n    <string name=\"appwidget_recent_description\">Manga yang anda baru baca</string>\n    <string name=\"report\">Lapor</string>\n    <string name=\"show_reading_indicators\">Tunjukkan penunjuk kemajuan pembacaan</string>\n    <string name=\"data_deletion\">Pembuangan data</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga ditanda sebagai NSFW tidak akan ditambahkan dalam sejarah dan kemajuan tidak akan disimpan</string>\n    <string name=\"show_all\">Tunjuk semua</string>\n    <string name=\"invalid_domain_message\">Domain tidak sah</string>\n    <string name=\"select_range\">Pilih julat</string>\n    <string name=\"clear_all_history\">Kosongkan semua sejarah</string>\n    <string name=\"last_2_hours\">2 jam lepas</string>\n    <string name=\"history_cleared\">Sejarah dikosongkan</string>\n    <string name=\"manage\">Urus</string>\n    <string name=\"no_bookmarks_yet\">Tiada penanda lagi</string>\n    <string name=\"no_bookmarks_summary\">Anda boleh membuat penanda ketika membaca manga</string>\n    <string name=\"no_manga_sources\">Tiada sumber manga</string>\n    <string name=\"no_manga_sources_text\">Bolehkan sumber manga untuk membaca manga di dalam talian</string>\n    <string name=\"random\">Rawak</string>\n    <string name=\"categories_delete_confirm\">Adakah anda pasti anda mahu membuang kategori kegemaran yang dipilih? \\nSemua manga di dalamnya akan hilang dan ini tidak boleh diundur semula.</string>\n    <string name=\"reorder\">Susun semula</string>\n    <string name=\"empty\">Kosong</string>\n    <string name=\"explore\">Jelajah</string>\n    <string name=\"confirm_exit\">Tekan Kembali sekali lagi untuk keluar</string>\n    <string name=\"exit_confirmation_summary\">Tekan Kembali dua kali untuk keluar apl</string>\n    <string name=\"exit_confirmation\">Pengesahan keluar</string>\n    <string name=\"pages_cache\">Cache muka surat</string>\n    <string name=\"other_cache\">Cache lain-lain</string>\n    <string name=\"storage_usage\">Penggunaan storan</string>\n    <string name=\"available\">Tersedia</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"removed_from_favourites\">Dibuang daripada kegemaran</string>\n    <string name=\"options\">Pilihan</string>\n    <string name=\"local_storage\">Storan tempatan</string>\n    <string name=\"reader_mode_hint\">Tetapan yang telah dipilih akan diperingati untuk manga ini</string>\n    <string name=\"backup_information\">Anda boleh membuat sandaran sejarah dan kegemaran anda dan pulihkannya</string>\n    <string name=\"default_s\">Lalai: %s</string>\n    <string name=\"text_clear_search_history_prompt\">Buang semua carian terkini selama-lamanya?</string>\n    <string name=\"tracker_warning\">Sesetengah peranti mempunyai kelakuan sistem berlainan, yang mampu merosakkan tugasan latar belakang.</string>\n    <string name=\"queued\">Dibariskan</string>\n    <string name=\"chapter_is_missing\">Bab ini hilang</string>\n    <string name=\"backup_saved\">Sandaran disimpan</string>\n    <string name=\"about_app_translation\">Terjemahan</string>\n    <string name=\"about_app_translation_summary\">Terjemahkan apl ini</string>\n    <string name=\"error_occurred\">Sebuah ralat telah berlaku</string>\n    <string name=\"share_s\">Kongsi %s</string>\n    <string name=\"nothing_found\">Tiada dijumpai</string>\n    <string name=\"suggestions_info\">Semua data hanya dianalisa secara tempatan pada peranti ini dan tidak dihantar ke mana-mana.</string>\n    <string name=\"you_have_not_favourites_yet\">Tiada kegemaran lagi</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Jangan syor manga NSFW</string>\n    <string name=\"processing_\">Memproses…</string>\n    <string name=\"onboard_text\">Pilih bahasa untuk manga yang anda mahu baca. Anda boleh mengubah ini kemudian melalui tetapan.</string>\n    <string name=\"add_to_favourites\">Tambah dalam kegemaran</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"suggestions_excluded_genres\">Kecualikan genre</string>\n    <string name=\"download_slowdown_summary\">Membantu mengelakkan alamat IP anda disekat</string>\n    <string name=\"delete\">Buang</string>\n    <string name=\"search_on_s\">Cari pada %s</string>\n    <string name=\"clear_thumbs_cache\">Kosongkan cache imej kenit</string>\n    <string name=\"chapters_will_removed_background\">Bab akan dibuang dalam latar belakang</string>\n    <string name=\"operation_not_supported\">Operasi ini tidak disokong</string>\n    <string name=\"delete_manga\">Buang manga</string>\n    <string name=\"page_saved\">Halaman disimpan</string>\n    <string name=\"domain\">Domain</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d daripada %2$d pada</string>\n    <string name=\"read_mode\">Mod pembacaan</string>\n    <string name=\"text_delete_local_manga\">Buang \\\"%s\\\" selama-lamanya daripada peranti?</string>\n    <string name=\"reader_settings\">Tetapan pembaca</string>\n    <string name=\"download\">Muat turun</string>\n    <string name=\"notifications_settings\">Tetapan pemberitahuan</string>\n    <string name=\"grid_size\">Saiz grid</string>\n    <string name=\"switch_pages\">Tukar muka surat</string>\n    <string name=\"text_empty_holder_primary\">Ianya agak kosong di sini…</string>\n    <string name=\"text_search_holder_secondary\">Cuba untuk merumuskan semula carian.</string>\n    <string name=\"updates\">Kemas kini</string>\n    <string name=\"all_favourites\">Semua kegemaran</string>\n    <string name=\"favourites_categories\">Kategori kegemaran</string>\n    <string name=\"text_history_holder_secondary\">Cari apa yang boleh dibaca melalui seksyen «Jelajah»</string>\n    <string name=\"pages_animation\">Animasi muka surat</string>\n    <string name=\"manga_save_location\">Folder muat turun</string>\n    <string name=\"not_available\">Tidak tersedia</string>\n    <string name=\"notifications_enable\">Bolehkan pemberitahuan</string>\n    <string name=\"name\">Nama</string>\n    <string name=\"edit\">Sunting</string>\n    <string name=\"bookmarks\">Penanda</string>\n    <string name=\"clear_cookies_summary\">Boleh membantu sekiranya terdapat isu. Semua keizinan akan dinyahsahkan</string>\n    <string name=\"detect_reader_mode_summary\">Kesan sekiranya manga adalah webtoon secara automatik</string>\n    <string name=\"show_reading_indicators_summary\">Tunjuk peratusan dibaca dalam sejarah dan kegemaran</string>\n    <string name=\"bookmarks_removed\">Penanda dibuang</string>\n    <string name=\"crash_text\">Sebuah ralat telah berlaku. Sila hantar laporan pepijat kepada pembangun untuk membantu kami membetulkannya.</string>\n    <string name=\"saved_manga\">Manga disimpan</string>\n    <string name=\"text_feed_holder\">Bab-bab baharu bacaan anda ditunjukkan di sini</string>\n    <string name=\"size_s\">Saiz: %s</string>\n    <string name=\"new_version_s\">Versi baharu: %s</string>\n    <string name=\"clear_updates_feed\">Kosongkan siaran kemaskini</string>\n    <string name=\"updates_feed_cleared\">Dikosongkan</string>\n    <string name=\"wrong_password\">Kata laluan salah</string>\n    <string name=\"rotate_screen\">Putar skrin</string>\n    <string name=\"update\">Kemas kini</string>\n    <string name=\"feed_will_update_soon\">Kemaskini siaran akan bermula sebentar</string>\n    <string name=\"dont_check\">Jangan semak</string>\n    <string name=\"advanced\">lanjutan</string>\n    <string name=\"manga_list\">Senarai manga</string>\n    <string name=\"error_corrupted_file\">Data tidak sah dikembalikan atau fail rosak</string>\n    <string name=\"on_device\">Pada peranti</string>\n    <string name=\"directories\">Panduan</string>\n    <string name=\"last_read\">Bacaan terakhir</string>\n    <string name=\"automatic\">Automatik</string>\n    <string name=\"retry\">Mencoba kembali</string>\n    <string name=\"pages_saved\">Halaman disimpan</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Tidak ada manga yang cocok dengan filter yang Anda pilih</string>\n    <string name=\"chapters_grid_view\">Tampilan kisi</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"download_slowdown\">Unduhan melambat</string>\n    <string name=\"invalid_server_address_message\">Alamat pelayan tidak sah</string>\n    <string name=\"not_found_404\">Kandungan tidak ditemui atau dialih keluar</string>\n    <string name=\"import_completed_hint\">Anda boleh memadamkan fail asal daripada storan untuk menjimatkan ruang</string>\n    <string name=\"reader_control_ltr_summary\">Jangan laraskan arah penukaran halaman kepada mod pembaca, e. g. menekan kekunci kanan sentiasa beralih ke halaman seterusnya. Pilihan ini hanya mempengaruhi peranti input perkakasan</string>\n    <string name=\"reader_control_ltr\">Kawalan pembaca ergonomik</string>\n    <string name=\"history_shortcuts_summary\">Jadikan manga terbaru tersedia dengan menekan lama pada ikon aplikasi</string>\n    <string name=\"import_completed\">Import selesai</string>\n    <string name=\"history_shortcuts\">Tunjukkan pintasan komik terbaru</string>\n    <string name=\"incognito_mode\">Mod inkognito</string>\n    <string name=\"no_chapters\">Tiada bab</string>\n    <string name=\"automatic_scroll\">Penatalan automatik</string>\n    <string name=\"reader_info_bar\">Tunjukkan bar maklumat dalam pembaca</string>\n    <string name=\"comics_archive\">Arkib komik</string>\n    <string name=\"folder_with_images\">Folder dengan imej</string>\n    <string name=\"importing_manga\">Mengimport komik</string>\n    <string name=\"import_will_start_soon\">Import akan bermula tidak lama lagi</string>\n    <string name=\"feed\">Suapan</string>\n    <string name=\"manga_error_description_pattern\">Butiran ralat:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Cuba &lt;a href=%2$s&gt;buka manga dalam pelayar web&lt;/a&gt; untuk memastikan ia tersedia pada sumbernya&lt;br&gt;2. Pastikan anda menggunakan &lt;a href=kotatsu://about&gt;versi terbaharu Kotatsu&lt;/a&gt;&lt;br&gt;3. Jika tersedia, hantar laporan ralat kepada pembangun.</string>\n    <string name=\"color_correction\">Pembetulan warna</string>\n    <string name=\"brightness\">Kecerahan</string>\n    <string name=\"contrast\">Berbeza</string>\n    <string name=\"reset\">Tetapkan semula</string>\n    <string name=\"text_unsaved_changes_prompt\">Simpan atau buang perubahan yang belum disimpan?</string>\n    <string name=\"discard\">Buang</string>\n    <string name=\"error_no_space_left\">Tiada ruang yang tinggal pada peranti</string>\n    <string name=\"reader_slider\">Tunjukkan peluncur penukaran halaman</string>\n    <string name=\"reader_info_pattern\">Bab %1$d/%2$d Hlm. %3$d/%4$d</string>\n    <string name=\"webtoon_zoom\">Webtoon zum</string>\n    <string name=\"network_unavailable\">Rangkaian tidak tersedia</string>\n    <string name=\"network_unavailable_hint\">Hidupkan rangkaian Wi-Fi atau mudah alih untuk membaca manga dalam talian</string>\n    <string name=\"server_error\">Ralat Side Server (%1$d). Sila cuba lagi kemudian</string>\n    <string name=\"clear_new_chapters_counters\">Juga jelas maklumat mengenai bab baru</string>\n    <string name=\"compact\">Padat</string>\n    <string name=\"source_disabled\">Sumber dilumpuhkan</string>\n    <string name=\"prefetch_content\">Kandungan Preloading</string>\n    <string name=\"mark_as_current\">Tandakan sebagai semasa</string>\n    <string name=\"language\">Bahasa</string>\n    <string name=\"share_logs\">Kongsi log</string>\n    <string name=\"enable_logging\">Dayakan pembalakan</string>\n    <string name=\"enable_logging_summary\">Catat beberapa tindakan untuk tujuan debug. Jangan menghidupkannya jika anda tidak pasti apa yang anda lakukan</string>\n    <string name=\"show_suspicious_content\">Tunjukkan kandungan yang mencurigakan</string>\n    <string name=\"theme_name_dynamic\">Dinamik</string>\n    <string name=\"theme_name_expressive\">Ekspresif (ujian)</string>\n    <string name=\"color_theme\">Skema warna</string>\n    <string name=\"show_in_grid_view\">Tunjukkan dalam paparan grid</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"nothing_here\">Tidak ada apa -apa di sini</string>\n    <string name=\"scrobbling_empty_hint\">Untuk Melacak Progres Membaca, Pilih Menu → LaCak Di Layar Butiran Manga.</string>\n    <string name=\"services\">Perkhidmatan</string>\n    <string name=\"allow_unstable_updates\">Benarkan kemas kini yang tidak stabil</string>\n    <string name=\"allow_unstable_updates_summary\">Terima pemberitahuan mengenai binaan yang tidak stabil</string>\n    <string name=\"download_started\">Muat turun bermula</string>\n    <string name=\"got_it\">Mendapatnya</string>\n    <string name=\"sources_reorder_tip\">Ketik dan tahan item untuk menyusun semula mereka</string>\n    <string name=\"user_agent\">Pengepala useragent</string>\n    <string name=\"settings_apply_restart_required\">Sila mulakan semula permohonan untuk menerapkan perubahan ini</string>\n    <string name=\"comics_archive_import_description\">Anda boleh memilih satu atau lebih fail .cbz atau .zip, setiap fail akan diiktiraf sebagai manga yang berasingan.</string>\n    <string name=\"folder_with_images_import_description\">Anda boleh memilih direktori dengan arkib atau imej. Setiap arkib (atau subdirektori) akan diiktiraf sebagai bab.</string>\n    <string name=\"speed\">Kelajuan</string>\n    <string name=\"show_on_shelf\">Tunjukkan di rak</string>\n    <string name=\"sync_auth_hint\">Anda boleh masuk ke akaun yang ada atau membuat yang baru</string>\n    <string name=\"find_similar\">Cari serupa</string>\n    <string name=\"sync_settings\">Tetapan Penyegerakan</string>\n    <string name=\"server_address\">Alamat pelayan</string>\n    <string name=\"sync_host_description\">Anda boleh menggunakan pelayan penyegerakan sendiri atau lalai. Jangan ubah ini jika anda tidak pasti apa yang anda lakukan.</string>\n    <string name=\"ignore_ssl_errors\">Abaikan kesilapan SSL</string>\n    <string name=\"mirror_switching\">Pilih cermin secara automatik</string>\n    <string name=\"mirror_switching_summary\">Tukar domain secara automatik untuk sumber manga atas kesilapan jika cermin tersedia</string>\n    <string name=\"pause\">Jeda</string>\n    <string name=\"resume\">Teruskan</string>\n    <string name=\"paused\">Jeda</string>\n    <string name=\"remove_completed\">Keluarkan selesai</string>\n    <string name=\"cancel_all\">Batalkan semua</string>\n    <string name=\"downloads_wifi_only\">Muat turun Hanya melalui Wi-Fi</string>\n    <string name=\"downloads_wifi_only_summary\">Berhenti memuat turun semasa beralih ke rangkaian mudah alih</string>\n    <string name=\"suggestion_manga\">Cadangan: %s</string>\n    <string name=\"suggestions_notifications_summary\">Kadang -kadang menunjukkan pemberitahuan dengan manga yang dicadangkan</string>\n    <string name=\"more\">Lebih</string>\n    <string name=\"enable\">Membolehkan</string>\n    <string name=\"no_thanks\">Tidak terima kasih</string>\n    <string name=\"cancel_all_downloads_confirm\">Semua muat turun aktif akan dibatalkan, data yang dimuat turun sebahagiannya akan hilang</string>\n    <string name=\"remove_completed_downloads_confirm\">Sejarah muat turun anda akan dipadamkan secara kekal. Tiada fail yang dimuat turun akan terjejas</string>\n    <string name=\"text_downloads_list_holder\">Anda tidak mempunyai muat turun</string>\n    <string name=\"downloads_resumed\">Muat turun telah diteruskan</string>\n    <string name=\"downloads_paused\">Muat turun telah dijeda</string>\n    <string name=\"downloads_removed\">Muat turun telah dikeluarkan</string>\n    <string name=\"downloads_cancelled\">Muat turun telah dibatalkan</string>\n    <string name=\"suggestions_enable_prompt\">Adakah anda ingin menerima cadangan manga yang diperibadikan?</string>\n    <string name=\"web_view_unavailable\">WebView Tidak Tersedia: Periksa sama ada pembekal WebView dipasang</string>\n    <string name=\"clear_network_cache\">Cache Rangkaian cache</string>\n    <string name=\"type\">Jenis</string>\n    <string name=\"address\">Alamat</string>\n    <string name=\"port\">Pelabuhan</string>\n    <string name=\"proxy\">Proksi</string>\n    <string name=\"invalid_value_message\">Nilai tidak sah</string>\n    <string name=\"email_password_enter_hint\">Masukkan e -mel dan kata laluan anda untuk diteruskan</string>\n    <string name=\"downloaded\">Dimuat turun</string>\n    <string name=\"images_proxy_title\">Proksi Pengoptimuman Imej</string>\n    <string name=\"images_procy_description\">Gunakan perkhidmatan WSRV.NL untuk mengurangkan penggunaan lalu lintas dan mempercepat pemuatan imej jika boleh</string>\n    <string name=\"invert_colors\">Warna terbalik</string>\n    <string name=\"username\">Nama pengguna</string>\n    <string name=\"password\">Kata laluan</string>\n    <string name=\"authorization_optional\">Kebenaran (pilihan)</string>\n    <string name=\"invalid_port_number\">Nombor port tidak sah</string>\n    <string name=\"network\">Rangkaian</string>\n    <string name=\"data_and_privacy\">Data dan privasi</string>\n    <string name=\"restore_summary\">Pulihkan sandaran yang dibuat sebelum ini</string>\n    <string name=\"webtoon_zoom_summary\">Benarkan isyarat zum masuk dalam mod webtoon</string>\n    <string name=\"pull_to_prev_chapter\">Siaran untuk membuka bab sebelumnya</string>\n    <string name=\"pull_to_next_chapter\">Siaran untuk membuka bab seterusnya</string>\n    <string name=\"pull_top_no_prev\">Tiada bab sebelumnya</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"pull_bottom_no_next\">Tiada bab seterusnya</string>\n    <string name=\"reader_info_bar_summary\">Tunjukkan masa sekarang dan kemajuan membaca di bahagian atas skrin</string>\n    <string name=\"show_pages_numbers_summary\">Tunjukkan nombor halaman di sudut bawah</string>\n    <string name=\"clear_source_cookies_summary\">Clear cookies untuk domain yang ditentukan sahaja. Dalam kebanyakan kes akan membatalkan kebenaran</string>\n    <string name=\"download_option_all_chapters\">Semua bab dengan terjemahan %s</string>\n    <string name=\"download_option_whole_manga\">Seluruh manga</string>\n    <string name=\"download_option_first_n_chapters\">Pertama %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Seterusnya belum dibaca %s</string>\n    <string name=\"download_option_all_unread\">Semua bab yang belum dibaca</string>\n    <string name=\"download_option_all_unread_b\">Semua bab yang belum dibaca (%s)</string>\n    <string name=\"download_option_manual_selection\">Pilih bab secara manual</string>\n    <string name=\"pick_custom_directory\">Pilih direktori tersuai</string>\n    <string name=\"no_access_to_file\">Anda tidak mempunyai akses ke fail atau direktori ini</string>\n    <string name=\"local_manga_directories\">Direktori manga tempatan</string>\n    <string name=\"description\">Penerangan</string>\n    <string name=\"this_month\">Bulan ini</string>\n    <string name=\"voice_search\">Carian suara</string>\n    <string name=\"related_manga\">Manga yang berkaitan</string>\n    <string name=\"color_light\">Cahaya</string>\n    <string name=\"color_dark\">Gelap</string>\n    <string name=\"color_white\">Putih</string>\n    <string name=\"color_black\">Hitam</string>\n    <string name=\"background\">Latar belakang</string>\n    <string name=\"data_not_restored\">Data tidak dipulihkan</string>\n    <string name=\"data_not_restored_text\">Pastikan anda telah memilih fail sandaran yang betul</string>\n    <string name=\"manage_categories\">Menguruskan kategori</string>\n    <string name=\"suggestions_wifi_only_summary\">Jangan mengemas kini cadangan menggunakan sambungan rangkaian meter</string>\n    <string name=\"tracker_wifi_only_summary\">Jangan periksa bab baru menggunakan sambungan rangkaian meter</string>\n    <string name=\"search_hint\">Masukkan tajuk manga, genre atau nama sumber</string>\n    <string name=\"progress\">Kemajuan</string>\n    <string name=\"order_added\">Ditambah</string>\n    <string name=\"show\">Tunjukkan</string>\n    <string name=\"captcha_required_summary\">%s memerlukan captcha dapat diselesaikan untuk berfungsi dengan baik</string>\n    <string name=\"languages\">Bahasa</string>\n    <string name=\"unknown\">Tidak diketahui</string>\n    <string name=\"in_progress\">Sedang berjalan</string>\n    <string name=\"disable_nsfw\">Lumpuhkan NSFW</string>\n    <string name=\"too_many_requests_message\">Terlalu banyak permintaan. Cuba lagi kemudian</string>\n    <string name=\"too_many_requests_message_retry\">Terlalu banyak permintaan. Cuba lagi selepas %s</string>\n    <string name=\"related_manga_summary\">Tunjukkan senarai manga yang berkaitan. Dalam beberapa kes, ia mungkin tidak tepat atau hilang</string>\n    <string name=\"main_screen_sections\">Bahagian skrin utama</string>\n    <string name=\"items_limit_exceeded\">Tidak ada lagi item yang boleh ditambah</string>\n    <string name=\"to_top\">Ke atas</string>\n    <string name=\"moved_to_top\">Berpindah ke atas</string>\n    <string name=\"zoom_out\">Zum keluar</string>\n    <string name=\"zoom_in\">Zum masuk</string>\n    <string name=\"reader_zoom_buttons\">Tunjukkan butang zum</string>\n    <string name=\"reader_zoom_buttons_summary\">Sama ada untuk menunjukkan butang kawalan zum di sudut kanan bawah</string>\n    <string name=\"keep_screen_on\">Teruskan skrin</string>\n    <string name=\"keep_screen_on_summary\">Jangan matikan skrin semasa anda membaca manga</string>\n    <string name=\"state_abandoned\">Jatuh</string>\n    <string name=\"enhanced_colors_summary\">Mengurangkan banding, tetapi mungkin memberi kesan kepada prestasi</string>\n    <string name=\"enhanced_colors\">Mod warna 32-bit</string>\n    <string name=\"suggest_new_sources\">Cadangkan sumber baru selepas kemas kini aplikasi</string>\n    <string name=\"suggest_new_sources_summary\">Segera untuk membolehkan sumber yang baru ditambah setelah mengemas kini aplikasi</string>\n    <string name=\"list_options\">Pilihan Senarai</string>\n    <string name=\"by_relevance\">Kaitan</string>\n    <string name=\"categories\">Kategori</string>\n    <string name=\"online_variant\">Varian dalam talian</string>\n    <string name=\"periodic_backups\">Sandaran berkala</string>\n    <string name=\"backup_frequency\">Kekerapan penciptaan sandaran</string>\n    <string name=\"frequency_every_day\">Setiap hari</string>\n    <string name=\"frequency_every_2_days\">Setiap 2 hari</string>\n    <string name=\"frequency_once_per_week\">Sekali seminggu</string>\n    <string name=\"frequency_twice_per_month\">Dua kali sebulan</string>\n    <string name=\"frequency_once_per_month\">Sekali sebulan</string>\n    <string name=\"periodic_backups_enable\">Dayakan sandaran berkala</string>\n    <string name=\"backups_output_directory\">Direktori Output Backup</string>\n    <string name=\"last_successful_backup\">Sandaran terakhir yang berjaya: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">Putaran skrin kunci</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_comics\">Komik</string>\n    <string name=\"content_type_hentai\">matang</string>\n    <string name=\"content_type_other\">Yang lain</string>\n    <string name=\"sources_catalog\">Sumber Katalog</string>\n    <string name=\"source_enabled\">Sumber didayakan</string>\n    <string name=\"no_manga_sources_catalog_text\">Tidak ada sumber yang terdapat di bahagian ini, atau semuanya mungkin telah ditambah.\\nTinggal</string>\n    <string name=\"no_manga_sources_found\">Tiada sumber manga yang ada yang dijumpai oleh pertanyaan anda</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"manage_sources\">Menguruskan sumber</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"available_d\">Terdapat: %1$d</string>\n    <string name=\"disable_nsfw_summary\">Lumpuhkan sumber NSFW dan sembunyikan manga dewasa dari senarai jika boleh</string>\n    <string name=\"state_paused\">Jeda</string>\n    <string name=\"reader_optimize\">Kurangkan Penggunaan Memori (Beta)</string>\n    <string name=\"reader_optimize_summary\">Kurangkan kualiti halaman offscreen untuk menggunakan memori yang kurang</string>\n    <string name=\"state\">Negeri</string>\n    <string name=\"error_multiple_genres_not_supported\">Penapisan oleh pelbagai genre tidak disokong oleh sumber manga ini</string>\n    <string name=\"error_multiple_states_not_supported\">Penapisan oleh pelbagai negeri tidak disokong oleh sumber manga ini</string>\n    <string name=\"error_search_not_supported\">Carian tidak disokong oleh sumber manga ini</string>\n    <string name=\"downloads_settings_info\">Anda boleh mengaktifkan kelembapan muat turun untuk setiap sumber manga secara individu dalam tetapan sumber jika anda menghadapi masalah dengan menyekat pelayan</string>\n    <string name=\"skip\">Langkau</string>\n    <string name=\"grayscale\">Skala abu-abu</string>\n    <string name=\"globally\">Di seluruh dunia</string>\n    <string name=\"this_manga\">Manga ini</string>\n    <string name=\"color_correction_apply_text\">Tetapan ini boleh digunakan secara global atau hanya kepada manga semasa. Jika digunakan di seluruh dunia, tetapan individu tidak akan ditindih.</string>\n    <string name=\"apply\">Memohon</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Penapisan oleh kedua -dua genre dan locale tidak disokong oleh sumber ini</string>\n    <string name=\"error_filter_states_genre_not_supported\">Penapisan oleh kedua -dua genre dan negeri tidak disokong oleh sumber ini</string>\n    <string name=\"genres_search_hint\">Mula menaip nama genre</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Mungkin membantu mendapatkan muat turun bermula jika anda mempunyai masalah dengannya</string>\n    <string name=\"welcome_text\">Sila pilih sumber kandungan mana yang anda ingin aktifkan. Ini juga boleh dikonfigurasikan kemudian dalam tetapan</string>\n    <string name=\"sync_auth\">Log masuk ke Akaun Menyegerakkan</string>\n    <string name=\"restore\">Pulihkan</string>\n    <string name=\"backup_date_\">Tarikh sandaran: %s</string>\n    <string name=\"state_upcoming\">Akan datang</string>\n    <string name=\"by_name_reverse\">Nama dibalikkan</string>\n    <string name=\"content_rating\">Penilaian Kandungan</string>\n    <string name=\"genres_exclude\">Tidak termasuk genre</string>\n    <string name=\"rating_safe\">Selamat</string>\n    <string name=\"rating_suggestive\">Sugestif</string>\n    <string name=\"rating_adult\">Matang</string>\n    <string name=\"default_tab\">Tab lalai</string>\n    <string name=\"mark_as_completed\">Tanda seperti selesai</string>\n    <string name=\"mark_as_completed_prompt\">Tandakan manga yang dipilih sepenuhnya dibaca?\\n\\nAmaran: Kemajuan bacaan semasa akan hilang.</string>\n    <string name=\"category_hidden_done\">Kategori ini tersembunyi dari skrin utama dan boleh diakses melalui menu → Urus kategori</string>\n    <string name=\"volume_\">Kelantangan %d</string>\n    <string name=\"volume_unknown\">Jumlah yang tidak diketahui</string>\n    <string name=\"incognito_mode_hint\">Kemajuan bacaan anda tidak akan disimpan</string>\n    <string name=\"vertical\">Menegak</string>\n    <string name=\"show_menu\">Tunjukkan menu</string>\n    <string name=\"toggle_ui\">Tunjukkan/sembunyikan UI</string>\n    <string name=\"prev_chapter\">Bab sebelumnya</string>\n    <string name=\"next_chapter\">Bab seterusnya</string>\n    <string name=\"prev_page\">Halaman sebelumnya</string>\n    <string name=\"next_page\">Halaman seterusnya</string>\n    <string name=\"reader_actions\">Tindakan pembaca</string>\n    <string name=\"reader_actions_summary\">Konfigurasikan Tindakan untuk Kawasan Skrin Tappable</string>\n    <string name=\"switch_pages_volume_buttons\">Dayakan butang kelantangan</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Gunakan butang kelantangan untuk menukar halaman</string>\n    <string name=\"reader_navigation_inverted\">Kawalan navigasi terbalik</string>\n    <string name=\"reader_navigation_inverted_summary\">Tukar arah butang kelantangan dan navigasi utama perkakasan arah (kiri/atas/bawah/kanan)</string>\n    <string name=\"tap_action\">Ketik Tindakan</string>\n    <string name=\"long_tap_action\">Tindakan ketuk panjang</string>\n    <string name=\"none\">Tiada</string>\n    <string name=\"config_reset_confirm\">Tetapkan semula tetapan ke nilai lalai? Tindakan ini tidak dapat dibatalkan.</string>\n    <string name=\"use_two_pages_landscape\">Gunakan susun atur dua halaman pada orientasi landskap (beta)</string>\n    <string name=\"default_webtoon_zoom_out\">Webtoon lalai zum keluar</string>\n    <string name=\"fullscreen_mode\">Mod skrin penuh</string>\n    <string name=\"reader_fullscreen_summary\">Menyembunyikan status sistem dan bar navigasi</string>\n    <string name=\"reading_time_estimation\">Tunjukkan anggaran masa bacaan</string>\n    <string name=\"reading_time_estimation_summary\">Nilai anggaran masa mungkin tidak tepat</string>\n    <string name=\"suggestions_unavailable_text\">Ciri Cadangan dilumpuhkan</string>\n    <string name=\"check_for_new_chapters_disabled\">Memeriksa bab baru dilumpuhkan</string>\n    <string name=\"show_labels_in_navbar\">Tunjukkan label di bar navigasi</string>\n    <string name=\"pages_saving\">Menyimpan halaman</string>\n    <string name=\"ask_for_dest_dir_every_time\">Minta direktori destinasi setiap kali</string>\n    <string name=\"default_page_save_dir\">Halaman lalai Simpan direktori</string>\n    <string name=\"remove_from_history\">Keluarkan dari sejarah</string>\n    <string name=\"location\">Lokasi</string>\n    <string name=\"preferred_download_format\">Format muat turun pilihan</string>\n    <string name=\"single_cbz_file\">Fail CBZ tunggal</string>\n    <string name=\"multiple_cbz_files\">Fail CBZ berganda</string>\n    <string name=\"reading_stats\">Statistik membaca</string>\n    <string name=\"other_manga\">Manga lain</string>\n    <string name=\"less_than_minute\">Kurang dari satu minit</string>\n    <string name=\"statistics\">Statistik</string>\n    <string name=\"clear_stats\">Statistik yang jelas</string>\n    <string name=\"stats_cleared\">Statistik dibersihkan</string>\n    <string name=\"clear_stats_confirm\">Adakah anda benar -benar ingin membersihkan semua statistik membaca? Tindakan ini tidak dapat dibatalkan.</string>\n    <string name=\"week\">Minggu</string>\n    <string name=\"month\">Bulan</string>\n    <string name=\"all_time\">Sepanjang masa</string>\n    <string name=\"day\">Hari</string>\n    <string name=\"three_months\">Tiga bulan</string>\n    <string name=\"empty_stats_text\">Tidak ada statistik untuk tempoh yang dipilih</string>\n    <string name=\"pages_read_s\">Halaman dibaca: %s</string>\n    <string name=\"alternatives\">Alternatif</string>\n    <string name=\"migrate\">Berhijrah</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" dari \\\"%2$s\\\" akan digantikan dengan \\\"%3$s\\\" dari \\\"%4$s\\\" dalam sejarah dan kegemaran anda (jika ada)</string>\n    <string name=\"manga_migration\">Penghijrahan manga</string>\n    <string name=\"migration_completed\">Penghijrahan selesai</string>\n    <string name=\"delete_read_chapters\">Padam Baca Bab</string>\n    <string name=\"no_chapters_deleted\">Tidak ada bab yang telah dipadam</string>\n    <string name=\"chapters_deleted_pattern\">Dikeluarkan %1$s, dibersihkan %2$s</string>\n    <string name=\"delete_read_chapters_summary\">Padamkan bab yang telah anda baca dari simpanan tempatan untuk membebaskan ruang</string>\n    <string name=\"delete_read_chapters_prompt\">Ini akan memadamkan semua bab yang ditandakan secara kekal seperti yang dibaca dari storan tempatan anda. Anda boleh memuat turun semula kemudian, tetapi bab yang diimport mungkin hilang selama-lamanya</string>\n    <string name=\"delete_read_chapters_auto\">Padam Baca Bab secara automatik</string>\n    <string name=\"runs_on_app_start\">Berjalan ketika aplikasi bermula</string>\n    <string name=\"split_by_translations\">Berpecah oleh terjemahan</string>\n    <string name=\"split_by_translations_summary\">Tunjukkan bab dengan terjemahan yang berbeza secara berasingan, bukannya dalam satu senarai</string>\n    <string name=\"order_oldest\">Tertua</string>\n    <string name=\"long_ago_read\">Lama dahulu baca</string>\n    <string name=\"unread\">Belum dibaca</string>\n    <string name=\"enable_source\">Dayakan sumber</string>\n    <string name=\"unsupported_source\">Sumber manga ini tidak disokong</string>\n    <string name=\"show_pages_thumbs\">Tunjukkan Halaman Thumbnails</string>\n    <string name=\"show_pages_thumbs_summary\">Dayakan tab \\\"Halaman\\\" pada skrin Butiran</string>\n    <string name=\"error_no_data_received\">Tiada data diterima dari pelayan</string>\n    <string name=\"unsupported_backup_message\">Sila pilih fail sandaran Kotatsu yang betul</string>\n    <string name=\"hours_short\">%d j</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"hours_minutes_short\">%1$d j %2$d m</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d d</string>\n    <string name=\"fix\">Betulkan</string>\n    <string name=\"missing_storage_permission\">Tidak ada kebenaran untuk mengakses manga pada storan luaran</string>\n    <string name=\"last_used\">Terakhir digunakan</string>\n    <string name=\"show_updated\">Tunjukkan dikemas kini</string>\n    <string name=\"webtoon_gaps\">Jurang dalam mod webtoon</string>\n    <string name=\"webtoon_gaps_summary\">Tunjukkan jurang menegak antara halaman dalam mod webtoon</string>\n    <string name=\"enable_pull_gesture_title\">Dayakan tarik isyarat</string>\n    <string name=\"enable_pull_gesture_summary\">Gunakan isyarat tarik untuk menukar bab di webtoon</string>\n    <string name=\"less_frequently\">Kurang kerap</string>\n    <string name=\"more_frequently\">Lebih kerap</string>\n    <string name=\"frequency_of_check\">Kekerapan cek</string>\n    <string name=\"pin_navigation_ui\">UI navigasi pin</string>\n    <string name=\"pin_navigation_ui_summary\">Jangan sembunyikan bar navigasi dan paparan cari pada tatal</string>\n    <string name=\"search_suggestions\">Cadangan carian</string>\n    <string name=\"recent_queries\">Pertanyaan terkini</string>\n    <string name=\"suggested_queries\">Pertanyaan yang dicadangkan</string>\n    <string name=\"authors\">Penulis</string>\n    <string name=\"blocked_by_server_message\">Anda disekat oleh pelayan. Cuba gunakan sambungan rangkaian yang berbeza (VPN, proksi, dll.)</string>\n    <string name=\"disable\">Lumpuhkan</string>\n    <string name=\"sources_disabled\">Sumber dilumpuhkan</string>\n    <string name=\"disable_connectivity_check\">Lumpuhkan Semak Sambungan</string>\n    <string name=\"ignore_ssl_errors_summary\">Anda boleh melumpuhkan pengesahan sijil SSL sekiranya anda menghadapi masalah yang berkaitan dengan SSL semasa mengakses sumber rangkaian. Ini boleh menjejaskan keselamatan anda. Permohonan dimulakan semula diperlukan setelah berubah.</string>\n    <string name=\"disable_connectivity_check_summary\">Langkau pemeriksaan sambungan sekiranya anda mempunyai masalah dengannya (mis. Mod luar talian walaupun rangkaian disambungkan)</string>\n    <string name=\"disable_nsfw_notifications\">Lumpuhkan pemberitahuan NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">Jangan tunjukkan pemberitahuan mengenai kemas kini manga NSFW</string>\n    <string name=\"tracker_debug_info\">Memeriksa log bab baru</string>\n    <string name=\"tracker_debug_info_summary\">Maklumat debug mengenai pemeriksaan latar untuk bab baru</string>\n    <string name=\"_new\">Baru</string>\n    <string name=\"all_languages\">Semua bahasa</string>\n    <string name=\"screenshots_block_incognito\">Blok semasa mod penyamaran</string>\n    <string name=\"image_server\">Pelayan Imej Pilihan</string>\n    <string name=\"crop_pages\">Halaman tanaman</string>\n    <string name=\"pin\">Pin</string>\n    <string name=\"unpin\">Batalkan PIN</string>\n    <string name=\"source_pinned\">Sumber disematkan</string>\n    <string name=\"source_unpinned\">Sumber yang tidak diingini</string>\n    <string name=\"sources_unpinned\">Sumber yang tidak diingini</string>\n    <string name=\"sources_pinned\">Sumber disematkan</string>\n    <string name=\"recent_sources\">Sumber terkini</string>\n    <string name=\"percent_read\">Peratus membaca</string>\n    <string name=\"percent_left\">Peratus kiri</string>\n    <string name=\"chapters_read\">Bab Baca</string>\n    <string name=\"chapters_left\">Bab kiri</string>\n    <string name=\"external_source\">External/plugin</string>\n    <string name=\"plugin_incompatible\">Plugin yang tidak serasi atau ralat dalaman. Pastikan anda menggunakan versi terkini plugin dan kotatsu</string>\n    <string name=\"plugin_incompatible_with_cause\">Ralat Plugin: %s\\n Pastikan anda menggunakan versi terkini plugin dan kotatsu</string>\n    <string name=\"connection_ok\">Sambungan ok</string>\n    <string name=\"invalid_proxy_configuration\">Konfigurasi proksi tidak sah</string>\n    <string name=\"show_quick_filters\">Tunjukkan penapis cepat</string>\n    <string name=\"show_quick_filters_summary\">Memberi keupayaan untuk menapis senarai manga oleh parameter tertentu</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"skip_all\">Langkau semua</string>\n    <string name=\"stuck\">Terjebak</string>\n    <string name=\"not_in_favorites\">Tidak sesuai</string>\n    <string name=\"updated_long_ago\">Dikemas kini lama dahulu</string>\n    <string name=\"unpopular\">Tidak popular</string>\n    <string name=\"low_rating\">Penilaian rendah</string>\n    <string name=\"sort_order_asc\">Menaik</string>\n    <string name=\"sort_order_desc\">Turun</string>\n    <string name=\"by_date\">Tarikh</string>\n    <string name=\"popularity\">Populariti</string>\n    <string name=\"scrobbler_auth_required\">Log masuk ke %s untuk meneruskan</string>\n    <string name=\"scrobbler_auth_intro\">Log masuk untuk menyediakan integrasi dengan %s. Ini akan membolehkan anda menjejaki kemajuan dan status membaca manga anda</string>\n    <string name=\"unstable_feature\">Ciri yang tidak stabil</string>\n    <string name=\"unstable_feature_summary\">Fungsi ini adalah percubaan. Pastikan anda mempunyai sandaran untuk mengelakkan kehilangan data</string>\n    <string name=\"downloads_background\">Muat turun latar belakang</string>\n    <string name=\"download_new_chapters\">Muat turun bab baru</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga dengan bab yang dimuat turun</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) digantikan dengan \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"fixing_manga\">Memperbaiki manga</string>\n    <string name=\"fixed\">Tetap berjaya</string>\n    <string name=\"no_fix_required\">Tiada pembetulan diperlukan untuk \\\"%s\\\"</string>\n    <string name=\"no_alternatives_found\">Tiada alternatif yang dijumpai \\\"%s\\\"</string>\n    <string name=\"manga_fix_prompt\">Fungsi ini akan menemui sumber alternatif untuk manga yang dipilih. Tugas akan mengambil sedikit masa dan akan diteruskan di latar belakang</string>\n    <string name=\"content_type_novel\">Novel</string>\n    <string name=\"content_type_manhua\">Komik Mandarin (manhua)</string>\n    <string name=\"content_type_manhwa\">Komik Korea (Manhwa)</string>\n    <string name=\"recently_added\">Baru-baru ini ditambah</string>\n    <string name=\"added_long_ago\">Ditambah lama dahulu</string>\n    <string name=\"popular_in_hour\">Popular jam ini</string>\n    <string name=\"popular_today\">Popular hari ini</string>\n    <string name=\"popular_in_week\">Popular minggu ini</string>\n    <string name=\"popular_in_month\">Popular bulan ini</string>\n    <string name=\"popular_in_year\">Popular tahun ini</string>\n    <string name=\"original_language\">Bahasa asal</string>\n    <string name=\"year\">Tahun</string>\n    <string name=\"demographics\">Demografi</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"years\">Tahun</string>\n    <string name=\"any\">Mana-mana</string>\n    <string name=\"filter_search_warning\">Sumber ini tidak menyokong carian dengan penapis. Penapis anda telah dibersihkan</string>\n    <string name=\"demographic_kodomo\">Kanak-kanak</string>\n    <string name=\"content_type_one_shot\">Satu pukulan</string>\n    <string name=\"content_type_doujinshi\">Komik independen</string>\n    <string name=\"content_type_image_set\">Set imej</string>\n    <string name=\"content_type_artist_cg\">Artist CG</string>\n    <string name=\"content_type_game_cg\">Permainan CG</string>\n    <string name=\"debug\">Awakutu</string>\n    <string name=\"source_code\">Kod sumber</string>\n    <string name=\"user_manual\">Manual Pengguna</string>\n    <string name=\"telegram_group\">Kumpulan Telegram</string>\n    <string name=\"error_image_format\">Format imej yang tidak disokong: %s</string>\n    <string name=\"error_not_image\">Format tidak sah: Imej yang diharapkan tetapi mendapat %s</string>\n    <string name=\"start_download\">Mula muat turun</string>\n    <string name=\"save_manga_confirm\">Simpan manga terpilih? Ini mungkin mengambil ruang lalu lintas dan cakera</string>\n    <string name=\"save_manga\">Simpan manga</string>\n    <string name=\"genre\">Genre</string>\n    <string name=\"download_added\">Muat turun ditambah</string>\n    <string name=\"more_options\">Lebih banyak pilihan</string>\n    <string name=\"destination_directory\">Direktori Destinasi</string>\n    <string name=\"chapter_selection_hint\">Anda boleh memilih bab untuk memuat turun dengan klik lama pada item dalam senarai bab.</string>\n    <string name=\"chapters_all\">Semua</string>\n    <string name=\"download_over_cellular\">Memuat turun rangkaian selular</string>\n    <string name=\"download_cellular_confirm\">Benarkan muat turun melalui rangkaian selular?</string>\n    <string name=\"dont_allow\">Jangan biarkan</string>\n    <string name=\"allow_always\">Benarkan selalu</string>\n    <string name=\"allow_once\">Benarkan sekali</string>\n    <string name=\"ask_every_time\">Tanya setiap masa</string>\n    <string name=\"screen_orientation\">Orientasi skrin</string>\n    <string name=\"portrait\">Potret</string>\n    <string name=\"landscape\">Landskap</string>\n    <string name=\"access_denied_403\">Akses Ditolak (403)</string>\n    <string name=\"max_backups_count\">Bilangan sandaran maksimum</string>\n    <string name=\"delete_old_backups\">Padam sandaran lama</string>\n    <string name=\"delete_old_backups_summary\">Memadam fail sandaran lama secara automatik untuk menjimatkan ruang penyimpanan</string>\n    <string name=\"handle_links\">Mengendalikan pautan</string>\n    <string name=\"handle_links_summary\">Mengendalikan pautan manga dari aplikasi luaran (mis. Pelayar web). Anda juga mungkin perlu membolehkannya secara manual dalam tetapan sistem aplikasi</string>\n    <string name=\"email\">E -mel</string>\n    <string name=\"captcha_required_message\">Sumber ini memerlukan menyelesaikan Captcha untuk meneruskan</string>\n    <string name=\"author\">Pengarang</string>\n    <string name=\"rating\">Penilaian</string>\n    <string name=\"source\">Sumber</string>\n    <string name=\"translation\">Terjemahan</string>\n    <string name=\"show_slider\">Tunjukkan gelangsar</string>\n    <string name=\"incognito\">Mod penyamaran</string>\n    <string name=\"error_connection_reset\">Tetapkan semula sambungan oleh tuan rumah jauh</string>\n    <string name=\"backup_tg_check\">Periksa sama ada API berfungsi</string>\n    <string name=\"backup_tg_echo\">Mesej ujian</string>\n    <string name=\"backup_tg_id_not_set\">ID sembang tidak ditetapkan</string>\n    <string name=\"telegram_chat_id\">ID sembang telegram</string>\n    <string name=\"open_telegram_bot\">Buka bot Telegram</string>\n    <string name=\"send_backups_telegram\">Hantar sandaran di Telegram</string>\n    <string name=\"test_connection\">Sambungan ujian</string>\n    <string name=\"telegram_chat_id_summary\">Masukkan ID sembang di mana sandaran harus dihantar</string>\n    <string name=\"open_telegram_bot_summary\">Tekan untuk membuka sembang dengan bot sandaran Kotatsu</string>\n    <string name=\"clear_database\">Pangkalan data yang jelas</string>\n    <string name=\"clear_database_summary\">Padamkan maklumat mengenai manga yang tidak digunakan</string>\n    <string name=\"enable_all_sources\">Dayakan semua sumber manga</string>\n    <string name=\"enable_all_sources_summary\">Semua sumber manga yang ada akan didayakan secara kekal</string>\n    <string name=\"all_sources_enabled\">Semua sumber diaktifkan</string>\n    <string name=\"reader_info_bar_transparent\">Bar Maklumat Pembaca Telus</string>\n    <string name=\"backup_restored_background\">Sandaran akan dipulihkan di latar belakang</string>\n    <string name=\"restoring_backup\">Memulihkan sandaran</string>\n    <string name=\"reader_controls_in_bottom_bar\">Kawalan pembaca di bar bawah</string>\n    <string name=\"chapters_and_pages\">Bab dan halaman</string>\n    <string name=\"pages_slider\">Slider suis halaman</string>\n    <string name=\"screen_rotation_locked\">Putaran skrin telah dikunci</string>\n    <string name=\"screen_rotation_unlocked\">Putaran skrin telah dibuka</string>\n    <string name=\"badges_in_lists\">Lencana dalam senarai</string>\n    <string name=\"search_everywhere\">Cari di mana -mana sahaja</string>\n    <string name=\"simple\">Mudah</string>\n    <string name=\"global_search\">Carian global</string>\n    <string name=\"disable_captcha_notifications\">Lumpuhkan pemberitahuan Captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Anda tidak akan menerima pemberitahuan mengenai menyelesaikan Captcha untuk sumber ini tetapi ini boleh membawa kepada operasi latar belakang (memeriksa bab baru, mendapatkan cadangan, dll)</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Bab %2$s</string>\n    <string name=\"chapter_number\">Bab%s</string>\n    <string name=\"unnamed_chapter\">Bab yang tidak dinamakan</string>\n    <string name=\"search_disabled_sources\">Cari melalui sumber kurang upaya</string>\n    <string name=\"error_details\">Butiran ralat</string>\n    <string name=\"error_disclaimer_manga\">Cuba buka manga dalam pelayar web untuk memastikan ia tersedia di sumbernya.</string>\n    <string name=\"error_disclaimer_app_outdated\">Ia kelihatan seperti versi Kotatsu anda sudah lapuk. Sila pasang versi terkini untuk mendapatkan semua pembetulan yang tersedia.</string>\n    <string name=\"error_disclaimer_report\">Anda boleh menghantar laporan pepijat kepada pemaju. Ini akan membantu kami menyiasat dan membetulkan isu ini.</string>\n    <string name=\"link_to_manga_on_s\">Pautan ke manga %s</string>\n    <string name=\"link_to_manga_in_app\">Kotatsu, pekerja manga</string>\n    <string name=\"clear_browser_data\">Data penyemak imbas yang jelas</string>\n    <string name=\"clear_browser_data_summary\">Data penyemak imbas yang jelas seperti cache dan kuki. Amaran: Kebenaran dalam sumber manga mungkin tidak sah</string>\n    <string name=\"no_write_permission_to_file\">Tidak mempunyai kebenaran untuk menulis fail</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Manga dewasa tidak akan ditunjukkan dalam cadangan. Pilihan ini mungkin tidak tepat dengan beberapa sumber</string>\n    <string name=\"include_disabled_sources\">Termasuk sumber kurang upaya</string>\n    <string name=\"suggestions_disabled_sources_summary\">Tunjukkan cadangan dari semua sumber manga, termasuk orang kurang upaya</string>\n    <string name=\"tags_warnings\">Sorot genre berbahaya</string>\n    <string name=\"tags_warnings_summary\">Sorot genre yang mungkin tidak sesuai untuk kebanyakan pengguna</string>\n    <string name=\"error_non_file_uri\">Laluan yang dipilih tidak dapat digunakan kerana ia tidak menandakan fail atau direktori</string>\n    <string name=\"manga_override_hint\">Perubahan ini akan mempengaruhi bagaimana manga dipaparkan dalam aplikasinya</string>\n    <string name=\"use_default_cover\">Gunakan penutup lalai</string>\n    <string name=\"pick_manga_page\">Pilih halaman manga</string>\n    <string name=\"pick_custom_file\">Pilih fail tersuai</string>\n    <string name=\"change_cover\">Tukar penutup</string>\n    <string name=\"page_switch_timer\">Halaman akan menukar setiap ~%d saat</string>\n    <string name=\"dont_ask_again\">Jangan tanya lagi</string>\n    <string name=\"incognito_mode_hint_nsfw\">Manga ini mungkin mengandungi kandungan dewasa. Adakah anda mahu menggunakan mod incognito?</string>\n    <string name=\"incognito_for_nsfw\">Mod penyamaran untuk manga nsfw</string>\n    <string name=\"additional_action_required\">Tindakan tambahan diperlukan</string>\n    <string name=\"hide_from_main_screen\">Sembunyikan dari skrin utama</string>\n    <string name=\"changelog\">Perubahan dalam perubahan</string>\n    <string name=\"changelog_summary\">Perubahan sejarah untuk versi yang dikeluarkan baru -baru ini</string>\n    <string name=\"collapse\">Runtuh</string>\n    <string name=\"expand\">Berkembang</string>\n    <string name=\"adblock\">Blok iklan dalam penyemak imbas</string>\n    <string name=\"adblock_summary\">Blok Iklan di Pelayar Terbina (Beta)</string>\n    <string name=\"collapse_long_description\">Kejutan panjang runtuh</string>\n    <string name=\"creating_backup\">Membuat sandaran</string>\n    <string name=\"share_backup\">Kongsi sandaran</string>\n    <string name=\"reader_multitask\">Buka pembaca dalam tugas yang berasingan</string>\n    <string name=\"reader_multitask_summary\">Membolehkan anda menyimpan beberapa pembaca dengan manga yang berbeza terbuka pada masa yang sama</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"book_effect\">Latar belakang kekuningan (penapis biru)</string>\n    <string name=\"local_storage_cleanup\">Pembersihan Penyimpanan Tempatan</string>\n    <string name=\"packup_creation_failed\">Gagal membuat sandaran</string>\n    <string name=\"main_screen\">Skrin utama</string>\n    <string name=\"main_screen_fab\">Tunjukkan butang Terus Terapung</string>\n    <string name=\"main_screen_fab_summary\">Membolehkan terus membaca dalam satu klik. Butang ini tidak akan muncul dalam mod penyamaran atau ketika sejarah kosong</string>\n    <string name=\"error_corrupted_zip\">Arkib zip yang rosak (%s)</string>\n    <string name=\"discord_rpc\">Discord Kehadiran yang kaya</string>\n    <string name=\"discord_token\">Token Discord</string>\n    <string name=\"discord_token_summary\">Masukkan token Discord anda untuk membolehkan kehadiran yang kaya</string>\n    <string name=\"discord_token_description\">Masukkan token atau klik Discord anda atau klik %s untuk menggunakannya menggunakan penyemak imbas</string>\n    <string name=\"discord_token_hint\">Tampalkan token Discord anda di sini</string>\n    <string name=\"discord_rpc_summary\">Tunjukkan status bacaan anda pada Discord</string>\n    <string name=\"obtain\">Memperoleh</string>\n    <string name=\"discord_rpc_description\">Membaca Manga di Kotatsu - Aplikasi Pembaca Manga</string>\n    <string name=\"reading_s\">Membaca %s</string>\n    <string name=\"read_on_s\">Baca terus %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Jangan gunakan RPC untuk kandungan dewasa</string>\n    <string name=\"invalid_token\">Token tidak sah: %s</string>\n    <string name=\"show_floating_control_button\">Tunjukkan butang kawalan terapung</string>\n    <string name=\"unavailable\">Tidak tersedia</string>\n    <string name=\"manga_restricted_description\">Manga ini tidak tersedia untuk dibaca dalam sumber ini. Cuba cari di sumber lain atau membukanya dalam penyemak imbas untuk maklumat lanjut</string>\n    <string name=\"no_chapters_in_manga\">Manga ini tidak mengandungi sebarang bab</string>\n    <string name=\"chapters_load_failed\">Gagal memuatkan senarai bab</string>\n    <string name=\"telegram_integration\">Telegram integrasi</string>\n    <string name=\"test_parser\">Ujian manga sumber</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-my/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d စာမျက်နှာအသစ်</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d စာမျက်နှာ</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d မိနစ်အကြာက</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d နာရီအကြာက</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d အချက်အလက်</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d ရက်အကြာက</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d လအကြာက</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d နာရီအကြာက</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d မိနစ်</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-my/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"local_storage\">ကိုယ်ပိုင်သိုလှောင်မှု</string>\n    <string name=\"favourites\">အနှစ်သက်ဆုံး</string>\n    <string name=\"history\">မှတ်တမ်း</string>\n    <string name=\"error_occurred\">အမှားတစ်ခု ဖြစ်ပေါ်ခဲ့သည်</string>\n    <string name=\"network_error\">အင်တာနက် ပြဿနာ</string>\n    <string name=\"details\">အသေးစိတ်များ</string>\n    <string name=\"chapters\">အပိုင်းများ</string>\n    <string name=\"detailed_list\">အသေးစိတ်စာရင်း</string>\n    <string name=\"grid\">ဂရစ်</string>\n    <string name=\"list_mode\">စာရင်းမုဒ်</string>\n    <string name=\"settings\">စက်တင်များ</string>\n    <string name=\"remote_sources\">မန်ဂါရင်းမြစ်များ</string>\n    <string name=\"loading_\">စောင့်ဆိုင်းပါ…</string>\n    <string name=\"computing_\">တွက်ချက်နေသည်…</string>\n    <string name=\"chapter_d_of_d\">%2$d ရဲ့ အခန်း %1$d</string>\n    <string name=\"close\">ပိတ်မည်</string>\n    <string name=\"try_again\">ပြန်လည်ကြိုးစားပါ</string>\n    <string name=\"retry\">ထပ်မံကြိုးစားပါ</string>\n    <string name=\"clear_history\">မှတ်တန်းများဖျက်မည်</string>\n    <string name=\"nothing_found\">ဘာမှရှာမတွေ့ပါ</string>\n    <string name=\"history_is_empty\">ဘာမှတ်တန်းမှမရှိသေး</string>\n    <string name=\"read\">ဖတ်မည်</string>\n    <string name=\"add_to_favourites\">အကြိုက်ဆုံးအဖြစ်မှတ်</string>\n    <string name=\"add_new_category\">အမျိုးအစားအသစ်</string>\n    <string name=\"add\">ထည့်မည်</string>\n    <string name=\"save\">သိမ်းမည်</string>\n    <string name=\"share\">မျှဝေ</string>\n    <string name=\"create_shortcut\">Shortcut ဖန်တီး</string>\n    <string name=\"share_s\">%s ကို မျှဝေ</string>\n    <string name=\"search\">ရှာဖွေ</string>\n    <string name=\"search_manga\">Manga ကိုရှာမည်</string>\n    <string name=\"manga_downloading_\">Donwload လုပ်နေ…</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-nb-rNO/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nytt kapittel</item>\n        <item quantity=\"other\">%1$d nye kapitler</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d kapittel</item>\n        <item quantity=\"other\">%1$d kapitler</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d time siden</item>\n        <item quantity=\"other\">%1$d timer siden</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d dag siden</item>\n        <item quantity=\"other\">%1$d dager siden</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d element</item>\n        <item quantity=\"other\">%1$d elementer</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minutt siden</item>\n        <item quantity=\"other\">%1$d minutter siden</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d måned siden</item>\n        <item quantity=\"other\">%1$d måneder siden</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-nb-rNO/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"cannot_find_available_storage\">Ingen tilgjengelig lagring</string>\n    <string name=\"manga_save_location\">Mappe for nedlastninger</string>\n    <string name=\"text_local_holder_secondary\">Lagre fra nettbaserte kilder, eller importer filer.</string>\n    <string name=\"tracker_warning\">Noen enheter endrer systemoppførselen, noe som kan ødelegge for bakgrunnsoppgaver.</string>\n    <string name=\"text_clear_search_history_prompt\">Fjern alle nylige søkespørringer for godt\\?</string>\n    <string name=\"password_length_hint\">Passordet må være minst fire tegn</string>\n    <string name=\"protect_application_subtitle\">Skriv inn passord å kreve for å starte programmet</string>\n    <string name=\"auth_required\">Logg inn for å se dette innholdet</string>\n    <string name=\"reverse\">Inverter</string>\n    <string name=\"check_for_new_chapters\">Sjekker etter nye kapitler</string>\n    <string name=\"text_clear_updates_feed_prompt\">Tøm all oppdateringshistorikk for godt\\?</string>\n    <string name=\"clear_feed\">Tøm flyt</string>\n    <string name=\"reader_mode_hint\">Valgt oppsett vil bli husket for denne filen</string>\n    <string name=\"backup_information\">Du kan opprette en sikkerhetskopi av din historikk og favoritter å gjenopprette senere</string>\n    <string name=\"data_restored_with_errors\">Dataene ble gjenopprettet, men med feil</string>\n    <string name=\"file_not_found\">Fant ikke filen</string>\n    <string name=\"backup_restore\">Sikkerhetskopiering og gjenoppretting</string>\n    <string name=\"zoom_mode_keep_start\">Behold ved oppstart</string>\n    <string name=\"zoom_mode_fit_width\">Tilpass bredde</string>\n    <string name=\"zoom_mode_fit_height\">Tilpass høyde</string>\n    <string name=\"zoom_mode_fit_center\">Tilpass sentrum</string>\n    <string name=\"scale_mode\">Skaleringsmodus</string>\n    <string name=\"right_to_left\">Høyre-til-venstre</string>\n    <string name=\"no_update_available\">Ingen tilgjengelige oppdateringer</string>\n    <string name=\"check_for_updates\">Se etter oppdateringer</string>\n    <string name=\"protect_application_summary\">Spør om passord ved programoppstart</string>\n    <string name=\"track_sources\">Se etter oppdateringer</string>\n    <string name=\"feed_will_update_soon\">Flytoppdatering starter snart</string>\n    <string name=\"update\">Oppdater</string>\n    <string name=\"updates_feed_cleared\">Tømt</string>\n    <string name=\"clear_updates_feed\">Tøm oppdateringsflyt</string>\n    <string name=\"new_version_s\">Ny versjon: %s</string>\n    <string name=\"text_feed_holder\">Nye kapitler av det du leser vises her</string>\n    <string name=\"updates\">Oppdatering</string>\n    <string name=\"other_storage\">Annen lagring</string>\n    <string name=\"manga_shelf\">Hylle</string>\n    <string name=\"text_local_holder_primary\">Lagre noe først</string>\n    <string name=\"text_history_holder_secondary\">Finn lesestoff i sidemenyen.</string>\n    <string name=\"text_history_holder_primary\">Det du leser vil vises her</string>\n    <string name=\"text_search_holder_secondary\">Prøv å reformulere spørringen.</string>\n    <string name=\"text_empty_holder_primary\">Det er ganske tomt her …</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d av %2$d påskrudd</string>\n    <string name=\"open_in_browser\">Åpne i nettleser</string>\n    <string name=\"app_update_available\">En ny versjon av programmet er tilgjengelig</string>\n    <string name=\"search_history_cleared\">Tømt</string>\n    <string name=\"clear_thumbs_cache\">Tøm miniatyrbildehurtiglager</string>\n    <string name=\"text_delete_local_manga\">Slett «%s» fra enheten for godt?</string>\n    <string name=\"webtoon\">Nettserie</string>\n    <string name=\"standard\">Forvalg</string>\n    <string name=\"clear_pages_cache\">Tøm sidehurtiglager</string>\n    <string name=\"text_file_not_supported\">Velg en ZIP- eller CBZ-fil.</string>\n    <string name=\"operation_not_supported\">Ustøttet handling</string>\n    <string name=\"_import\">Importer</string>\n    <string name=\"page_saved\">Lagret</string>\n    <string name=\"read\">Les</string>\n    <string name=\"nothing_found\">Resultatløst</string>\n    <string name=\"remote_sources\">Manga-kilder</string>\n    <string name=\"read_more\">Les mer</string>\n    <string name=\"backup_saved\">Sikkerhetskopi lagret</string>\n    <string name=\"welcome\">Velkommen</string>\n    <string name=\"confirm\">Bekreft</string>\n    <string name=\"next\">Neste</string>\n    <string name=\"default_s\">Standard: %s</string>\n    <string name=\"sign_in\">Logg inn</string>\n    <string name=\"cookies_cleared\">Alle infokapsler ble fjernet</string>\n    <string name=\"clear_cookies\">Tøm infokapsler</string>\n    <string name=\"captcha_solve\">Løs</string>\n    <string name=\"captcha_required\">CAPTCHA kreves</string>\n    <string name=\"silent\">Stille</string>\n    <string name=\"tap_to_try_again\">Trykk for å prøve igjen</string>\n    <string name=\"today\">I dag</string>\n    <string name=\"group\">Gruppe</string>\n    <string name=\"long_ago\">Lenge siden</string>\n    <string name=\"yesterday\">I går</string>\n    <string name=\"just_now\">Akkurat nå</string>\n    <string name=\"data_restored_success\">All data ble gjenopprettet</string>\n    <string name=\"preparing_\">Forbereder …</string>\n    <string name=\"data_restored\">Data gjenopprettet</string>\n    <string name=\"restore_backup\">Gjenopprett fra sikkerhetskopi</string>\n    <string name=\"create_backup\">Opprett sikkerhetskopi</string>\n    <string name=\"black_dark_theme_summary\">Bruker mindre strøm på AMOLED-skjermer</string>\n    <string name=\"black_dark_theme\">Svart</string>\n    <string name=\"create_category\">Ny kategori</string>\n    <string name=\"app_version\">Versjon %s</string>\n    <string name=\"about\">Om</string>\n    <string name=\"passwords_mismatch\">Passordene samsvarer ikke</string>\n    <string name=\"repeat_password\">Gjenta passordet</string>\n    <string name=\"protect_application\">Beskytt programmet</string>\n    <string name=\"wrong_password\">Feil passord</string>\n    <string name=\"enter_password\">Skriv inn passord</string>\n    <string name=\"dont_check\">Ikke sjekk</string>\n    <string name=\"rotate_screen\">Roter skjerm</string>\n    <string name=\"size_s\">Størrelse: %s</string>\n    <string name=\"search_results\">Søkeresultater</string>\n    <string name=\"read_later\">Les senere</string>\n    <string name=\"favourites_category_empty\">Tom kategori</string>\n    <string name=\"all_favourites\">Alle favoritter</string>\n    <string name=\"done\">Ferdig</string>\n    <string name=\"not_available\">Ikke tilgjengelig</string>\n    <string name=\"pages_animation\">Sideanimasjon</string>\n    <string name=\"recent_manga\">Nylig</string>\n    <string name=\"remove_category\">Fjern</string>\n    <string name=\"favourites_categories\">Favorittkategorier</string>\n    <string name=\"light_indicator\">LED-indikator</string>\n    <string name=\"vibration\">Vibrasjon</string>\n    <string name=\"notification_sound\">Merknadslyd</string>\n    <string name=\"notifications_settings\">Merknadsinnstillinger</string>\n    <string name=\"download\">Last ned</string>\n    <string name=\"new_chapters\">Nye kapittel</string>\n    <string name=\"notifications\">Merknader</string>\n    <string name=\"domain\">Domene</string>\n    <string name=\"external_storage\">Eksternlagring</string>\n    <string name=\"internal_storage\">Internlagring</string>\n    <string name=\"clear_search_history\">Tøm søkehistorikk</string>\n    <string name=\"error\">Feil</string>\n    <string name=\"_continue\">Fortsett</string>\n    <string name=\"switch_pages\">Bytt sider</string>\n    <string name=\"reader_settings\">Leserinnstillinger</string>\n    <string name=\"delete_manga\">Slett manga</string>\n    <string name=\"search_on_s\">Søk på %s</string>\n    <string name=\"grid_size\">Rutenettsstørrelse</string>\n    <string name=\"read_mode\">Lesemodus</string>\n    <string name=\"no_description\">Ingen beskrivelse</string>\n    <string name=\"delete\">Slett</string>\n    <string name=\"share_image\">Del bilde</string>\n    <string name=\"save_page\">Lagre side</string>\n    <string name=\"_s_deleted_from_local_storage\">«%s» slettet fra lokallagring</string>\n    <string name=\"remove\">Fjern</string>\n    <string name=\"clear\">Tøm</string>\n    <string name=\"pages\">Sider</string>\n    <string name=\"follow_system\">Følg systemet</string>\n    <string name=\"dark\">Mørk</string>\n    <string name=\"light\">Lys</string>\n    <string name=\"by_name\">Navn</string>\n    <string name=\"theme\">Drakt</string>\n    <string name=\"filter\">Filter</string>\n    <string name=\"sort_order\">Sorteringsrekkefølge</string>\n    <string name=\"by_rating\">Vurdering</string>\n    <string name=\"newest\">Nyeste</string>\n    <string name=\"updated\">Oppdatert</string>\n    <string name=\"popular\">Popularitet</string>\n    <string name=\"downloads\">Nedlastinger</string>\n    <string name=\"download_complete\">Nedlastet</string>\n    <string name=\"processing_\">Behandler …</string>\n    <string name=\"manga_downloading_\">Laster ned …</string>\n    <string name=\"search_manga\">Søk manga</string>\n    <string name=\"search\">Søk</string>\n    <string name=\"share_s\">Del %s</string>\n    <string name=\"create_shortcut\">Opprett snarvei …</string>\n    <string name=\"share\">Del</string>\n    <string name=\"save\">Lagre</string>\n    <string name=\"add\">Legg til</string>\n    <string name=\"add_new_category\">Ny kategori</string>\n    <string name=\"add_to_favourites\">Favorittmerk dette</string>\n    <string name=\"you_have_not_favourites_yet\">Ingen favoritter enda</string>\n    <string name=\"history_is_empty\">Ingen historikk enda</string>\n    <string name=\"clear_history\">Tøm historikk</string>\n    <string name=\"try_again\">Prøv igjen</string>\n    <string name=\"close\">Lukk</string>\n    <string name=\"chapter_d_of_d\">Kapittel %1$d av %2$d</string>\n    <string name=\"loading_\">Laster inn …</string>\n    <string name=\"settings\">Innstillinger</string>\n    <string name=\"list_mode\">Listemodus</string>\n    <string name=\"grid\">Rutenett</string>\n    <string name=\"detailed_list\">Detaljert liste</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"chapters\">Kapittel</string>\n    <string name=\"details\">Detaljer</string>\n    <string name=\"network_error\">Nettverksfeil</string>\n    <string name=\"error_occurred\">En feil inntraff</string>\n    <string name=\"history\">Historikk</string>\n    <string name=\"favourites\">Favoritter</string>\n    <string name=\"local_storage\">Lokallagring</string>\n    <string name=\"chapter_is_missing\">Kapittelet mangler</string>\n    <string name=\"queued\">I kø</string>\n    <string name=\"state_finished\">Fullført</string>\n    <string name=\"state_ongoing\">Pågående</string>\n    <string name=\"about_app_translation_summary\">Oversett dette programmet</string>\n    <string name=\"about_app_translation\">Oversettelse</string>\n    <string name=\"auth_complete\">Identitetsbekreftet</string>\n    <string name=\"auth_not_supported_by\">Innlogging på %s støttes ikke</string>\n    <string name=\"text_clear_cookies_prompt\">Du vil bli utlogget fra alle kilder</string>\n    <string name=\"genres\">Sjangere</string>\n    <string name=\"exclude_nsfw_from_history\">Utelat sensurerbar-manga fra historikk</string>\n    <string name=\"system_default\">Forvalg</string>\n    <string name=\"show_pages_numbers\">Sidenummerering</string>\n    <string name=\"computing_\">Beregner …</string>\n    <string name=\"screenshots_allow\">Tillat</string>\n    <string name=\"screenshots_block_nsfw\">Blokker for sensurerbare</string>\n    <string name=\"screenshots_block_all\">Alltid blokker</string>\n    <string name=\"screenshots_policy\">Skjermavbildningspraksis</string>\n    <string name=\"suggestions_enable\">Skru på forslag</string>\n    <string name=\"suggestions\">Forslag</string>\n    <string name=\"text_suggestion_holder\">Du vil få personaliserte forslag når du begynner å lese manga</string>\n    <string name=\"suggestions_info\">Alle data analyseres lokalt på denne enheten. Det er ingen overføring av dine personlige data til noen tjenester</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Ikke foreslå sensurerbar manga</string>\n    <string name=\"suggestions_summary\">Foreslå manga basert på vaner</string>\n    <string name=\"enabled\">Påskrudd</string>\n    <string name=\"disabled\">Avskrudd</string>\n    <string name=\"always\">Alltid</string>\n    <string name=\"preload_pages\">Forhåndsinnlast bilder</string>\n    <string name=\"reset_filter\">Tilbakestill filter</string>\n    <string name=\"never\">Aldri</string>\n    <string name=\"only_using_wifi\">Kun på Wi-Fi</string>\n    <string name=\"onboard_text\">Velg språkene du ønsker å lese manga. Du kan endre dette senere i innstillingene.</string>\n    <string name=\"logged_in_as\">Innlogget som %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"search_chapters\">Finn kapittel</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"various_languages\">Forskjellige språk</string>\n    <string name=\"chapters_empty\">Ingen kapitler i denne mangaen</string>\n    <string name=\"appearance\">Utseende</string>\n    <string name=\"name\">Navn</string>\n    <string name=\"edit\">Rediger</string>\n    <string name=\"edit_category\">Rediger kategori</string>\n    <string name=\"dns_over_https\">DNS over HTTPS</string>\n    <string name=\"default_mode\">Forvalgt modus</string>\n    <string name=\"removal_completed\">Fjerning fullført</string>\n    <string name=\"empty_favourite_categories\">Ingen favorittkategorier</string>\n    <string name=\"bookmark_add\">Legg til bokmerke</string>\n    <string name=\"bookmarks\">Bokmerker</string>\n    <string name=\"bookmark_removed\">Bokmerke fjernet</string>\n    <string name=\"bookmark_added\">Bokmerke lagt til</string>\n    <string name=\"undo\">Angre</string>\n    <string name=\"removed_from_history\">Fjernet fra historikk</string>\n    <string name=\"bookmark_remove\">Fjern bokmerke</string>\n    <string name=\"send\">Send</string>\n    <string name=\"suggestions_updating\">Oppdaterer forslag …</string>\n    <string name=\"hide\">Skjul</string>\n    <string name=\"new_sources_text\">Nye manga-kilder tilgjengelig</string>\n    <string name=\"download_slowdown\">Tregere nedlasting</string>\n    <string name=\"email_enter_hint\">Skriv inn e-postadressen din for å fortsette</string>\n    <string name=\"back\">Tilbake</string>\n    <string name=\"sync\">Synkronisering</string>\n    <string name=\"sync_title\">Synkroniser dataen din</string>\n    <string name=\"explore\">Utforsk</string>\n    <string name=\"import_completed\">Importert</string>\n    <string name=\"no_chapters\">Ingen kapitler</string>\n    <string name=\"automatic_scroll\">Automatisk rulling</string>\n    <string name=\"confirm_exit\">Trykk «Tilbake» igjen for å avslutte</string>\n    <string name=\"exit_confirmation_summary\">Trykk «Tilbake» to ganger for å avslutte programmet</string>\n    <string name=\"removed_from_favourites\">Fjernet fra favoritter</string>\n    <string name=\"options\">Alternativer</string>\n    <string name=\"suggestions_excluded_genres\">Utelat sjangere</string>\n    <string name=\"suggestions_excluded_genres_summary\">Angi sjangere du ikke vil ha foreslått</string>\n    <string name=\"text_delete_local_manga_batch\">Slett valgte elementer fra enheten for godt\\?</string>\n    <string name=\"canceled\">Avbrutt</string>\n    <string name=\"account_already_exists\">Kontoen finnes allerede</string>\n    <string name=\"download_slowdown_summary\">Kan unngå å få IP-adressen din blokkert</string>\n    <string name=\"tracking\">Sporing</string>\n    <string name=\"logout\">Logg ut</string>\n    <string name=\"status_completed\">Fullført</string>\n    <string name=\"status_on_hold\">På vent</string>\n    <string name=\"appwidget_shelf_description\">Manga fra favorittene dine</string>\n    <string name=\"appwidget_recent_description\">Manga du har lest nylig</string>\n    <string name=\"clear_all_history\">Tøm all historikk</string>\n    <string name=\"last_2_hours\">De siste to timene</string>\n    <string name=\"history_cleared\">Historikk tømt</string>\n    <string name=\"manage\">Håndter</string>\n    <string name=\"no_bookmarks_yet\">Ingen bokmerker enda</string>\n    <string name=\"bookmarks_removed\">Bokmerker fjernet</string>\n    <string name=\"no_manga_sources\">Ingen manga-kilder</string>\n    <string name=\"random\">Tilfeldig</string>\n    <string name=\"empty\">Tom</string>\n    <string name=\"saved_manga\">Lagret manga</string>\n    <string name=\"available\">Tilgjengelig</string>\n    <string name=\"discard\">Forkast</string>\n    <string name=\"reset\">Tilbakestill</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"brightness\">Lysstyrke</string>\n    <string name=\"color_correction\">Fargekorrigering</string>\n    <string name=\"text_unsaved_changes_prompt\">Lagre eller forkast ulagrede endringer\\?</string>\n    <string name=\"compact\">Kompakt</string>\n    <string name=\"disable_all\">Skru av alle</string>\n    <string name=\"use_fingerprint\">Bruk fingeravtrykk hvis tilgjengelig</string>\n    <string name=\"network_unavailable_hint\">Skru på Wi-Fi eller mobilnettverk for å lese manga på nett</string>\n    <string name=\"notifications_enable\">Skru på merknader</string>\n    <string name=\"show_reading_indicators\">Vis indikatorer for leseforløp</string>\n    <string name=\"data_deletion\">Datasletting</string>\n    <string name=\"exit_confirmation\">Avsluttingsbekreftelse</string>\n    <string name=\"invalid_domain_message\">Ugyldig daomene</string>\n    <string name=\"share_logs\">Del loggføring</string>\n    <string name=\"color_theme\">Fargedrakt</string>\n    <string name=\"theme_name_dynamic\">Dynamisk</string>\n    <string name=\"language\">Språk</string>\n    <string name=\"enable_logging\">Skru på loggføring</string>\n    <string name=\"storage_usage\">Lagringsbruk</string>\n    <string name=\"sync_auth_hint\">Du kan logge med en eksisterende konto, eller opprette en ny</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ne/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d नयाँ अध्याय</item>\n        <item quantity=\"other\">%1$d नयाँ अध्यायहरू</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d अध्याय</item>\n        <item quantity=\"other\">%1$d अध्यायहरू</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d मिनेट पहिले</item>\n        <item quantity=\"other\">%1$d मिनेट पहिले</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d घण्टा पहिले</item>\n        <item quantity=\"other\">%1$d घण्टा पहिले</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d वस्तु</item>\n        <item quantity=\"other\">%1$d वस्तुहरू</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d दिन अघि</item>\n        <item quantity=\"other\">%1$d दिन पहिले</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d महिना अघि</item>\n        <item quantity=\"other\">%1$d महिना अघि</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d घण्टा</item>\n        <item quantity=\"other\">%1$d घण्टा</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d मिनेट</item>\n        <item quantity=\"other\">%1$d मिनेट</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ne/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"manga_downloading_\">डाउनलोड गर्दै…</string>\n    <string name=\"download_complete\">डाउनलोड गरेको</string>\n    <string name=\"filter\">फिल्टर</string>\n    <string name=\"dark\">अँध्यारो</string>\n    <string name=\"clear\">खाली गर्नुहोस्</string>\n    <string name=\"pages\">पृष्ठहरू</string>\n    <string name=\"error_occurred\">एउटा त्रुटि भयो</string>\n    <string name=\"list_mode\">सूची मोड</string>\n    <string name=\"list\">सूची</string>\n    <string name=\"try_again\">पुनः प्रयास गर्नुहोस्</string>\n    <string name=\"nothing_found\">केही पनि फेला परेन</string>\n    <string name=\"read\">पढ्नुहोस्</string>\n    <string name=\"local_storage\">लोकल भण्डारण</string>\n    <string name=\"history\">इतिहास</string>\n    <string name=\"network_error\">नेटवर्क त्रुटि</string>\n    <string name=\"chapters\">अध्यायहरू</string>\n    <string name=\"settings\">सेटिङहरू</string>\n    <string name=\"remote_sources\">माङ्गा स्रोतहरू</string>\n    <string name=\"loading_\">लोड हुँदै…</string>\n    <string name=\"clear_history\">इतिहास खाली गर्नुहोस्</string>\n    <string name=\"you_have_not_favourites_yet\">अहिले कुनै मनपर्ने छैन</string>\n    <string name=\"add_to_favourites\">मनपर्ने मा राख्नुहोस्</string>\n    <string name=\"add_new_category\">नयाँ वर्ग</string>\n    <string name=\"share_s\">%s साझा गर्नुहोस्</string>\n    <string name=\"search\">खोज्नुहोस्</string>\n    <string name=\"search_manga\">माङ्गा खोज्नुहोस्</string>\n    <string name=\"processing_\">प्रक्रिया गर्दैछ…</string>\n    <string name=\"downloads\">डाउनलोडहरू</string>\n    <string name=\"by_name\">नाम</string>\n    <string name=\"updated\">अपडेट गरिएको</string>\n    <string name=\"newest\">नवीनतम</string>\n    <string name=\"sort_order\">क्रमबद्ध क्रम</string>\n    <string name=\"theme\">थीम</string>\n    <string name=\"light\">उज्यालो</string>\n    <string name=\"follow_system\">सिस्टम पालना गर्नुहोस्</string>\n    <string name=\"computing_\">कम्प्युटिङ…</string>\n    <string name=\"favourites\">मनपर्ने</string>\n    <string name=\"details\">विवरण</string>\n    <string name=\"detailed_list\">विस्तृत सूची</string>\n    <string name=\"grid\">ग्रिड</string>\n    <string name=\"chapter_d_of_d\">अध्याय %1$d / %2$d</string>\n    <string name=\"close\">बन्द गर्नुहोस्</string>\n    <string name=\"history_is_empty\">अहिलेसम्म इतिहास छैन</string>\n    <string name=\"add\">थप्नुहोस्</string>\n    <string name=\"save\">सेभ गर्नुहोस्</string>\n    <string name=\"share\">साझा गर्नुहोस्</string>\n    <string name=\"create_shortcut\">सर्टकट सिर्जना गर्नुहोस्…</string>\n    <string name=\"popular\">लोकप्रिय</string>\n    <string name=\"by_rating\">मूल्याङ्कन</string>\n    <string name=\"new_chapters\">नयाँ अध्यायहरू</string>\n    <string name=\"remove\">हटाउनुहोस्</string>\n    <string name=\"_s_deleted_from_local_storage\">लोकल भण्डारणबाट %s हटाइयो</string>\n    <string name=\"page_saved\">सेभ गरियो</string>\n    <string name=\"save_page\">पृष्ठ सेभ गर्नुहोस्</string>\n    <string name=\"share_image\">छवि साझा गर्नुहोस्</string>\n    <string name=\"_import\">आयात गर्नुहोस्</string>\n    <string name=\"standard\">स्ट्यानडर्ड</string>\n    <string name=\"webtoon\">वेबटून</string>\n    <string name=\"read_mode\">पढ्ने मोड</string>\n    <string name=\"grid_size\">ग्रिड साइज</string>\n    <string name=\"text_delete_local_manga\">यन्त्रबाट %s लाई सदाका लागि हटाउने हो?</string>\n    <string name=\"reader_settings\">रिडर सेटिङहरू</string>\n    <string name=\"search_on_s\">%s मा खोज्नुहोस्</string>\n    <string name=\"delete_manga\">माङ्गा हटाउनुहोस्</string>\n    <string name=\"switch_pages\">पृष्ठ स्विच गर्नुहोस्</string>\n    <string name=\"_continue\">जारी राख्नुहोस्</string>\n    <string name=\"error\">त्रुटि</string>\n    <string name=\"search_history_cleared\">खाली गरियो</string>\n    <string name=\"clear_thumbs_cache\">थम्बनेल क्यास खाली गर्नुहोस्</string>\n    <string name=\"clear_search_history\">खोज इतिहास खाली गर्नुहोस्</string>\n    <string name=\"domain\">डोमेन</string>\n    <string name=\"internal_storage\">आन्तरिक भण्डारण</string>\n    <string name=\"app_update_available\">एपको नयाँ संस्करण उपलब्ध छ</string>\n    <string name=\"external_storage\">बाह्य भण्डारण</string>\n    <string name=\"open_in_browser\">वेब ब्राउजरमा खोल्नुहोस्</string>\n    <string name=\"download\">डाउनलोड</string>\n    <string name=\"notifications_settings\">सूचना सेटिङ</string>\n    <string name=\"light_indicator\">LED सूचक</string>\n    <string name=\"vibration\">भाईब्रेशन</string>\n    <string name=\"favourites_categories\">मनपर्ने वर्गहरू</string>\n    <string name=\"remove_category\">हटाउनुहोस्</string>\n    <string name=\"text_empty_holder_primary\">यहाँ अलि खाली जस्तो छ…</string>\n    <string name=\"text_search_holder_secondary\">सोधपुछ सुधार गर्ने प्रयास गर्नुहोस्।</string>\n    <string name=\"text_history_holder_primary\">तपाईँले पढेको कुरा यहाँ देखाइनेछ</string>\n    <string name=\"text_history_holder_secondary\">«अन्वेषण» खण्डमा गएर के पढ्ने हेर्नुहोस्</string>\n    <string name=\"pages_animation\">पृष्ठ एनिमेसन</string>\n    <string name=\"manga_save_location\">डाउनलोड फोल्डर</string>\n    <string name=\"recent_manga\">हालैका</string>\n    <string name=\"not_available\">उपलब्ध छैन</string>\n    <string name=\"text_feed_holder\">तपाईंले पढिरहनु भएको माङ्गाको नयाँ अध्यायहरू यहाँ देखाइएको छ</string>\n    <string name=\"search_results\">खोज परिणामहरू</string>\n    <string name=\"clear_updates_feed\">अपडेट फिड खाली गर्नुहोस्</string>\n    <string name=\"rotate_screen\">स्क्रिन घुमाउनुहोस्</string>\n    <string name=\"update\">अपडेट</string>\n    <string name=\"passwords_mismatch\">पासवर्डहरू म्याच गरेनन्</string>\n    <string name=\"about\">बारेमा</string>\n    <string name=\"app_version\">संस्करण %s</string>\n    <string name=\"operation_not_supported\">यो अपरेशन समर्थित छैन</string>\n    <string name=\"no_description\">कुनै विवरण छैन</string>\n    <string name=\"clear_pages_cache\">पृष्ठ क्यास खाली गर्नुहोस्</string>\n    <string name=\"notification_sound\">सूचना आवाज</string>\n    <string name=\"text_local_holder_primary\">पहिले केही सेभ गर्नुहोस्</string>\n    <string name=\"text_local_holder_secondary\">अनलाइन क्याटलगबाट केहि सेभ गर्नुहोस् वा फाइलबाट आयात गर्नुहोस्।</string>\n    <string name=\"updates_feed_cleared\">खाली गरियो</string>\n    <string name=\"protect_application\">एप सुरक्षित गर्नुहोस्</string>\n    <string name=\"protect_application_summary\">Kotatsu सुरु गर्दा पासवर्ड माग्नुहोस्</string>\n    <string name=\"repeat_password\">पासवर्ड दोहोर्याउनुहोस्</string>\n    <string name=\"delete\">हटाउनुहोस्</string>\n    <string name=\"text_file_not_supported\">या त ZIP वा CBZ फाइल छान्नुहोस्।</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"notifications\">सूचनाहरू</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%2$d को %1$d अन</string>\n    <string name=\"manga_shelf\">दराज</string>\n    <string name=\"cannot_find_available_storage\">भण्डारण उपलब्ध छैन</string>\n    <string name=\"other_storage\">अन्य भण्डारण</string>\n    <string name=\"done\">सकियो</string>\n    <string name=\"all_favourites\">सबै मनपर्नेहरू</string>\n    <string name=\"favourites_category_empty\">खाली वर्ग</string>\n    <string name=\"read_later\">पछि पढ्ने</string>\n    <string name=\"updates\">अपडेटहरू</string>\n    <string name=\"new_version_s\">नयाँ संस्करण: %s</string>\n    <string name=\"size_s\">साइज: %s</string>\n    <string name=\"feed_will_update_soon\">फिड अपडेट चाँडै सुरु हुनेछ</string>\n    <string name=\"track_sources\">अपडेटहरू खोज्नुहोस्</string>\n    <string name=\"dont_check\">जाँच नगर्नुहोस्</string>\n    <string name=\"wrong_password\">गलत पासवर्ड</string>\n    <string name=\"check_for_updates\">अपडेटका लागि जाँच गर्नुहोस्</string>\n    <string name=\"no_update_available\">कुनै अपडेट उपलब्ध छैन</string>\n    <string name=\"explore\">अन्वेषण</string>\n    <string name=\"enter_password\">पासवर्ड एन्टर गर्नुहोस्</string>\n    <string name=\"just_now\">भर्खरै</string>\n    <string name=\"reader_mode_hint\">छनोट गरिएको कन्फिगरेसन यो मङ्गाको लागि सम्झिनेछ</string>\n    <string name=\"next\">अर्को</string>\n    <string name=\"never\">कहिल्यै</string>\n    <string name=\"zoom_mode_fit_center\">फिट केन्द्र</string>\n    <string name=\"zoom_mode_fit_height\">उचाइमा फिट</string>\n    <string name=\"zoom_mode_fit_width\">चौडाइमा फिट</string>\n    <string name=\"zoom_mode_keep_start\">सुरुमा राख्नुहोस्</string>\n    <string name=\"black_dark_theme\">कालो</string>\n    <string name=\"black_dark_theme_summary\">AMOLED स्क्रिनहरूमा कम पावर प्रयोग हुन्छ</string>\n    <string name=\"backup_restore\">जगेडाना र पुनर्स्थापना</string>\n    <string name=\"create_backup\">डाटा ब्याकअप सिर्जना गर्नुहोस्</string>\n    <string name=\"restore_backup\">ब्याकअपबाट पुनर्स्थापना गर्नुहोस्</string>\n    <string name=\"data_restored\">पुनर्स्थापित</string>\n    <string name=\"preparing_\">तयारी गर्दै…</string>\n    <string name=\"file_not_found\">फाइल फेला परेन</string>\n    <string name=\"data_restored_success\">सबै डाटा पुनर्स्थापित गरियो</string>\n    <string name=\"data_restored_with_errors\">डाटा पुनर्स्थापित गरियो, तर त्यहाँ त्रुटिहरू पनि छन्</string>\n    <string name=\"backup_information\">तपाईं आफ्नो इतिहास र मनपर्ने को जगेडा सिर्जना र यसलाई पुनर्स्थापित गर्न सक्नुहुन्छ</string>\n    <string name=\"yesterday\">हिजो</string>\n    <string name=\"long_ago\">धेरै पहिले</string>\n    <string name=\"group\">समूह</string>\n    <string name=\"today\">आज</string>\n    <string name=\"silent\">मौनधारण</string>\n    <string name=\"captcha_solve\">समाधान गर्नुहोस्</string>\n    <string name=\"clear_cookies\">कुकीहरू खाली गर्नुहोस्</string>\n    <string name=\"cookies_cleared\">सबै कुकीहरू हटाइयो</string>\n    <string name=\"clear_feed\">फिड खाली गर्नुहोस्</string>\n    <string name=\"default_s\">पूर्वनिर्धारित: %s</string>\n    <string name=\"protect_application_subtitle\">एप सुरु गर्न पासवर्ड प्रविष्ट गर्नुहोस्</string>\n    <string name=\"confirm\">पुष्टि गर्नुहोस्</string>\n    <string name=\"password_length_hint\">पासवर्ड ४ वर्ण वा सोभन्दा बढी हुनुपर्छ</string>\n    <string name=\"text_clear_search_history_prompt\">हालका सबै खोज प्रश्नहरू स्थायी रूपमा हटाउने हो\\?</string>\n    <string name=\"text_clear_updates_feed_prompt\">सबै अद्यावधिक इतिहास स्थायी रूपमा खाली गर्ने हो\\?</string>\n    <string name=\"auth_required\">यो सामग्री हेर्न साइन इन गर्नुहोस्</string>\n    <string name=\"welcome\">स्वागत छ</string>\n    <string name=\"backup_saved\">ब्याकअप सुरक्षित गरियो छ</string>\n    <string name=\"tracker_warning\">केही यन्त्रहरूमा फरक प्रणाली व्यवहार हुन्छ, जसले पृष्ठभूमि कार्यहरू तोड्न सक्छ।</string>\n    <string name=\"queued\">लामबद्ध</string>\n    <string name=\"about_app_translation_summary\">यो एप अनुवाद गर्नुहोस्</string>\n    <string name=\"about_app_translation\">अनुवादन</string>\n    <string name=\"auth_complete\">अधिकृत</string>\n    <string name=\"auth_not_supported_by\">%s मा लग इन गर्न समर्थित छैन</string>\n    <string name=\"text_clear_cookies_prompt\">मा लग इन गर्न समर्थित छैन</string>\n    <string name=\"state_finished\">समाप्त भयो</string>\n    <string name=\"state_ongoing\">जारी छ</string>\n    <string name=\"system_default\">पूर्वनिर्धारित</string>\n    <string name=\"exclude_nsfw_from_history\">इतिहासबाट NSFW मङ्गा बहिष्कार गर्नुहोस्</string>\n    <string name=\"show_pages_numbers\">अंकित पृष्ठहरू</string>\n    <string name=\"screenshots_policy\">स्क्रिनसट नीति</string>\n    <string name=\"screenshots_allow\">अनुमति दिनुहोस्</string>\n    <string name=\"screenshots_block_nsfw\">NSFW मा रोक लगाउनुहोस्</string>\n    <string name=\"screenshots_block_all\">सधैं ब्लक गर्नुहोस्</string>\n    <string name=\"suggestions\">सुझावहरू</string>\n    <string name=\"suggestions_enable\">सुझावहरू सक्षम गर्नुहोस्</string>\n    <string name=\"suggestions_info\">सबै डेटा यस उपकरणमा स्थानीय रूपमा मात्र विश्लेषण गरिन्छ र कहिँ पनि पठाइँदैन।</string>\n    <string name=\"text_suggestion_holder\">मङ्गा पढ्न सुरु गर्नुहोस् र तपाईंले व्यक्तिगत सुझावहरू प्राप्त गर्नुहुनेछ</string>\n    <string name=\"exclude_nsfw_from_suggestions\">NSFW मंगा सुझाव नगर्नुहोस्</string>\n    <string name=\"enabled\">सक्षम गरियो</string>\n    <string name=\"disabled\">अक्षम</string>\n    <string name=\"reset_filter\">फिल्टर रिसेट गर्नुहोस्</string>\n    <string name=\"only_using_wifi\">Wi-Fi मा मात्र</string>\n    <string name=\"always\">सधैं</string>\n    <string name=\"preload_pages\">प्रिलोड पृष्ठहरू</string>\n    <string name=\"logged_in_as\">%s को रूपमा लग इन गरियो</string>\n    <string name=\"nsfw\">१८+</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"create_category\">नयाँ श्रेणी</string>\n    <string name=\"scale_mode\">स्केल मोड</string>\n    <string name=\"tap_to_try_again\">फेरि प्रयास गर्न ट्याप गर्नुहोस्</string>\n    <string name=\"check_for_new_chapters\">नयाँ अध्यायहरूको लागि जाँच गर्नुहोस्</string>\n    <string name=\"right_to_left\">दायाँ-देखि-बायाँ</string>\n    <string name=\"chapters_empty\">यो मङ्गामा कुनै अध्याय छैन</string>\n    <string name=\"captcha_required\">क्याप्चा आवश्यक छ</string>\n    <string name=\"sign_in\">साइन इन गर्नुहोस्</string>\n    <string name=\"reverse\">उल्टो</string>\n    <string name=\"read_more\">थप पढ्नुहोस्</string>\n    <string name=\"chapter_is_missing\">अध्याय हराइरहेको छ</string>\n    <string name=\"genres\">विधाहरू</string>\n    <string name=\"suggestions_summary\">तपाईको प्राथमिकतामा आधारित मंगा सुझाव दिनुहोस्</string>\n    <string name=\"onboard_text\">तपाईले मङ्गा पढ्न चाहनुहुने भाषाहरू चयन गर्नुहोस्। तपाईं यसलाई पछि सेटिङहरूमा परिवर्तन गर्न सक्नुहुन्छ।</string>\n    <string name=\"various_languages\">विभिन्न भाषाहरू</string>\n    <string name=\"search_chapters\">अध्याय खोज्नुहोस्</string>\n    <string name=\"appearance\">उपस्थिति</string>\n    <string name=\"history_shortcuts\">हालैको मंगा सर्टकट देखाउनुहोस्</string>\n    <string name=\"reader_control_ltr_summary\">दायाँ किनारामा ट्याप गर्नुहोस् वा दायाँ कुञ्जी थिच्दा सधैं अर्को पृष्ठमा स्विच हुन्छ</string>\n    <string name=\"reader_control_ltr\">Ergonomic पाठक नियन्त्रण</string>\n    <string name=\"manga_error_description_pattern\">त्रुटि विवरण:&lt;br&gt; &lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt; 1. &lt;a href=%2$s&gt;वेब ब्राउजरमा मंगा खोल्ने&lt;/a&gt; प्रयास गर्नुहोस् कि यो यसको स्रोतमा उपलब्ध छ&lt;br&gt; 2. निश्चित गर्नुहोस् कि तपाइँ &lt;a href=kotatsu://about&gt;Kotatsu को नवीनतम संस्करण&lt;/a&gt; प्रयोग गर्दै हुनुहुन्छ&lt;br&gt; 3. यदि यो उपलब्ध छ भने, विकासकर्ताहरूलाई त्रुटि रिपोर्ट पठाउनुहोस्।</string>\n    <string name=\"suggestions_excluded_genres\">जानरा अलग गर्नु</string>\n    <string name=\"suggestions_updating\">सुझावहरू अपडेट गर्दै</string>\n    <string name=\"text_empty_holder_secondary_filtered\">तपाईंले चयन गर्नुभएको फिल्टरसँग मिल्दो कुनै माङ्गा छैन</string>\n    <string name=\"download_slowdown\">डाउनलोड ढिलो गरियो</string>\n    <string name=\"download_slowdown_summary\">तपाईंको IP ठेगाना अवरुद्ध हुनबाट जोगिन मद्दत गर्दछ</string>\n    <string name=\"chapters_grid_view\">ग्रिड भ्यू</string>\n    <string name=\"suggestions_excluded_genres_summary\">विधाहरू निर्दिष्ट गर्नुहोस् जुन तपाईं सुझावहरूमा हेर्न चाहनुहुन्न</string>\n    <string name=\"text_delete_local_manga_batch\">यन्त्रबाट चयन गरिएका वस्तुहरू स्थायी मेट्ने हो?</string>\n    <string name=\"local_manga_processing\">मांगा प्रशोधन सेब गरियो</string>\n    <string name=\"removal_completed\">हटाउने कार्य सम्पन्न भयो</string>\n    <string name=\"retry\">पुन: प्रयास गर्नुहोस्</string>\n    <string name=\"back\">पछाडि</string>\n    <string name=\"chapters_will_removed_background\">अध्याय पृष्ठभूमिमा हटाइनेछ</string>\n    <string name=\"canceled\">रद्द गरियो</string>\n    <string name=\"account_already_exists\">खाता पहिल्यै अवस्थित छ</string>\n    <string name=\"sync\">सिंक्रोनाइजेसन</string>\n    <string name=\"sync_title\">आफ्नो डाटा सिंक गर्नुहोस्</string>\n    <string name=\"email_enter_hint\">जारी राख्न आफ्नो इमेल प्रविष्ट गर्नुहोस्</string>\n    <string name=\"hide\">लुकाउनुहोस्</string>\n    <string name=\"pages_saved\">पृष्ठहरू सुरक्षित गरियो</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<bool name=\"light_status_bar\">false</bool>\n\t<bool name=\"light_navigation_bar\">false</bool>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<color name=\"error\">#FFB4A9</color>\n\t<color name=\"errorContainer\">#930006</color>\n\t<color name=\"onError\">#680003</color>\n\t<color name=\"onErrorContainer\">#FFDAD4</color>\n\t<color name=\"warning\">#FB8C00</color>\n\t<color name=\"launcher_background\">#222222</color>\n\t<color name=\"common_green\">#81C784</color>\n\t<color name=\"common_yellow\">#FFF176</color>\n\t<color name=\"common_red\">#E57373</color>\n\t<color name=\"dim2\">#C8000000</color>\n\t<color name=\"dim_lite\">#66FFFFFF</color>\n\t<color name=\"nsfw_18\">#BF360C</color>\n\t<color name=\"nsfw_16\">#FF6F00</color>\n\n\t<!-- Color schemes colors -->\n\t<color name=\"background_miku\">#191C1C</color>\n\t<color name=\"background_asuka\">#201A1A</color>\n\t<color name=\"background_mion\">#191C1A</color>\n\t<color name=\"background_rikka\">#201A1A</color>\n\t<color name=\"background_sakura\">#191C1D</color>\n\t<color name=\"background_mamimi\">#201A1A</color>\n\t<color name=\"background_kanade\">#222222</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/colors_kotatsu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Colors for Default theme\n  ~ M3 colors generated by Material Theme Builder (https://goo.gle/material-theme-builder-web)\n-->\n<resources>\n\t<color name=\"kotatsu_primary\">#ABC7FF</color>\n\t<color name=\"kotatsu_primaryContainer\">#00458F</color>\n\t<color name=\"kotatsu_onPrimary\">#002F65</color>\n\t<color name=\"kotatsu_onPrimaryContainer\">#D7E3FF</color>\n\t<color name=\"kotatsu_inversePrimary\">#205DAF</color>\n\t<color name=\"kotatsu_secondary\">#BEC6DC</color>\n\t<color name=\"kotatsu_secondaryContainer\">#3E4759</color>\n\t<color name=\"kotatsu_onSecondary\">#283041</color>\n\t<color name=\"kotatsu_onSecondaryContainer\">#DAE2F9</color>\n\t<color name=\"kotatsu_tertiary\">#DDBCE0</color>\n\t<color name=\"kotatsu_tertiaryContainer\">#573E5C</color>\n\t<color name=\"kotatsu_onTertiary\">#3F2844</color>\n\t<color name=\"kotatsu_onTertiaryContainer\">#FAD8FD</color>\n\t<color name=\"kotatsu_surface\">#121316</color>\n\t<color name=\"kotatsu_surfaceDim\">#121316</color>\n\t<color name=\"kotatsu_surfaceBright\">#38393C</color>\n\t<color name=\"kotatsu_surfaceContainerLowest\">#0D0E11</color>\n\t<color name=\"kotatsu_surfaceContainerLow\">#1A1B1F</color>\n\t<color name=\"kotatsu_surfaceContainer\">#1F1F23</color>\n\t<color name=\"kotatsu_surfaceContainerHigh\">#292A2D</color>\n\t<color name=\"kotatsu_surfaceContainerHighest\">#343538</color>\n\t<color name=\"kotatsu_surfaceVariant\">#44474E</color>\n\t<color name=\"kotatsu_onSurface\">#C7C6CA</color>\n\t<color name=\"kotatsu_onSurfaceVariant\">#C4C6D0</color>\n\t<color name=\"kotatsu_inverseSurface\">#E3E2E6</color>\n\t<color name=\"kotatsu_inverseOnSurface\">#1A1B1F</color>\n\t<color name=\"kotatsu_background\">#1A1B1F</color>\n\t<color name=\"kotatsu_outline\">#8E9099</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/colors_themed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<!-- Totoro -->\n\t<color name=\"totoro_primary\">#A6C8FF</color>\n\t<color name=\"totoro_onPrimary\">#01315E</color>\n\t<color name=\"totoro_primaryContainer\">#224876</color>\n\t<color name=\"totoro_onPrimaryContainer\">#D4E3FF</color>\n\t<color name=\"totoro_secondary\">#BCC7DC</color>\n\t<color name=\"totoro_onSecondary\">#273141</color>\n\t<color name=\"totoro_secondaryContainer\">#3D4758</color>\n\t<color name=\"totoro_onSecondaryContainer\">#D8E3F8</color>\n\t<color name=\"totoro_tertiary\">#DABDE2</color>\n\t<color name=\"totoro_onTertiary\">#3D2846</color>\n\t<color name=\"totoro_tertiaryContainer\">#553F5D</color>\n\t<color name=\"totoro_onTertiaryContainer\">#F7D8FF</color>\n\t<color name=\"totoro_error\">#FFB4AB</color>\n\t<color name=\"totoro_onError\">#690005</color>\n\t<color name=\"totoro_errorContainer\">#93000A</color>\n\t<color name=\"totoro_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"totoro_background\">#111318</color>\n\t<color name=\"totoro_onBackground\">#E1E2E9</color>\n\t<color name=\"totoro_surface\">#111318</color>\n\t<color name=\"totoro_onSurface\">#E1E2E9</color>\n\t<color name=\"totoro_surfaceVariant\">#43474E</color>\n\t<color name=\"totoro_onSurfaceVariant\">#C3C6CF</color>\n\t<color name=\"totoro_outline\">#8D9199</color>\n\t<color name=\"totoro_outlineVariant\">#43474E</color>\n\t<color name=\"totoro_scrim\">#000000</color>\n\t<color name=\"totoro_inverseSurface\">#E1E2E9</color>\n\t<color name=\"totoro_inverseOnSurface\">#2E3035</color>\n\t<color name=\"totoro_inversePrimary\">#3C6090</color>\n\t<color name=\"totoro_primaryFixed\">#D4E3FF</color>\n\t<color name=\"totoro_onPrimaryFixed\">#001C3A</color>\n\t<color name=\"totoro_primaryFixedDim\">#A6C8FF</color>\n\t<color name=\"totoro_onPrimaryFixedVariant\">#224876</color>\n\t<color name=\"totoro_secondaryFixed\">#D8E3F8</color>\n\t<color name=\"totoro_onSecondaryFixed\">#111C2B</color>\n\t<color name=\"totoro_secondaryFixedDim\">#BCC7DC</color>\n\t<color name=\"totoro_onSecondaryFixedVariant\">#3D4758</color>\n\t<color name=\"totoro_tertiaryFixed\">#F7D8FF</color>\n\t<color name=\"totoro_onTertiaryFixed\">#271430</color>\n\t<color name=\"totoro_tertiaryFixedDim\">#DABDE2</color>\n\t<color name=\"totoro_onTertiaryFixedVariant\">#553F5D</color>\n\t<color name=\"totoro_surfaceDim\">#111318</color>\n\t<color name=\"totoro_surfaceBright\">#37393E</color>\n\t<color name=\"totoro_surfaceContainerLowest\">#0C0E13</color>\n\t<color name=\"totoro_surfaceContainerLow\">#191C20</color>\n\t<color name=\"totoro_surfaceContainer\">#1D2024</color>\n\t<color name=\"totoro_surfaceContainerHigh\">#282A2F</color>\n\t<color name=\"totoro_surfaceContainerHighest\">#32353A</color>\n\t<!-- Asuka -->\n\t<color name=\"asuka_primary\">#FFB4A8</color>\n\t<color name=\"asuka_onPrimary\">#561E17</color>\n\t<color name=\"asuka_primaryContainer\">#73342B</color>\n\t<color name=\"asuka_onPrimaryContainer\">#FFDAD5</color>\n\t<color name=\"asuka_secondary\">#E7BDB6</color>\n\t<color name=\"asuka_onSecondary\">#442925</color>\n\t<color name=\"asuka_secondaryContainer\">#5D3F3B</color>\n\t<color name=\"asuka_onSecondaryContainer\">#FFDAD5</color>\n\t<color name=\"asuka_tertiary\">#DEC38C</color>\n\t<color name=\"asuka_onTertiary\">#3E2E04</color>\n\t<color name=\"asuka_tertiaryContainer\">#564419</color>\n\t<color name=\"asuka_onTertiaryContainer\">#FCDFA6</color>\n\t<color name=\"asuka_error\">#FFB4AB</color>\n\t<color name=\"asuka_onError\">#690005</color>\n\t<color name=\"asuka_errorContainer\">#93000A</color>\n\t<color name=\"asuka_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"asuka_background\">#1A1110</color>\n\t<color name=\"asuka_onBackground\">#F1DEDC</color>\n\t<color name=\"asuka_surface\">#1A1110</color>\n\t<color name=\"asuka_onSurface\">#F1DEDC</color>\n\t<color name=\"asuka_surfaceVariant\">#534341</color>\n\t<color name=\"asuka_onSurfaceVariant\">#D8C2BE</color>\n\t<color name=\"asuka_outline\">#A08C89</color>\n\t<color name=\"asuka_outlineVariant\">#534341</color>\n\t<color name=\"asuka_scrim\">#000000</color>\n\t<color name=\"asuka_inverseSurface\">#F1DEDC</color>\n\t<color name=\"asuka_inverseOnSurface\">#392E2C</color>\n\t<color name=\"asuka_inversePrimary\">#904A40</color>\n\t<color name=\"asuka_primaryFixed\">#FFDAD5</color>\n\t<color name=\"asuka_onPrimaryFixed\">#3B0905</color>\n\t<color name=\"asuka_primaryFixedDim\">#FFB4A8</color>\n\t<color name=\"asuka_onPrimaryFixedVariant\">#73342B</color>\n\t<color name=\"asuka_secondaryFixed\">#FFDAD5</color>\n\t<color name=\"asuka_onSecondaryFixed\">#2C1512</color>\n\t<color name=\"asuka_secondaryFixedDim\">#E7BDB6</color>\n\t<color name=\"asuka_onSecondaryFixedVariant\">#5D3F3B</color>\n\t<color name=\"asuka_tertiaryFixed\">#FCDFA6</color>\n\t<color name=\"asuka_onTertiaryFixed\">#251A00</color>\n\t<color name=\"asuka_tertiaryFixedDim\">#DEC38C</color>\n\t<color name=\"asuka_onTertiaryFixedVariant\">#564419</color>\n\t<color name=\"asuka_surfaceDim\">#1A1110</color>\n\t<color name=\"asuka_surfaceBright\">#423735</color>\n\t<color name=\"asuka_surfaceContainerLowest\">#140C0B</color>\n\t<color name=\"asuka_surfaceContainerLow\">#231918</color>\n\t<color name=\"asuka_surfaceContainer\">#271D1C</color>\n\t<color name=\"asuka_surfaceContainerHigh\">#322826</color>\n\t<color name=\"asuka_surfaceContainerHighest\">#3D3231</color>\n\t<!-- Itsuka -->\n\t<color name=\"itsuka_primary\">#FFBA8F</color>\n\t<color name=\"itsuka_onPrimary\">#512400</color>\n\t<color name=\"itsuka_primaryContainer\">#FE9245</color>\n\t<color name=\"itsuka_onPrimaryContainer\">#6B3100</color>\n\t<color name=\"itsuka_secondary\">#F7B993</color>\n\t<color name=\"itsuka_onSecondary\">#4C270B</color>\n\t<color name=\"itsuka_secondaryContainer\">#673C1F</color>\n\t<color name=\"itsuka_onSecondaryContainer\">#E4A883</color>\n\t<color name=\"itsuka_tertiary\">#C8D14D</color>\n\t<color name=\"itsuka_onTertiary\">#303300</color>\n\t<color name=\"itsuka_tertiaryContainer\">#ACB533</color>\n\t<color name=\"itsuka_onTertiaryContainer\">#404500</color>\n\t<color name=\"itsuka_error\">#FFB4AB</color>\n\t<color name=\"itsuka_onError\">#690005</color>\n\t<color name=\"itsuka_errorContainer\">#93000A</color>\n\t<color name=\"itsuka_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"itsuka_background\">#1A110C</color>\n\t<color name=\"itsuka_onBackground\">#F1DFD5</color>\n\t<color name=\"itsuka_surface\">#1A110C</color>\n\t<color name=\"itsuka_onSurface\">#F1DFD5</color>\n\t<color name=\"itsuka_surfaceVariant\">#554338</color>\n\t<color name=\"itsuka_onSurfaceVariant\">#DCC1B3</color>\n\t<color name=\"itsuka_outline\">#A48C7F</color>\n\t<color name=\"itsuka_outlineVariant\">#554338</color>\n\t<color name=\"itsuka_scrim\">#000000</color>\n\t<color name=\"itsuka_inverseSurface\">#F1DFD5</color>\n\t<color name=\"itsuka_inverseOnSurface\">#392E28</color>\n\t<color name=\"itsuka_inversePrimary\">#974800</color>\n\t<color name=\"itsuka_primaryFixed\">#FFDBC7</color>\n\t<color name=\"itsuka_onPrimaryFixed\">#311300</color>\n\t<color name=\"itsuka_primaryFixedDim\">#FFB689</color>\n\t<color name=\"itsuka_onPrimaryFixedVariant\">#733500</color>\n\t<color name=\"itsuka_secondaryFixed\">#FFDBC7</color>\n\t<color name=\"itsuka_onSecondaryFixed\">#311300</color>\n\t<color name=\"itsuka_secondaryFixedDim\">#F7B993</color>\n\t<color name=\"itsuka_onSecondaryFixedVariant\">#673C1F</color>\n\t<color name=\"itsuka_tertiaryFixed\">#E1EB64</color>\n\t<color name=\"itsuka_onTertiaryFixed\">#1B1D00</color>\n\t<color name=\"itsuka_tertiaryFixedDim\">#C5CE4A</color>\n\t<color name=\"itsuka_onTertiaryFixedVariant\">#464A00</color>\n\t<color name=\"itsuka_surfaceDim\">#1A110C</color>\n\t<color name=\"itsuka_surfaceBright\">#423730</color>\n\t<color name=\"itsuka_surfaceContainerLowest\">#150C07</color>\n\t<color name=\"itsuka_surfaceContainerLow\">#231A14</color>\n\t<color name=\"itsuka_surfaceContainer\">#271E18</color>\n\t<color name=\"itsuka_surfaceContainerHigh\">#322822</color>\n\t<color name=\"itsuka_surfaceContainerHighest\">#3E322C</color>\n\t<!-- Kanade -->\n\t<color name=\"kanade_primary\">#FFFFFF</color>\n\t<color name=\"kanade_onPrimary\">#2F2F3D</color>\n\t<color name=\"kanade_primaryContainer\">#E3E1F3</color>\n\t<color name=\"kanade_onPrimaryContainer\">#474755</color>\n\t<color name=\"kanade_secondary\">#DDDCDC</color>\n\t<color name=\"kanade_onSecondary\">#252626</color>\n\t<color name=\"kanade_secondaryContainer\">#919191</color>\n\t<color name=\"kanade_onSecondaryContainer\">#000000</color>\n\t<color name=\"kanade_tertiary\">#DADDDD</color>\n\t<color name=\"kanade_onTertiary\">#232627</color>\n\t<color name=\"kanade_tertiaryContainer\">#A9ACAC</color>\n\t<color name=\"kanade_onTertiaryContainer\">#1E2122</color>\n\t<color name=\"kanade_error\">#FFD2CC</color>\n\t<color name=\"kanade_onError\">#540003</color>\n\t<color name=\"kanade_errorContainer\">#FF5449</color>\n\t<color name=\"kanade_onErrorContainer\">#000000</color>\n\t<color name=\"kanade_background\">#141314</color>\n\t<color name=\"kanade_onBackground\">#E5E1E3</color>\n\t<color name=\"kanade_surface\">#141314</color>\n\t<color name=\"kanade_onSurface\">#FFFFFF</color>\n\t<color name=\"kanade_surfaceVariant\">#47464C</color>\n\t<color name=\"kanade_onSurfaceVariant\">#DEDBE2</color>\n\t<color name=\"kanade_outline\">#B3B1B7</color>\n\t<color name=\"kanade_outlineVariant\">#918F96</color>\n\t<color name=\"kanade_scrim\">#000000</color>\n\t<color name=\"kanade_inverseSurface\">#E5E1E3</color>\n\t<color name=\"kanade_inverseOnSurface\">#2A2A2B</color>\n\t<color name=\"kanade_inversePrimary\">#474755</color>\n\t<color name=\"kanade_primaryFixed\">#E3E1F3</color>\n\t<color name=\"kanade_onPrimaryFixed\">#0F101C</color>\n\t<color name=\"kanade_primaryFixedDim\">#C6C5D6</color>\n\t<color name=\"kanade_onPrimaryFixedVariant\">#353543</color>\n\t<color name=\"kanade_secondaryFixed\">#E3E2E2</color>\n\t<color name=\"kanade_onSecondaryFixed\">#101112</color>\n\t<color name=\"kanade_secondaryFixedDim\">#C7C6C6</color>\n\t<color name=\"kanade_onSecondaryFixedVariant\">#353636</color>\n\t<color name=\"kanade_tertiaryFixed\">#E0E3E3</color>\n\t<color name=\"kanade_onTertiaryFixed\">#0E1212</color>\n\t<color name=\"kanade_tertiaryFixedDim\">#C4C7C7</color>\n\t<color name=\"kanade_onTertiaryFixedVariant\">#333737</color>\n\t<color name=\"kanade_surfaceDim\">#141314</color>\n\t<color name=\"kanade_surfaceBright\">#454445</color>\n\t<color name=\"kanade_surfaceContainerLowest\">#070708</color>\n\t<color name=\"kanade_surfaceContainerLow\">#1E1D1E</color>\n\t<color name=\"kanade_surfaceContainer\">#282829</color>\n\t<color name=\"kanade_surfaceContainerHigh\">#333233</color>\n\t<color name=\"kanade_surfaceContainerHighest\">#3E3D3E</color>\n\t<!-- Mamimi -->\n\t<color name=\"mamimi_primary\">#AFC6FF</color>\n\t<color name=\"mamimi_onPrimary\">#142F60</color>\n\t<color name=\"mamimi_primaryContainer\">#2D4578</color>\n\t<color name=\"mamimi_onPrimaryContainer\">#D9E2FF</color>\n\t<color name=\"mamimi_secondary\">#BFC6DC</color>\n\t<color name=\"mamimi_onSecondary\">#293042</color>\n\t<color name=\"mamimi_secondaryContainer\">#404659</color>\n\t<color name=\"mamimi_onSecondaryContainer\">#DBE2F9</color>\n\t<color name=\"mamimi_tertiary\">#DFBBDE</color>\n\t<color name=\"mamimi_onTertiary\">#412742</color>\n\t<color name=\"mamimi_tertiaryContainer\">#593E5A</color>\n\t<color name=\"mamimi_onTertiaryContainer\">#FDD7FB</color>\n\t<color name=\"mamimi_error\">#FFB4AB</color>\n\t<color name=\"mamimi_onError\">#690005</color>\n\t<color name=\"mamimi_errorContainer\">#93000A</color>\n\t<color name=\"mamimi_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"mamimi_background\">#121318</color>\n\t<color name=\"mamimi_onBackground\">#E2E2E9</color>\n\t<color name=\"mamimi_surface\">#121318</color>\n\t<color name=\"mamimi_onSurface\">#E2E2E9</color>\n\t<color name=\"mamimi_surfaceVariant\">#44464F</color>\n\t<color name=\"mamimi_onSurfaceVariant\">#C5C6D0</color>\n\t<color name=\"mamimi_outline\">#8F9099</color>\n\t<color name=\"mamimi_outlineVariant\">#44464F</color>\n\t<color name=\"mamimi_scrim\">#000000</color>\n\t<color name=\"mamimi_inverseSurface\">#E2E2E9</color>\n\t<color name=\"mamimi_inverseOnSurface\">#2F3036</color>\n\t<color name=\"mamimi_inversePrimary\">#465D91</color>\n\t<color name=\"mamimi_primaryFixed\">#D9E2FF</color>\n\t<color name=\"mamimi_onPrimaryFixed\">#001944</color>\n\t<color name=\"mamimi_primaryFixedDim\">#AFC6FF</color>\n\t<color name=\"mamimi_onPrimaryFixedVariant\">#2D4578</color>\n\t<color name=\"mamimi_secondaryFixed\">#DBE2F9</color>\n\t<color name=\"mamimi_onSecondaryFixed\">#141B2C</color>\n\t<color name=\"mamimi_secondaryFixedDim\">#BFC6DC</color>\n\t<color name=\"mamimi_onSecondaryFixedVariant\">#404659</color>\n\t<color name=\"mamimi_tertiaryFixed\">#FDD7FB</color>\n\t<color name=\"mamimi_onTertiaryFixed\">#2A132C</color>\n\t<color name=\"mamimi_tertiaryFixedDim\">#DFBBDE</color>\n\t<color name=\"mamimi_onTertiaryFixedVariant\">#593E5A</color>\n\t<color name=\"mamimi_surfaceDim\">#121318</color>\n\t<color name=\"mamimi_surfaceBright\">#38393E</color>\n\t<color name=\"mamimi_surfaceContainerLowest\">#0C0E13</color>\n\t<color name=\"mamimi_surfaceContainerLow\">#1A1B20</color>\n\t<color name=\"mamimi_surfaceContainer\">#1E1F25</color>\n\t<color name=\"mamimi_surfaceContainerHigh\">#282A2F</color>\n\t<color name=\"mamimi_surfaceContainerHighest\">#33353A</color>\n\t<!-- Miku -->\n\t<color name=\"miku_primary\">#6FDDE2</color>\n\t<color name=\"miku_onPrimary\">#003739</color>\n\t<color name=\"miku_primaryContainer\">#50C1C6</color>\n\t<color name=\"miku_onPrimaryContainer\">#004C4F</color>\n\t<color name=\"miku_secondary\">#A6CECF</color>\n\t<color name=\"miku_onSecondary\">#0C3638</color>\n\t<color name=\"miku_secondaryContainer\">#294F51</color>\n\t<color name=\"miku_onSecondaryContainer\">#98C0C1</color>\n\t<color name=\"miku_tertiary\">#E4BEFF</color>\n\t<color name=\"miku_onTertiary\">#431E60</color>\n\t<color name=\"miku_tertiaryContainer\">#CBA0EB</color>\n\t<color name=\"miku_onTertiaryContainer\">#583375</color>\n\t<color name=\"miku_error\">#FFB4AB</color>\n\t<color name=\"miku_onError\">#690005</color>\n\t<color name=\"miku_errorContainer\">#93000A</color>\n\t<color name=\"miku_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"miku_background\">#0F1415</color>\n\t<color name=\"miku_onBackground\">#DEE3E3</color>\n\t<color name=\"miku_surface\">#0F1415</color>\n\t<color name=\"miku_onSurface\">#DEE3E3</color>\n\t<color name=\"miku_surfaceVariant\">#3D4949</color>\n\t<color name=\"miku_onSurfaceVariant\">#BCC9C9</color>\n\t<color name=\"miku_outline\">#879393</color>\n\t<color name=\"miku_outlineVariant\">#3D4949</color>\n\t<color name=\"miku_scrim\">#000000</color>\n\t<color name=\"miku_inverseSurface\">#DEE3E3</color>\n\t<color name=\"miku_inverseOnSurface\">#2C3132</color>\n\t<color name=\"miku_inversePrimary\">#00696D</color>\n\t<color name=\"miku_primaryFixed\">#87F3F8</color>\n\t<color name=\"miku_onPrimaryFixed\">#002021</color>\n\t<color name=\"miku_primaryFixedDim\">#69D7DC</color>\n\t<color name=\"miku_onPrimaryFixedVariant\">#004F52</color>\n\t<color name=\"miku_secondaryFixed\">#C2EAEC</color>\n\t<color name=\"miku_onSecondaryFixed\">#002021</color>\n\t<color name=\"miku_secondaryFixedDim\">#A6CECF</color>\n\t<color name=\"miku_onSecondaryFixedVariant\">#274D4E</color>\n\t<color name=\"miku_tertiaryFixed\">#F2DAFF</color>\n\t<color name=\"miku_onTertiaryFixed\">#2D044A</color>\n\t<color name=\"miku_tertiaryFixedDim\">#E0B6FF</color>\n\t<color name=\"miku_onTertiaryFixedVariant\">#5B3679</color>\n\t<color name=\"miku_surfaceDim\">#0F1415</color>\n\t<color name=\"miku_surfaceBright\">#353A3A</color>\n\t<color name=\"miku_surfaceContainerLowest\">#0A0F0F</color>\n\t<color name=\"miku_surfaceContainerLow\">#171D1D</color>\n\t<color name=\"miku_surfaceContainer\">#1B2121</color>\n\t<color name=\"miku_surfaceContainerHigh\">#262B2B</color>\n\t<color name=\"miku_surfaceContainerHighest\">#303636</color>\n\t<!-- Mion -->\n\t<color name=\"mion_primary\">#A1D39A</color>\n\t<color name=\"mion_onPrimary\">#09390F</color>\n\t<color name=\"mion_primaryContainer\">#235024</color>\n\t<color name=\"mion_onPrimaryContainer\">#BCF0B4</color>\n\t<color name=\"mion_secondary\">#EEBF6D</color>\n\t<color name=\"mion_onSecondary\">#422D00</color>\n\t<color name=\"mion_secondaryContainer\">#5E4200</color>\n\t<color name=\"mion_onSecondaryContainer\">#FFDEA8</color>\n\t<color name=\"mion_tertiary\">#98D4A4</color>\n\t<color name=\"mion_onTertiary\">#003919</color>\n\t<color name=\"mion_tertiaryContainer\">#16512C</color>\n\t<color name=\"mion_onTertiaryContainer\">#B3F1BE</color>\n\t<color name=\"mion_error\">#FFB4AB</color>\n\t<color name=\"mion_onError\">#690005</color>\n\t<color name=\"mion_errorContainer\">#93000A</color>\n\t<color name=\"mion_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"mion_background\">#10140F</color>\n\t<color name=\"mion_onBackground\">#E0E4DB</color>\n\t<color name=\"mion_surface\">#10140F</color>\n\t<color name=\"mion_onSurface\">#E0E4DB</color>\n\t<color name=\"mion_surfaceVariant\">#424940</color>\n\t<color name=\"mion_onSurfaceVariant\">#C2C9BD</color>\n\t<color name=\"mion_outline\">#8C9388</color>\n\t<color name=\"mion_outlineVariant\">#424940</color>\n\t<color name=\"mion_scrim\">#000000</color>\n\t<color name=\"mion_inverseSurface\">#E0E4DB</color>\n\t<color name=\"mion_inverseOnSurface\">#2D322C</color>\n\t<color name=\"mion_inversePrimary\">#3B693A</color>\n\t<color name=\"mion_primaryFixed\">#BCF0B4</color>\n\t<color name=\"mion_onPrimaryFixed\">#002204</color>\n\t<color name=\"mion_primaryFixedDim\">#A1D39A</color>\n\t<color name=\"mion_onPrimaryFixedVariant\">#235024</color>\n\t<color name=\"mion_secondaryFixed\">#FFDEA8</color>\n\t<color name=\"mion_onSecondaryFixed\">#271900</color>\n\t<color name=\"mion_secondaryFixedDim\">#EEBF6D</color>\n\t<color name=\"mion_onSecondaryFixedVariant\">#5E4200</color>\n\t<color name=\"mion_tertiaryFixed\">#B3F1BE</color>\n\t<color name=\"mion_onTertiaryFixed\">#00210C</color>\n\t<color name=\"mion_tertiaryFixedDim\">#98D4A4</color>\n\t<color name=\"mion_onTertiaryFixedVariant\">#16512C</color>\n\t<color name=\"mion_surfaceDim\">#10140F</color>\n\t<color name=\"mion_surfaceBright\">#363A34</color>\n\t<color name=\"mion_surfaceContainerLowest\">#0B0F0A</color>\n\t<color name=\"mion_surfaceContainerLow\">#191D17</color>\n\t<color name=\"mion_surfaceContainer\">#1D211B</color>\n\t<color name=\"mion_surfaceContainerHigh\">#272B25</color>\n\t<color name=\"mion_surfaceContainerHighest\">#323630</color>\n\t<!-- Rikka -->\n\t<color name=\"rikka_primary\">#D3BBFD</color>\n\t<color name=\"rikka_onPrimary\">#39265C</color>\n\t<color name=\"rikka_primaryContainer\">#503C74</color>\n\t<color name=\"rikka_onPrimaryContainer\">#EBDCFF</color>\n\t<color name=\"rikka_secondary\">#CDC2DB</color>\n\t<color name=\"rikka_onSecondary\">#342D40</color>\n\t<color name=\"rikka_secondaryContainer\">#4B4358</color>\n\t<color name=\"rikka_onSecondaryContainer\">#EADEF7</color>\n\t<color name=\"rikka_tertiary\">#E6C36C</color>\n\t<color name=\"rikka_onTertiary\">#3E2E00</color>\n\t<color name=\"rikka_tertiaryContainer\">#594400</color>\n\t<color name=\"rikka_onTertiaryContainer\">#FFDF95</color>\n\t<color name=\"rikka_error\">#FFB4AB</color>\n\t<color name=\"rikka_onError\">#690005</color>\n\t<color name=\"rikka_errorContainer\">#93000A</color>\n\t<color name=\"rikka_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"rikka_background\">#151218</color>\n\t<color name=\"rikka_onBackground\">#E7E0E8</color>\n\t<color name=\"rikka_surface\">#151218</color>\n\t<color name=\"rikka_onSurface\">#E7E0E8</color>\n\t<color name=\"rikka_surfaceVariant\">#49454E</color>\n\t<color name=\"rikka_onSurfaceVariant\">#CBC4CF</color>\n\t<color name=\"rikka_outline\">#948F99</color>\n\t<color name=\"rikka_outlineVariant\">#49454E</color>\n\t<color name=\"rikka_scrim\">#000000</color>\n\t<color name=\"rikka_inverseSurface\">#E7E0E8</color>\n\t<color name=\"rikka_inverseOnSurface\">#322F35</color>\n\t<color name=\"rikka_inversePrimary\">#68548D</color>\n\t<color name=\"rikka_primaryFixed\">#EBDCFF</color>\n\t<color name=\"rikka_onPrimaryFixed\">#230F46</color>\n\t<color name=\"rikka_primaryFixedDim\">#D3BBFD</color>\n\t<color name=\"rikka_onPrimaryFixedVariant\">#503C74</color>\n\t<color name=\"rikka_secondaryFixed\">#EADEF7</color>\n\t<color name=\"rikka_onSecondaryFixed\">#1F182A</color>\n\t<color name=\"rikka_secondaryFixedDim\">#CDC2DB</color>\n\t<color name=\"rikka_onSecondaryFixedVariant\">#4B4358</color>\n\t<color name=\"rikka_tertiaryFixed\">#FFDF95</color>\n\t<color name=\"rikka_onTertiaryFixed\">#251A00</color>\n\t<color name=\"rikka_tertiaryFixedDim\">#E6C36C</color>\n\t<color name=\"rikka_onTertiaryFixedVariant\">#594400</color>\n\t<color name=\"rikka_surfaceDim\">#151218</color>\n\t<color name=\"rikka_surfaceBright\">#3B383E</color>\n\t<color name=\"rikka_surfaceContainerLowest\">#0F0D13</color>\n\t<color name=\"rikka_surfaceContainerLow\">#1D1B20</color>\n\t<color name=\"rikka_surfaceContainer\">#211F24</color>\n\t<color name=\"rikka_surfaceContainerHigh\">#2C292F</color>\n\t<color name=\"rikka_surfaceContainerHighest\">#37343A</color>\n\t<!-- Sakura -->\n\t<color name=\"sakura_primary\">#FFB1C8</color>\n\t<color name=\"sakura_onPrimary\">#541D32</color>\n\t<color name=\"sakura_primaryContainer\">#703348</color>\n\t<color name=\"sakura_onPrimaryContainer\">#FFD9E2</color>\n\t<color name=\"sakura_secondary\">#E3BDC6</color>\n\t<color name=\"sakura_onSecondary\">#422931</color>\n\t<color name=\"sakura_secondaryContainer\">#5B3F47</color>\n\t<color name=\"sakura_onSecondaryContainer\">#FFD9E2</color>\n\t<color name=\"sakura_tertiary\">#99CCFA</color>\n\t<color name=\"sakura_onTertiary\">#003351</color>\n\t<color name=\"sakura_tertiaryContainer\">#074B72</color>\n\t<color name=\"sakura_onTertiaryContainer\">#CCE5FF</color>\n\t<color name=\"sakura_error\">#FFB4AB</color>\n\t<color name=\"sakura_onError\">#690005</color>\n\t<color name=\"sakura_errorContainer\">#93000A</color>\n\t<color name=\"sakura_onErrorContainer\">#FFDAD6</color>\n\t<color name=\"sakura_background\">#191113</color>\n\t<color name=\"sakura_onBackground\">#EFDFE1</color>\n\t<color name=\"sakura_surface\">#191113</color>\n\t<color name=\"sakura_onSurface\">#EFDFE1</color>\n\t<color name=\"sakura_surfaceVariant\">#514347</color>\n\t<color name=\"sakura_onSurfaceVariant\">#D5C2C6</color>\n\t<color name=\"sakura_outline\">#9E8C90</color>\n\t<color name=\"sakura_outlineVariant\">#514347</color>\n\t<color name=\"sakura_scrim\">#000000</color>\n\t<color name=\"sakura_inverseSurface\">#EFDFE1</color>\n\t<color name=\"sakura_inverseOnSurface\">#372E30</color>\n\t<color name=\"sakura_inversePrimary\">#8C4A60</color>\n\t<color name=\"sakura_primaryFixed\">#FFD9E2</color>\n\t<color name=\"sakura_onPrimaryFixed\">#3A071D</color>\n\t<color name=\"sakura_primaryFixedDim\">#FFB1C8</color>\n\t<color name=\"sakura_onPrimaryFixedVariant\">#703348</color>\n\t<color name=\"sakura_secondaryFixed\">#FFD9E2</color>\n\t<color name=\"sakura_onSecondaryFixed\">#2B151C</color>\n\t<color name=\"sakura_secondaryFixedDim\">#E3BDC6</color>\n\t<color name=\"sakura_onSecondaryFixedVariant\">#5B3F47</color>\n\t<color name=\"sakura_tertiaryFixed\">#CCE5FF</color>\n\t<color name=\"sakura_onTertiaryFixed\">#001D31</color>\n\t<color name=\"sakura_tertiaryFixedDim\">#99CCFA</color>\n\t<color name=\"sakura_onTertiaryFixedVariant\">#074B72</color>\n\t<color name=\"sakura_surfaceDim\">#191113</color>\n\t<color name=\"sakura_surfaceBright\">#413739</color>\n\t<color name=\"sakura_surfaceContainerLowest\">#140C0E</color>\n\t<color name=\"sakura_surfaceContainerLow\">#22191C</color>\n\t<color name=\"sakura_surfaceContainer\">#261D20</color>\n\t<color name=\"sakura_surfaceContainerHigh\">#31282A</color>\n\t<color name=\"sakura_surfaceContainerHighest\">#3C3235</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<style name=\"ThemeOverlay.Kotatsu\" parent=\"ThemeOverlay.Material3.Dark\">\n\t\t<item name=\"android:navigationBarColor\">@android:color/transparent</item>\n\t\t<item name=\"android:windowBackground\">?android:colorBackground</item>\n\t</style>\n\n\t<style name=\"ThemeOverlay.Kotatsu.Amoled\" parent=\"\">\n\t\t<item name=\"colorSurface\">@color/background_amoled</item>\n\t\t<item name=\"android:colorBackground\">@color/background_amoled</item>\n\t\t<item name=\"android:windowBackground\">@color/background_amoled</item>\n\t\t<item name=\"colorBackgroundFloating\">@color/background_amoled</item>\n\t</style>\n\n\t<!-- Monet theme only support S+ -->\n\t<style name=\"ThemeOverlay.Kotatsu.Monet\" parent=\"ThemeOverlay.Material3.DynamicColors.Dark\" />\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night-v31/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<style name=\"Base.V31.Kotatsu\" parent=\"Base.V27.Kotatsu\">\n\t\t<item name=\"android:windowSplashScreenAnimatedIcon\">@drawable/avd_splash</item>\n\t\t<item name=\"android:windowSplashScreenAnimationDuration\">@integer/splash_screen_duration</item>\n\t\t<item name=\"android:windowSplashScreenBackground\">@color/m3_sys_color_dynamic_dark_surface</item>\n\t</style>\n\n\t<style name=\"Theme.Kotatsu.AppWidgetContainer\" parent=\"@android:style/Theme.DeviceDefault.DayNight\">\n\t\t<item name=\"android:colorBackground\">@color/m3_ref_palette_dynamic_secondary20</item>\n\t\t<item name=\"android:panelColorBackground\">@color/m3_ref_palette_dynamic_secondary40</item>\n\t\t<item name=\"colorTertiary\">@color/m3_ref_palette_dynamic_secondary40</item>\n\t</style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-nl/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nieuwe hoofdstuk</item>\n        <item quantity=\"other\">%1$d nieuwe hoofdstukken</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d hoofdstuk</item>\n        <item quantity=\"other\">%1$d hoofdstukken</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minuut geleden</item>\n        <item quantity=\"other\">%1$d minuten geleden</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d uur geleden</item>\n        <item quantity=\"other\">%1$d uren geleden</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d dag geleden</item>\n        <item quantity=\"other\">%1$d dagen geleden</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d maand geleden</item>\n        <item quantity=\"other\">%1$d maanden geleden</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d uur</item>\n        <item quantity=\"other\">%1$d uren</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuut</item>\n        <item quantity=\"other\">%1$d minuten</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-nl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"chapters\">Hoofdstukken</string>\n    <string name=\"new_chapters\">Nieuwe hoofdstukken</string>\n    <string name=\"favourites\">Favorieten</string>\n    <string name=\"history\">Geschiedenis</string>\n    <string name=\"error_occurred\">Er is een fout opgetreden</string>\n    <string name=\"network_error\">Netwerk fout</string>\n    <string name=\"details\">Details</string>\n    <string name=\"detailed_list\">Gedetailleerde lijst</string>\n    <string name=\"grid\">Rooster</string>\n    <string name=\"list_mode\">Lijstmodus</string>\n    <string name=\"settings\">Instellingen</string>\n    <string name=\"remote_sources\">Mangabronnen</string>\n    <string name=\"loading_\">Bezig met laden…</string>\n    <string name=\"computing_\">Computeren…</string>\n    <string name=\"chapter_d_of_d\">Hoofdstuk %1$d van %2$d</string>\n    <string name=\"close\">Sluiten</string>\n    <string name=\"try_again\">Probeer het opnieuw</string>\n    <string name=\"retry\">Opnieuw proberen</string>\n    <string name=\"clear_history\">Geschiedenis wissen</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-nn/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<plurals name=\"minutes_ago\">\n\t\t<item quantity=\"one\">%1$d minutt sida</item>\n\t\t<item quantity=\"other\">%1$d minutt sida</item>\n\t</plurals>\n\t<plurals name=\"hours_ago\">\n\t\t<item quantity=\"one\">%1$d time sida</item>\n\t\t<item quantity=\"other\">%1$d timar sida</item>\n\t</plurals>\n\t<plurals name=\"days_ago\">\n\t\t<item quantity=\"one\">%1$d dag sida</item>\n\t\t<item quantity=\"other\">%1$d dagar sida</item>\n\t</plurals>\n\t<plurals name=\"items\">\n\t\t<item quantity=\"one\">%1$d element</item>\n\t\t<item quantity=\"other\">%1$d element</item>\n\t</plurals>\n\t<plurals name=\"new_chapters\">\n\t\t<item quantity=\"one\">%1$d nytt kapittel</item>\n\t\t<item quantity=\"other\">%1$d nye kapittel</item>\n\t</plurals>\n\t<plurals name=\"chapters\">\n\t\t<item quantity=\"one\">%1$d kapittel</item>\n\t\t<item quantity=\"other\">%1$d kapittel</item>\n\t</plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-nn/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"error_occurred\">Det hende ein feil</string>\n    <string name=\"grid\">Rutenett</string>\n    <string name=\"settings\">Innstillingar</string>\n    <string name=\"remote_sources\">Mangakjelder</string>\n    <string name=\"clear_history\">Tøm historikken</string>\n    <string name=\"history_is_empty\">Historikken er tom</string>\n    <string name=\"read\">Les</string>\n    <string name=\"add_to_favourites\">Lik</string>\n    <string name=\"add_new_category\">Ny hop</string>\n    <string name=\"add\">Legg til</string>\n    <string name=\"save\">Hent</string>\n    <string name=\"create_shortcut\">Lag ein snarveg …</string>\n    <string name=\"share_s\">Del %s</string>\n    <string name=\"search\">Søk</string>\n    <string name=\"search_manga\">Søk manga</string>\n    <string name=\"manga_downloading_\">Hentar …</string>\n    <string name=\"processing_\">Handsamar …</string>\n    <string name=\"downloads\">Henta</string>\n    <string name=\"newest\">Nyaste</string>\n    <string name=\"by_rating\">Omdøme</string>\n    <string name=\"filter\">Sil ut</string>\n    <string name=\"light\">Ljos</string>\n    <string name=\"pages\">Sider</string>\n    <string name=\"clear\">Tøm</string>\n    <string name=\"remove\">Ta bort</string>\n    <string name=\"save_page\">Hent sida</string>\n    <string name=\"page_saved\">Henta</string>\n    <string name=\"share_image\">Del biletet</string>\n    <string name=\"_import\">Før inn</string>\n    <string name=\"delete\">Slett</string>\n    <string name=\"text_file_not_supported\">Vel ei ZIP- eller CBZ-fil.</string>\n    <string name=\"no_description\">Ingen utgreiing</string>\n    <string name=\"clear_pages_cache\">Tøm mellomminnet for sider</string>\n    <string name=\"standard\">Vanleg</string>\n    <string name=\"webtoon\">Nettserie</string>\n    <string name=\"grid_size\">Rutenettstorleik</string>\n    <string name=\"delete_manga\">Slett mangaen</string>\n    <string name=\"text_delete_local_manga\">Slett «%s» ifrå eininga?</string>\n    <string name=\"reader_settings\">Lesing</string>\n    <string name=\"switch_pages\">Bla med</string>\n    <string name=\"_continue\">Hald fram</string>\n    <string name=\"error\">Feil</string>\n    <string name=\"clear_thumbs_cache\">Tøm mellomminnet for småbilete</string>\n    <string name=\"clear_search_history\">Tøm søkehistorikken</string>\n    <string name=\"search_history_cleared\">Tømt</string>\n    <string name=\"notifications\">Varsel</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d av %2$d</string>\n    <string name=\"download\">Hent</string>\n    <string name=\"notifications_settings\">Varselinnstillingar</string>\n    <string name=\"notification_sound\">Varselljod</string>\n    <string name=\"light_indicator\">Varselljos</string>\n    <string name=\"vibration\">Dirring</string>\n    <string name=\"favourites_categories\">Hopar til leiting</string>\n    <string name=\"text_local_holder_secondary\">Hent ifrå nettkjelder eller før inn filer.</string>\n    <string name=\"manga_shelf\">Hylla</string>\n    <string name=\"recent_manga\">Nytt</string>\n    <string name=\"manga_save_location\">Legg henta mangaar i:</string>\n    <string name=\"done\">Ferdig</string>\n    <string name=\"all_favourites\">Alt du likar</string>\n    <string name=\"favourites_category_empty\">Tom hop</string>\n    <string name=\"read_later\">Les seinare</string>\n    <string name=\"updates\">Oppdateringar</string>\n    <string name=\"search_results\">Søkesvar</string>\n    <string name=\"new_version_s\">Ny utgåve: %s</string>\n    <string name=\"updates_feed_cleared\">Tømt</string>\n    <string name=\"rotate_screen\">Snu skjermen</string>\n    <string name=\"track_sources\">Leit etter oppdateringar</string>\n    <string name=\"dont_check\">Nei</string>\n    <string name=\"about\">Om</string>\n    <string name=\"check_for_updates\">Leit etter oppdateringar</string>\n    <string name=\"right_to_left\">Høgre-til-venstre</string>\n    <string name=\"scale_mode\">Lesevising</string>\n    <string name=\"zoom_mode_fit_height\">Høv til høgda</string>\n    <string name=\"zoom_mode_fit_width\">Høv til breidda</string>\n    <string name=\"black_dark_theme\">Svart</string>\n    <string name=\"backup_restore\">Tryggleikskopiering og gjenoppretting</string>\n    <string name=\"create_backup\">Lag ein tryggleikskopi</string>\n    <string name=\"data_restored\">Gjenoppretta</string>\n    <string name=\"file_not_found\">Fann ikkje fila</string>\n    <string name=\"data_restored_success\">Gjenoppretta all data</string>\n    <string name=\"data_restored_with_errors\">Gjenoppretta dataa, men med feil</string>\n    <string name=\"tap_to_try_again\">Trykk for å røyna att</string>\n    <string name=\"list_mode\">Listeslag</string>\n    <string name=\"local_storage\">Henta</string>\n    <string name=\"favourites\">Likar</string>\n    <string name=\"history\">Historikk</string>\n    <string name=\"network_error\">Nettverksfeil</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"loading_\">Hentar fram …</string>\n    <string name=\"close\">Steng</string>\n    <string name=\"try_again\">Røyn att</string>\n    <string name=\"nothing_found\">Fann ikkje noko</string>\n    <string name=\"share\">Del</string>\n    <string name=\"download_complete\">Henta</string>\n    <string name=\"by_name\">Namn</string>\n    <string name=\"updated\">Oppdatert</string>\n    <string name=\"theme\">Vising</string>\n    <string name=\"follow_system\">Lyd systemet</string>\n    <string name=\"dark\">Mørk</string>\n    <string name=\"text_local_holder_primary\">Hent noko først</string>\n    <string name=\"not_available\">Ikkje tilgjengeleg</string>\n    <string name=\"update\">Oppdater</string>\n    <string name=\"size_s\">Storleik: %s</string>\n    <string name=\"black_dark_theme_summary\">Brukar mindre straum på AMOLED-skjermar</string>\n    <string name=\"app_version\">Utgåve %s</string>\n    <string name=\"no_update_available\">Ingen tilgjengelege oppdateringar</string>\n    <string name=\"create_category\">Ny hop</string>\n    <string name=\"zoom_mode_fit_center\">Midtstill</string>\n    <string name=\"zoom_mode_keep_start\">Auk byrjinga av sida</string>\n    <string name=\"restore_backup\">Gjenopprett</string>\n    <string name=\"backup_information\">Du kan tryggleikskopiera historikken og likerlista di til seinare gjenoppretting</string>\n    <string name=\"preparing_\">Førebur …</string>\n    <string name=\"system_default\">Forval</string>\n    <string name=\"just_now\">No</string>\n    <string name=\"today\">I dag</string>\n    <string name=\"yesterday\">I går</string>\n    <string name=\"long_ago\">Lenge sida</string>\n    <string name=\"read_mode\">Lesing</string>\n    <string name=\"app_update_available\">Ei ny utgåve av appen er tilgjengeleg</string>\n    <string name=\"open_in_browser\">Opne i ein nettlesar</string>\n    <string name=\"text_history_holder_primary\">Det du les vert vist her</string>\n    <string name=\"enter_password\">Rit inn lykelordet</string>\n    <string name=\"repeat_password\">Gjentak lykelordet</string>\n    <string name=\"passwords_mismatch\">Lykelorda er ulike</string>\n    <string name=\"reader_mode_hint\">Brigde av oppsettet vedkjem berre denne mangaen</string>\n    <string name=\"silent\">Stille</string>\n    <string name=\"captcha_required\">Krev CAPTCHA</string>\n    <string name=\"captcha_solve\">Løys</string>\n    <string name=\"clear_cookies\">Slett infokapslane</string>\n    <string name=\"cookies_cleared\">Sletta infokapslane</string>\n    <string name=\"text_clear_updates_feed_prompt\">Tøm oppdateringshistorikken\\?</string>\n    <string name=\"sign_in\">Logg inn</string>\n    <string name=\"next\">Neste</string>\n    <string name=\"confirm\">Stadfest</string>\n    <string name=\"password_length_hint\">Lykelordet må vera lengre enn fire teikn</string>\n    <string name=\"backup_saved\">Tryggleikskopi laga</string>\n    <string name=\"read_more\">Les meir</string>\n    <string name=\"queued\">I kø</string>\n    <string name=\"about_app_translation_summary\">Omset denne appen</string>\n    <string name=\"about_app_translation\">Omsetjing</string>\n    <string name=\"auth_complete\">Godkjend</string>\n    <string name=\"genres\">Slag</string>\n    <string name=\"state_finished\">Fullgjort</string>\n    <string name=\"state_ongoing\">I gang</string>\n    <string name=\"exclude_nsfw_from_history\">Utelèt mangaar med vakse innhald ifrå historikken</string>\n    <string name=\"show_pages_numbers\">Sidetal</string>\n    <string name=\"screenshots_policy\">Skjermbilete</string>\n    <string name=\"screenshots_block_all\">Hindre alltid</string>\n    <string name=\"suggestions\">Råd</string>\n    <string name=\"suggestions_enable\">Slå på råd</string>\n    <string name=\"suggestions_summary\">Rå om mangaar ut ifrå det du har lese</string>\n    <string name=\"text_suggestion_holder\">Byrja å lesa nokre mangaar for å få personlege råd</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Ikkje rå mangaar med vakse innhald</string>\n    <string name=\"enabled\">Påslegen</string>\n    <string name=\"various_languages\">Fleire språk</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"suggestions_updating\">Oppdaterer råd</string>\n    <string name=\"suggestions_excluded_genres\">Utelat slag</string>\n    <string name=\"removal_completed\">Sletta</string>\n    <string name=\"download_slowdown\">Avgrens hentesnøggleiken</string>\n    <string name=\"download_slowdown_summary\">Lægjer vona for at IP-adressa di vert blokkert</string>\n    <string name=\"local_manga_processing\">Handsamar henta manga</string>\n    <string name=\"canceled\">Avbroten</string>\n    <string name=\"account_already_exists\">Kontoen finst alt</string>\n    <string name=\"back\">Attende</string>\n    <string name=\"sync_title\">Synkroniser dataet ditt</string>\n    <string name=\"email_enter_hint\">Rit inn e-postadressa di for å halda fram</string>\n    <string name=\"hide\">Skjul</string>\n    <string name=\"name\">Namn</string>\n    <string name=\"edit\">Brigd</string>\n    <string name=\"edit_category\">Brigd hopen</string>\n    <string name=\"tracking\">Sporing</string>\n    <string name=\"empty_favourite_categories\">Ingen likte hopar</string>\n    <string name=\"logout\">Logg ut</string>\n    <string name=\"bookmark_add\">Bokmerk</string>\n    <string name=\"bookmark_remove\">Ta bort bokmerket</string>\n    <string name=\"bookmarks\">Bokmerke</string>\n    <string name=\"removed_from_history\">Teken bort ifrå historikken</string>\n    <string name=\"dns_over_https\">DNS over HTTPS</string>\n    <string name=\"default_mode\">Forvald lesing</string>\n    <string name=\"detect_reader_mode\">Finn sjølvverkande ut av lesing</string>\n    <string name=\"detect_reader_mode_summary\">Finn sjølvverkande ut av om mangaen er ein nettserie</string>\n    <string name=\"disable_battery_optimization\">Slå av batterilenging</string>\n    <string name=\"send\">Send</string>\n    <string name=\"status_reading\">Les</string>\n    <string name=\"status_planned\">Skal lesa</string>\n    <string name=\"status_re_reading\">Les att</string>\n    <string name=\"status_completed\">Lesen</string>\n    <string name=\"status_on_hold\">På vent</string>\n    <string name=\"status_dropped\">Gjeven opp</string>\n    <string name=\"disable_all\">Slå av alle</string>\n    <string name=\"appwidget_recent_description\">Mangaar du har nyleg lese</string>\n    <string name=\"data_deletion\">Sletting av data</string>\n    <string name=\"show_reading_indicators_summary\">Vis % lesen i historikken og likerlista</string>\n    <string name=\"clear_cookies_summary\">Kan løysa nokre feil. Alle godkjenningar vert ugilde</string>\n    <string name=\"show_all\">Vis alle</string>\n    <string name=\"invalid_domain_message\">Ugildt domene</string>\n    <string name=\"select_range\">Vel område</string>\n    <string name=\"clear_all_history\">Tøm heile historikken</string>\n    <string name=\"history_cleared\">Tømte historikken</string>\n    <string name=\"no_bookmarks_yet\">Ingen bokmerke endå</string>\n    <string name=\"no_bookmarks_summary\">Du kan lage bokmerke medan du les</string>\n    <string name=\"bookmarks_removed\">Tok bort bokmerka</string>\n    <string name=\"no_manga_sources\">Ingen mangakjelder</string>\n    <string name=\"no_manga_sources_text\">Slå på minst ei mangakjelde for å lesa mangaar på nett</string>\n    <string name=\"random\">Tilfeldig</string>\n    <string name=\"reorder\">Flytt</string>\n    <string name=\"empty\">Tom</string>\n    <string name=\"confirm_exit\">Trykk Attende att for å gå or appen</string>\n    <string name=\"exit_confirmation\">Stadfest apputgåing</string>\n    <string name=\"pages_cache\">Mellominnet for sider</string>\n    <string name=\"other_cache\">Mellomminnet for anna</string>\n    <string name=\"available\">Tilgjengeleg</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"removed_from_favourites\">Teken or likerlista</string>\n    <string name=\"not_found_404\">Fann ikkje innhaldet</string>\n    <string name=\"automatic_scroll\">Bla sjølvverkande</string>\n    <string name=\"comics_archive\">Teikneseriearkiv</string>\n    <string name=\"folder_with_images\">Mappe med bilete</string>\n    <string name=\"importing_manga\">Fører inn manga</string>\n    <string name=\"color_correction\">Brigd letar</string>\n    <string name=\"brightness\">Ljosstyrk</string>\n    <string name=\"reset\">Still attende</string>\n    <string name=\"text_unsaved_changes_prompt\">Spar eller avvis dei usparte brigda\\?</string>\n    <string name=\"discard\">Avvis</string>\n    <string name=\"error_no_space_left\">Eininga er fylt</string>\n    <string name=\"reader_slider\">Vis ei rulleline til blading</string>\n    <string name=\"webtoon_zoom\">Auk/mink nettseriar</string>\n    <string name=\"network_unavailable\">Nettverk ikkje tilgjengeleg</string>\n    <string name=\"network_unavailable_hint\">Slå på Wi-Fi eller mobilnettverk for å lesa mangaar på nett</string>\n    <string name=\"server_error\">Tjenarfeil (%1$d). Røyn att seinare</string>\n    <string name=\"source_disabled\">Kjelde avslegen</string>\n    <string name=\"prefetch_content\">Hent inn innhald på forhand</string>\n    <string name=\"mark_as_current\">Merk som aktuelt</string>\n    <string name=\"language\">Språk</string>\n    <string name=\"share_logs\">Del loggføringar</string>\n    <string name=\"enable_logging\">Loggfør</string>\n    <string name=\"enable_logging_summary\">Ta opp nokre gjerder til bruk i istandsetjing</string>\n    <string name=\"theme_name_dynamic\">Skiftande</string>\n    <string name=\"color_theme\">Letar</string>\n    <string name=\"show_in_grid_view\">Vis som rutenett</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"nothing_here\">Ingenting her</string>\n    <string name=\"services\">Tenester</string>\n    <string name=\"download_started\">Har byrja å hente</string>\n    <string name=\"text_clear_search_history_prompt\">Tøm søkehistorikken\\?</string>\n    <string name=\"welcome\">Velkomen</string>\n    <string name=\"text_clear_cookies_prompt\">Du vert logga ut ifrå alle kjeldane</string>\n    <string name=\"screenshots_block_nsfw\">Hindre ved vakse innhald</string>\n    <string name=\"remove_category\">Slett</string>\n    <string name=\"text_empty_holder_primary\">Her var det tomt …</string>\n    <string name=\"protect_application_summary\">Spør om lykelordet ved byrjing av appen</string>\n    <string name=\"wrong_password\">Feil lykelord</string>\n    <string name=\"protect_application\">Vern appen</string>\n    <string name=\"auth_required\">Logg inn for å sjå dette innhaldet</string>\n    <string name=\"protect_application_subtitle\">Vern appen med eit lykelord</string>\n    <string name=\"suggestions_info\">All data vert handsama på eininga di og vert ikkje førte over til noka teneste</string>\n    <string name=\"disabled\">Avslegen</string>\n    <string name=\"onboard_text\">Vel språka du vil lesa mangaar på. Du kan endre på dette seinare i innstillingane.</string>\n    <string name=\"never\">Aldri</string>\n    <string name=\"only_using_wifi\">Berre på Wi-Fi</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"always\">Alltid</string>\n    <string name=\"preload_pages\">Hent sider på forhand</string>\n    <string name=\"appearance\">Utsjånaden</string>\n    <string name=\"text_delete_local_manga_batch\">Slett valde ting ifrå eininga di\\?</string>\n    <string name=\"logged_in_as\">Logga inn som %s</string>\n    <string name=\"suggestions_excluded_genres_summary\">Opplys om kva slags slag du ikkje vil få råd om</string>\n    <string name=\"sync\">Synkronisering</string>\n    <string name=\"new_sources_text\">Nye mangakjelder tilgjengelege</string>\n    <string name=\"show_notification_new_chapters_on\">Du kjem til å få varsel når mangaar du les vert oppdaterte</string>\n    <string name=\"notifications_enable\">Slå på varsel</string>\n    <string name=\"bookmark_added\">La til eit bokmerke</string>\n    <string name=\"bookmark_removed\">Tok bort bokmerket</string>\n    <string name=\"undo\">Angre</string>\n    <string name=\"use_fingerprint\">Nytt fingermerke om tilgjengeleg</string>\n    <string name=\"show_reading_indicators\">Vis leseframgang</string>\n    <string name=\"appwidget_shelf_description\">Mangaar du likar</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Mangaar merkte med vakse innhald vert ikkje lagde til i historikken din, og framgangen din vert ikkje spart</string>\n    <string name=\"categories_delete_confirm\">Slett dei valde hopane? \\nDu kan ikkje angre. Alle mangaane inni vert tekne ut av likerlista.</string>\n    <string name=\"options\">Oppsett</string>\n    <string name=\"last_2_hours\">Dei siste to timane</string>\n    <string name=\"manage\">Handsam</string>\n    <string name=\"exit_confirmation_summary\">Trykk Attende to gongar for å gå or appen</string>\n    <string name=\"saved_manga\">Henta mangaar</string>\n    <string name=\"incognito_mode\">Privat modus</string>\n    <string name=\"import_completed\">Førte inn</string>\n    <string name=\"import_completed_hint\">Slett den opphavlege fila for å frigjera rom</string>\n    <string name=\"import_will_start_soon\">Byrjar å føra inn snart</string>\n    <string name=\"contrast\">Motsetjing</string>\n    <string name=\"show_suspicious_content\">Vis mistenksamt innhald</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"settings_apply_restart_required\">Byrja om appen for å nytta desse brigda</string>\n    <string name=\"chapters\">Kapittel</string>\n    <string name=\"computing_\">Utreknar…</string>\n    <string name=\"chapter_d_of_d\">Kapittel %1$d av %2$d</string>\n    <string name=\"operation_not_supported\">Ustødd gjerd</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"domain\">Domene</string>\n    <string name=\"new_chapters\">Nye kapittel</string>\n    <string name=\"pages_animation\">Siderørsle</string>\n    <string name=\"text_feed_holder\">Nye kapittel av det du les vert viste her</string>\n    <string name=\"check_for_new_chapters\">Sjå etter nye kapittel</string>\n    <string name=\"reverse\">Siste øvst</string>\n    <string name=\"tracker_warning\">Nokre einingar har systemåtferd som kan knuse bakgrunnsføreloger.</string>\n    <string name=\"chapter_is_missing\">Saknar kapittelet</string>\n    <string name=\"search_chapters\">Finn kapittel</string>\n    <string name=\"chapters_empty\">Ingen kapittel i denne mangaen</string>\n    <string name=\"chapters_will_removed_background\">Kapittel vert teken bort i bakgrunnen</string>\n    <string name=\"check_new_chapters_title\">Sjå etter og varsle om nye kapittel</string>\n    <string name=\"show_notification_new_chapters_off\">Nye kapittel vert merkte i listene utan varsel</string>\n    <string name=\"explore\">Gransk</string>\n    <string name=\"no_chapters\">Ingen kapittel</string>\n    <string name=\"reader_info_pattern\">Ka. %1$d/%2$d Side %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Vis opplysingsområde i lesevisinga</string>\n    <string name=\"reader_control_ltr_summary\">Trykk på høgre side eller ljodstyrke-ned-knappen blar alltid til neste side</string>\n    <string name=\"compact\">Samantrengt</string>\n    <string name=\"show_on_shelf\">Vis i hylla</string>\n    <string name=\"folder_with_images_import_description\">Du kan velja ei mappe med arkiv eller bilete. Kvart arkiv (eller undermappe) vert tydde som eit kapittel.</string>\n    <string name=\"speed\">Snøggleik</string>\n    <string name=\"comics_archive_import_description\">Vel ei eller fleire .cbz eller .zip-filer, kvar fil vert tydde som ein eigen manga.</string>\n    <string name=\"clear_new_chapters_counters\">Tøm au opplysingar om nye kapittel</string>\n    <string name=\"scrobbling_empty_hint\">Trykk «Meny → Spor» på ei mangadetaljside for å spore leseframdrifta.</string>\n    <string name=\"got_it\">Skjønar</string>\n    <string name=\"sources_reorder_tip\">Trykk og hald på eit element for å flytta på det</string>\n    <string name=\"_s_deleted_from_local_storage\">Sletta «%s» ifrå eininga</string>\n    <string name=\"internal_storage\">Indre gøyme</string>\n    <string name=\"external_storage\">Ytre gøyme</string>\n    <string name=\"cannot_find_available_storage\">Ingen tilgjengelege gøyme</string>\n    <string name=\"other_storage\">Anna gøyme</string>\n    <string name=\"text_search_holder_secondary\">Røyn å skriva det om.</string>\n    <string name=\"you_have_not_favourites_yet\">Ingen likte enno</string>\n    <string name=\"popular\">Mest lest for tida</string>\n    <string name=\"search_on_s\">Søk på %s</string>\n    <string name=\"text_history_holder_secondary\">Finn lesnadar i sidemenyen.</string>\n    <string name=\"screenshots_allow\">Tillat</string>\n    <string name=\"allow_unstable_updates\">Tillat ustøe oppdateringar</string>\n    <string name=\"downloads_wifi_only\">Hent berre på WiFi</string>\n    <string name=\"downloads_wifi_only_summary\">Stans all henting når du byter til eit mobilnettverk</string>\n    <string name=\"find_similar\">Finn liknande</string>\n    <string name=\"sort_order\">Skiljingsrekkjefølgd</string>\n    <string name=\"auth_not_supported_by\">Innlogging på %s er ikkje stødd</string>\n    <string name=\"sync_host_description\">Du kan nytte ein sjølvhusa synkroniseringstenar, eller den vanlege. Om du er uviss, ikkje rør.</string>\n    <string name=\"cancel_all_downloads_confirm\">Alle pågåande hentingar vert avbrotne, og uheile data sletta</string>\n    <string name=\"network\">Nettverk</string>\n    <string name=\"reader_info_bar_summary\">Vis klokka og leseframgangen på toppen av skjermen</string>\n    <string name=\"default_s\">Forvalt: %s</string>\n    <string name=\"reset_filter\">Attendestill silen</string>\n    <string name=\"report\">Sei ifrå</string>\n    <string name=\"storage_usage\">Gøymebruk</string>\n    <string name=\"history_shortcuts\">Vis snarvegen for nylege mangaar</string>\n    <string name=\"sync_settings\">Synkroniseringsinnstillingar</string>\n    <string name=\"server_address\">Tenaradresse</string>\n    <string name=\"resume\">Hald fram</string>\n    <string name=\"remove_completed\">Ta bort fullgjorde</string>\n    <string name=\"cancel_all\">Avbryt alle</string>\n    <string name=\"type\">Slag</string>\n    <string name=\"address\">Adresse</string>\n    <string name=\"downloaded\">Henta</string>\n    <string name=\"authorization_optional\">Godkjenning (valfri)</string>\n    <string name=\"invert_colors\">Snu på letane</string>\n    <string name=\"username\">Brukarnamn</string>\n    <string name=\"password\">Passord</string>\n    <string name=\"data_and_privacy\">Data og personvern</string>\n    <string name=\"restore_summary\">Gjenopprett ifrå ein tryggleiskopi</string>\n    <string name=\"show_pages_numbers_summary\">Vis sidetal i nedre hjørne</string>\n    <string name=\"no_thanks\">Nei takk</string>\n    <string name=\"enable\">Slå på</string>\n    <string name=\"clear_network_cache\">Tøm nettverksmellomminnet</string>\n    <string name=\"suggestion_manga\">Råd: %s</string>\n    <string name=\"suggestions_notifications_summary\">Ein gong iblant, vis varsel med rådde mangaar</string>\n    <string name=\"more\">Meir</string>\n    <string name=\"remove_completed_downloads_confirm\">Hentehistorikken din vil verta sletta for godt</string>\n    <string name=\"text_downloads_list_holder\">Du har ikkje henta noko</string>\n    <string name=\"downloads_resumed\">Heldt fram hentingane</string>\n    <string name=\"downloads_removed\">Tok bort hentingane</string>\n    <string name=\"downloads_cancelled\">Avbraut hentingane</string>\n    <string name=\"suggestions_enable_prompt\">Vil du få personlege mangaråd\\?</string>\n    <string name=\"downloads_paused\">Stansa hentingane</string>\n    <string name=\"sync_auth_hint\">Du kan logge inn på ein konto du alt har, eller lage ein ny ein</string>\n    <string name=\"invalid_value_message\">Ugild verdi</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-or/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<plurals name=\"minutes_ago\">\n\t\t<item quantity=\"one\">%1$d ମିନିଟ୍ ପୂର୍ଵେ</item>\n\t\t<item quantity=\"other\">%1$d ମିନିଟ୍ ପୂର୍ଵେ</item>\n\t</plurals>\n\t<plurals name=\"days_ago\">\n\t\t<item quantity=\"one\">%1$d ଦିନ ପୂର୍ଵେ</item>\n\t\t<item quantity=\"other\">%1$d ଦିନ ପୂର୍ଵେ</item>\n\t</plurals>\n\t<plurals name=\"new_chapters\">\n\t\t<item quantity=\"one\">%1$dଟିଏ ନୂଆ ଅଧ୍ୟାୟ</item>\n\t\t<item quantity=\"other\">%1$dଟି ନୂଆ ଅଧ୍ୟାୟ</item>\n\t</plurals>\n\t<plurals name=\"hours_ago\">\n\t\t<item quantity=\"one\">%1$d ଘଣ୍ଟା ପୂର୍ଵେ</item>\n\t\t<item quantity=\"other\">%1$d ଘଣ୍ଟା ପୂର୍ଵେ</item>\n\t</plurals>\n\t<plurals name=\"chapters\">\n\t\t<item quantity=\"one\">%1$dଟିଏ ଅଧ୍ୟାୟ</item>\n\t\t<item quantity=\"other\">%1$dଟି ଅଧ୍ୟାୟ</item>\n\t</plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-or/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"history\">ଇତିଵୃତ୍ତି</string>\n\t<string name=\"details\">ଵିଵରଣୀ</string>\n\t<string name=\"settings\">ସେଟିଂ</string>\n\t<string name=\"suggestion_manga\">ପରାମର୍ଶ: %s</string>\n\t<string name=\"done\">ହେଲା</string>\n\t<string name=\"services\">ସେଵା</string>\n\t<string name=\"got_it\">ବୁଝିଗଲି</string>\n\t<string name=\"black_dark_theme\">କଳା</string>\n\t<string name=\"theme\">ଥିମ୍</string>\n\t<string name=\"speed\">ଵେଗ</string>\n\t<string name=\"download_started\">ଡାଉନଲୋଡ୍ ଆରମ୍ଭ ହେଲା</string>\n\t<string name=\"reader_settings\">ପାଠକ ସେଟିଂ</string>\n\t<string name=\"notifications\">ଵିଜ୍ଞପ୍ତି</string>\n\t<string name=\"manga_shelf\">ଥାକ</string>\n\t<string name=\"always\">ସର୍ଵଦା</string>\n\t<string name=\"notifications_settings\">ଵିଜ୍ଞପ୍ତି ସେଟିଂ</string>\n\t<string name=\"appearance\">ରୂପ</string>\n\t<string name=\"dont_check\">ଯାଞ୍ଚ କରନି</string>\n\t<string name=\"check_for_updates\">ଅଦ୍ୟତନ ପାଇଁ ଯାଞ୍ଚ କରିବା</string>\n\t<string name=\"yesterday\">ଗତକାଲି</string>\n\t<string name=\"today\">ଆଜି</string>\n\t<string name=\"check_new_chapters_title\">ନୂଆ ଅଧ୍ୟାୟ ପାଇଁ ଯାଞ୍ଚ କରି ଏହା ଵିଷୟରେ ସୂଚିତ କରିବା</string>\n\t<string name=\"check_for_new_chapters\">ନୂଆ ଅଧ୍ୟାୟ ପାଇଁ ଯାଞ୍ଚ କରିବା</string>\n\t<string name=\"file_not_found\">ଫାଇଲ ମିଳୁନାହିଁ</string>\n\t<string name=\"backup_restore\">ବ୍ୟାକଅପ୍ ଓ ପୁନରୁଦ୍ଧାର</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pa/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"favourites\">ਮਨਪਸੰਦ</string>\n    <string name=\"history\">ਇਤਿਹਾਸ</string>\n    <string name=\"error_occurred\">ਇੱਕ ਗਲਤੀ ਆਈ ਹੈ</string>\n    <string name=\"details\">ਵੇਰਵਾ</string>\n    <string name=\"chapters\">ਅਧਿਆਇ</string>\n    <string name=\"grid\">ਗਰਿੱਡ</string>\n    <string name=\"list_mode\">ਲਿਸਟ ਮੋਡ</string>\n    <string name=\"settings\">ਸੈਟਿੰਗ</string>\n    <string name=\"remote_sources\">ਮਾਂਗਾ ਸਰੋਤ</string>\n    <string name=\"loading_\">ਲੋਡ…</string>\n    <string name=\"computing_\">ਹਿਸਾਬ ਲਾ ਰਿਹਾ</string>\n    <string name=\"chapter_d_of_d\">%2$d ਦਾ %1$d ਅਧਿਆਇ</string>\n    <string name=\"close\">ਬੰਦ</string>\n    <string name=\"try_again\">ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ</string>\n    <string name=\"local_storage\">ਸਥਾਨਕ ਸਟੋਰੇਜ</string>\n    <string name=\"network_error\">ਨੈੱਟਵਰਕ ਖਰਾਬ</string>\n    <string name=\"list\">ਲਿਸਟ</string>\n    <string name=\"detailed_list\">ਵੇਰਵਾ ਲਿਸਟ</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pa-rPK/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-pl/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nowy rozdział</item>\n        <item quantity=\"few\">%1$d nowe rozdziały</item>\n        <item quantity=\"many\">%1$d nowych rozdziałów</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minutę temu</item>\n        <item quantity=\"few\">%1$d minuty temu</item>\n        <item quantity=\"many\">%1$d minut temu</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d godzinę temu</item>\n        <item quantity=\"few\">%1$d godziny temu</item>\n        <item quantity=\"many\">%1$d godzin temu</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d dzień temu</item>\n        <item quantity=\"few\">%1$d dni temu</item>\n        <item quantity=\"many\">%1$d dni temu</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d przedmiot</item>\n        <item quantity=\"few\">%1$d przedmioty</item>\n        <item quantity=\"many\">%1$d przedmiotów</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d rozdział</item>\n        <item quantity=\"few\">%1$d rozdziały</item>\n        <item quantity=\"many\">%1$d rozdziałów</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d miesiąc temu</item>\n        <item quantity=\"few\">%1$d miesiące temu</item>\n        <item quantity=\"many\">%1$d miesięcy temu</item>\n        <item quantity=\"other\">%1$d miesięcy temu</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d godzinę</item>\n        <item quantity=\"few\">%1$d godzin</item>\n        <item quantity=\"many\">%1$d godzin</item>\n        <item quantity=\"other\">%1$d godzin</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minutę</item>\n        <item quantity=\"few\">%1$d minuty</item>\n        <item quantity=\"many\">%1$d minut</item>\n        <item quantity=\"other\">%1$d minut</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"favourites\">Ulubione</string>\n    <string name=\"history\">Historia</string>\n    <string name=\"error_occurred\">Napotkano błąd</string>\n    <string name=\"details\">Szczegóły</string>\n    <string name=\"chapters\">Rozdziały</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"detailed_list\">Lista szczegółowa</string>\n    <string name=\"grid\">Siatka</string>\n    <string name=\"list_mode\">Tryb listy</string>\n    <string name=\"settings\">Ustawienia</string>\n    <string name=\"loading_\">Ładowanie…</string>\n    <string name=\"chapter_d_of_d\">Rozdział %1$d z %2$d</string>\n    <string name=\"close\">Zamknij</string>\n    <string name=\"clear_history\">Wyczyść historię</string>\n    <string name=\"add\">Dodaj</string>\n    <string name=\"save\">Zapisz</string>\n    <string name=\"share\">Udostępnij</string>\n    <string name=\"search\">Szukaj</string>\n    <string name=\"search_manga\">Szukaj mang</string>\n    <string name=\"manga_downloading_\">Pobieranie…</string>\n    <string name=\"download_complete\">Pobrano</string>\n    <string name=\"downloads\">Pobrania</string>\n    <string name=\"by_name\">Nazwa</string>\n    <string name=\"popular\">Popularność</string>\n    <string name=\"newest\">Najnowsze</string>\n    <string name=\"by_rating\">Ocena</string>\n    <string name=\"filter\">Filtry</string>\n    <string name=\"light\">Jasny</string>\n    <string name=\"dark\">Ciemny</string>\n    <string name=\"pages\">Strony</string>\n    <string name=\"clear\">Wyczyść</string>\n    <string name=\"remove\">Usuń</string>\n    <string name=\"share_image\">Udostępnij zdjęcie</string>\n    <string name=\"delete\">Usuń</string>\n    <string name=\"no_description\">Brak opisu</string>\n    <string name=\"read_mode\">Tryb czytania</string>\n    <string name=\"network_error\">Błąd sieci</string>\n    <string name=\"computing_\">Obliczanie…</string>\n    <string name=\"try_again\">Spróbuj ponownie</string>\n    <string name=\"nothing_found\">Nic nie znaleziono</string>\n    <string name=\"history_is_empty\">Brak historii</string>\n    <string name=\"read\">Czytaj</string>\n    <string name=\"you_have_not_favourites_yet\">Brak ulubionych</string>\n    <string name=\"add_to_favourites\">Dodaj do ulubionych</string>\n    <string name=\"add_new_category\">Nowa kategoria</string>\n    <string name=\"create_shortcut\">Stwórz skrót</string>\n    <string name=\"share_s\">Udostępnij %s</string>\n    <string name=\"processing_\">Przetwarzanie…</string>\n    <string name=\"updated\">Zaktualizowane</string>\n    <string name=\"save_page\">Zapisz stronę</string>\n    <string name=\"page_saved\">Zapisano stronę</string>\n    <string name=\"vibration\">Wibracje</string>\n    <string name=\"manga_shelf\">Biblioteka</string>\n    <string name=\"recent_manga\">Ostatnie</string>\n    <string name=\"black_dark_theme\">Tryb czarny</string>\n    <string name=\"preparing_\">Przygotowywanie…</string>\n    <string name=\"file_not_found\">Plik nieznaleziony</string>\n    <string name=\"yesterday\">Wczoraj</string>\n    <string name=\"long_ago\">Dawno temu</string>\n    <string name=\"group\">Grupa</string>\n    <string name=\"today\">Dzisiaj</string>\n    <string name=\"sign_in\">Zaloguj</string>\n    <string name=\"next\">Dalej</string>\n    <string name=\"confirm\">Potwierdź</string>\n    <string name=\"welcome\">Witaj</string>\n    <string name=\"state_finished\">Skończone</string>\n    <string name=\"state_ongoing\">W trakcie</string>\n    <string name=\"screenshots_allow\">Zezwól</string>\n    <string name=\"suggestions\">Proponowane</string>\n    <string name=\"suggestions_enable\">Włącz propozycje</string>\n    <string name=\"enabled\">Włączone</string>\n    <string name=\"disabled\">Wyłączone</string>\n    <string name=\"never\">Nigdy</string>\n    <string name=\"always\">Zawsze</string>\n    <string name=\"search_chapters\">Znajdź rozdział</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"appearance\">Wygląd</string>\n    <string name=\"hide\">Schowaj</string>\n    <string name=\"sync\">Synchronizacja</string>\n    <string name=\"sync_title\">Synchronizuj swoje dane</string>\n    <string name=\"name\">Nazwa</string>\n    <string name=\"edit\">Edytuj</string>\n    <string name=\"logout\">Wyloguj</string>\n    <string name=\"undo\">Cofnij</string>\n    <string name=\"send\">Wyślij</string>\n    <string name=\"status_planned\">Planowane</string>\n    <string name=\"status_reading\">Czytane</string>\n    <string name=\"status_re_reading\">Czytane ponownie</string>\n    <string name=\"status_completed\">Skończone</string>\n    <string name=\"show_all\">Pokaż wszystkie</string>\n    <string name=\"select_range\">Wybierz zakres</string>\n    <string name=\"clear_all_history\">Wyczyść całą historię</string>\n    <string name=\"last_2_hours\">Ostatnie 2 godziny</string>\n    <string name=\"history_cleared\">Historia wyczyszczona</string>\n    <string name=\"manage\">Zarządzaj</string>\n    <string name=\"random\">Losowe</string>\n    <string name=\"empty\">Puste</string>\n    <string name=\"explore\">Przeglądaj</string>\n    <string name=\"available\">Dostępne</string>\n    <string name=\"options\">Ustawienia</string>\n    <string name=\"source_disabled\">Źródło wyłączone</string>\n    <string name=\"compact\">Kompaktowy</string>\n    <string name=\"server_error\">Błąd po stronie serwera (%1$d). Sprónuj ponownie później</string>\n    <string name=\"network_unavailable\">Sieć niedostępna</string>\n    <string name=\"discard\">Odrzuć</string>\n    <string name=\"brightness\">Jasność</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"color_correction\">Korekcja kolorów</string>\n    <string name=\"automatic_scroll\">Automatyczne przewijanie</string>\n    <string name=\"no_chapters\">Brak rozdziałów</string>\n    <string name=\"incognito_mode\">Tryb incognito</string>\n    <string name=\"removed_from_favourites\">Usunięto z ulubionych</string>\n    <string name=\"storage_usage\">Wykorzystana pamięć</string>\n    <string name=\"saved_manga\">Zapisane mangi</string>\n    <string name=\"no_bookmarks_yet\">Brak zakładek</string>\n    <string name=\"no_bookmarks_summary\">Możesz tworzyć zakładki w trakcie czytania mangi</string>\n    <string name=\"bookmarks_removed\">Zakładki usunięte</string>\n    <string name=\"appwidget_recent_description\">Twoje ostatnio czytane mangi</string>\n    <string name=\"disable_all\">Wyłącz wszystkie</string>\n    <string name=\"disable_battery_optimization\">Wyłącz optymalizację baterii</string>\n    <string name=\"detect_reader_mode\">Autowykrywanie trybu czytania</string>\n    <string name=\"removed_from_history\">Usunięte z historii</string>\n    <string name=\"bookmark_added\">Dodano zakładkę</string>\n    <string name=\"bookmark_removed\">Usunięto zakładkę</string>\n    <string name=\"bookmarks\">Zakładki</string>\n    <string name=\"bookmark_remove\">Usuń zakładkę</string>\n    <string name=\"bookmark_add\">Dodaj zakładkę</string>\n    <string name=\"empty_favourite_categories\">Brak ulubionych kategorii</string>\n    <string name=\"edit_category\">Edytuj kategorię</string>\n    <string name=\"notifications_enable\">Włącz powiadomienia</string>\n    <string name=\"back\">Wróć</string>\n    <string name=\"account_already_exists\">Konto już istnieje</string>\n    <string name=\"canceled\">Anulowano</string>\n    <string name=\"download_slowdown\">Zwolnienie pobierania</string>\n    <string name=\"chapters_empty\">Brak rozdziałów w tej mandze</string>\n    <string name=\"various_languages\">Różne języki</string>\n    <string name=\"only_using_wifi\">Tylko na Wi-Fi</string>\n    <string name=\"screenshots_block_all\">Zawsze blokuj</string>\n    <string name=\"genres\">Gatunki</string>\n    <string name=\"read_more\">Czytaj więcej</string>\n    <string name=\"captcha_solve\">Rozwiąż</string>\n    <string name=\"captcha_required\">Wymagane CAPTCHA</string>\n    <string name=\"silent\">Cichy</string>\n    <string name=\"tap_to_try_again\">Dotknij aby spróbować ponownie</string>\n    <string name=\"just_now\">Teraz</string>\n    <string name=\"data_restored\">Przywrócone</string>\n    <string name=\"zoom_mode_fit_width\">Dopasuj do szerokości</string>\n    <string name=\"zoom_mode_fit_height\">Dopasuj do wysokości</string>\n    <string name=\"zoom_mode_fit_center\">Dopasuj do środka</string>\n    <string name=\"create_category\">Nowa kategoria</string>\n    <string name=\"no_update_available\">Brak nowych aktualizacji</string>\n    <string name=\"check_for_updates\">Sprawdź dostępność aktualizacji</string>\n    <string name=\"app_version\">Wersja %s</string>\n    <string name=\"about\">O aplikacji</string>\n    <string name=\"remove_category\">Usuń</string>\n    <string name=\"text_empty_holder_primary\">Jest tu dosyć pusto…</string>\n    <string name=\"favourites_categories\">Ulubione kategorie</string>\n    <string name=\"light_indicator\">Powiadomienie LED</string>\n    <string name=\"new_chapters\">Nowe rozdziały</string>\n    <string name=\"local_storage\">Lokalna pamięć</string>\n    <string name=\"text_feed_holder\">W tym miejscu pojawią się powiadomienia o nowych rozdziałach z mang które czytasz</string>\n    <string name=\"pages_cache\">Strony w pamięci podręcznej</string>\n    <string name=\"pages_animation\">Animacja przewracania strony</string>\n    <string name=\"other_cache\">Inne rzeczy w pamięci podręcznej</string>\n    <string name=\"open_in_browser\">Otwórz w przeglądarce</string>\n    <string name=\"show_pages_numbers\">Numerowane strony</string>\n    <string name=\"notifications\">Powiadomienia</string>\n    <string name=\"notification_sound\">Dźwięk powiadomień</string>\n    <string name=\"notifications_settings\">Ustawienia powiadomień</string>\n    <string name=\"remote_sources\">Zewnętrzne źródła</string>\n    <string name=\"theme\">Motyw</string>\n    <string name=\"follow_system\">Systemowy</string>\n    <string name=\"clear_pages_cache\">Wyczyść pamięć podręczną stron</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"grid_size\">Wielkość siatki</string>\n    <string name=\"search_on_s\">Szukaj na %s</string>\n    <string name=\"delete_manga\">Usuń mangę</string>\n    <string name=\"_continue\">Kontynuuj</string>\n    <string name=\"error\">Błąd</string>\n    <string name=\"search_history_cleared\">Wyczyszczone</string>\n    <string name=\"internal_storage\">Pamięć wewnętrzna</string>\n    <string name=\"external_storage\">Pamięć zewnętrzna</string>\n    <string name=\"domain\">Domena</string>\n    <string name=\"app_update_available\">Nowa wersja aplikacji jest dostępna</string>\n    <string name=\"download\">Pobierz</string>\n    <string name=\"text_local_holder_primary\">Najpierw coś zapisz</string>\n    <string name=\"not_available\">Niedostępne</string>\n    <string name=\"done\">Zapisz</string>\n    <string name=\"all_favourites\">Wszystkie ulubione</string>\n    <string name=\"favourites_category_empty\">Pusta kategoria</string>\n    <string name=\"read_later\">Czytaj później</string>\n    <string name=\"updates\">Aktualizacje</string>\n    <string name=\"new_version_s\">Nowa wersja: %s</string>\n    <string name=\"size_s\">Wielkość: %s</string>\n    <string name=\"rotate_screen\">Obróć ekran</string>\n    <string name=\"update\">Odśwież</string>\n    <string name=\"track_sources\">Szukaj aktualizacji</string>\n    <string name=\"dont_check\">Nie sprawdzaj</string>\n    <string name=\"enter_password\">Wprowadź hasło</string>\n    <string name=\"wrong_password\">Złe hasło</string>\n    <string name=\"protect_application\">Chroń aplikację</string>\n    <string name=\"protect_application_summary\">Pytaj o hasło przy starcie Kotatsu</string>\n    <string name=\"repeat_password\">Wprowadź ponownie hasło</string>\n    <string name=\"black_dark_theme_summary\">Zużywa mniej prądu na ekranach AMOLED</string>\n    <string name=\"backup_restore\">Kopia zapasowa i przywracanie</string>\n    <string name=\"create_backup\">Utwórz kopię zapasową danych</string>\n    <string name=\"restore_backup\">Przywróć z kopii zapasowej</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"enabled_d_of_d\">%1$d na %2$d włączone</string>\n    <string name=\"standard\">Standardowy</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"reader_settings\">Ustawienia czytnika</string>\n    <string name=\"switch_pages\">Zmiana strony</string>\n    <string name=\"updates_feed_cleared\">Wyczyszczone</string>\n    <string name=\"scale_mode\">Tryb skalowania</string>\n    <string name=\"clear_cookies\">Wyczyść ciasteczka</string>\n    <string name=\"cookies_cleared\">Wszystkie ciasteczka wyczyszczone</string>\n    <string name=\"about_app_translation_summary\">Przetłumacz tą aplikację</string>\n    <string name=\"about_app_translation\">Tłumaczenie</string>\n    <string name=\"cannot_find_available_storage\">Brak dostępnej pamięci</string>\n    <string name=\"other_storage\">Inny</string>\n    <string name=\"search_results\">Wyniki wyszukiwania</string>\n    <string name=\"data_restored_success\">Wszystkie dane zostały przywrócone</string>\n    <string name=\"data_restored_with_errors\">Dane zostały przywrócone, ale z błędami</string>\n    <string name=\"reverse\">Od tyłu</string>\n    <string name=\"system_default\">Domyślny</string>\n    <string name=\"screenshots_policy\">Polityka zrzutów ekranu</string>\n    <string name=\"suggestions_excluded_genres\">Wyklucz gatunki</string>\n    <string name=\"suggestions_excluded_genres_summary\">Określ gatunki, których nie chcesz widzieć w sugestiach</string>\n    <string name=\"logged_in_as\">Zalogowano jako %s</string>\n    <string name=\"onboard_text\">Wybierz języki, w których chcesz czytać mangi. Możesz zmienić to później w ustawieniach.</string>\n    <string name=\"report\">Zgłoś</string>\n    <string name=\"data_deletion\">Usuwanie danych</string>\n    <string name=\"invalid_domain_message\">Nieważna domena</string>\n    <string name=\"reorder\">Zmień kolejność</string>\n    <string name=\"exit_confirmation\">Potwierdzenie wyjścia</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"reader_info_pattern\">Rozdz. %1$d/%2$d Str. %3$d/%4$d</string>\n    <string name=\"network_unavailable_hint\">Włącz Wi-Fi lub sieć komórkową, aby czytać mangę online</string>\n    <string name=\"_import\">Importuj</string>\n    <string name=\"text_file_not_supported\">Wybierz plik ZIP lub CBZ.</string>\n    <string name=\"clear_search_history\">Wyczyść historię wyszukiwania</string>\n    <string name=\"operation_not_supported\">Ta operacja nie jest obsługiwana</string>\n    <string name=\"sort_order\">Tryb sortowania</string>\n    <string name=\"status_on_hold\">Wstrzymane</string>\n    <string name=\"status_dropped\">Porzucone</string>\n    <string name=\"use_fingerprint\">Użyj biometrii, jeśli jest dostępna</string>\n    <string name=\"appwidget_shelf_description\">Mangi z Twoich ulubionych</string>\n    <string name=\"show_reading_indicators\">Pokaż wskaźniki postępu czytania</string>\n    <string name=\"show_reading_indicators_summary\">Pokaż procent przeczytania w historii i ulubionych</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga oznaczona jako NSFW nigdy nie zostanie dodana do historii, a Twoje postępy nie zostaną zapisane</string>\n    <string name=\"dns_over_https\">DNS przez HTTPS</string>\n    <string name=\"default_mode\">Tryb domyślny</string>\n    <string name=\"_s_deleted_from_local_storage\">„%s” usunięte z pamięci lokalnej</string>\n    <string name=\"clear_updates_feed\">Wyczyść tablicę aktualizacji</string>\n    <string name=\"feed\">Tablica</string>\n    <string name=\"text_delete_local_manga\">Trwale usunąć „%s” z urządzenia?</string>\n    <string name=\"clear_thumbs_cache\">Wyczyść pamięć podręczną miniatur</string>\n    <string name=\"text_search_holder_secondary\">Spróbuj przeformułować zapytanie.</string>\n    <string name=\"text_history_holder_primary\">To co czytasz będzie wyświetlane tutaj</string>\n    <string name=\"text_history_holder_secondary\">Znajdź rzeczy do przeczytania w sekcji Przegląd</string>\n    <string name=\"text_local_holder_secondary\">Zapisz coś z katalogu online lub zaimportuj z pliku.</string>\n    <string name=\"manga_save_location\">Folder Pobrane</string>\n    <string name=\"feed_will_update_soon\">Aktualizacja tablicy rozpocznie się wkrótce</string>\n    <string name=\"passwords_mismatch\">Niezgodne hasła</string>\n    <string name=\"right_to_left\">Od prawej do lewej</string>\n    <string name=\"zoom_mode_keep_start\">Trzymaj na starcie</string>\n    <string name=\"backup_information\">Możesz utworzyć kopię zapasową swojej historii i ulubionych oraz przywrócić ją</string>\n    <string name=\"reader_mode_hint\">Wybrana konfiguracja zostanie zapamiętana dla tej mangi</string>\n    <string name=\"clear_feed\">Wyczyść tablicę</string>\n    <string name=\"text_clear_updates_feed_prompt\">Wyczyścić trwale całą historię aktualizacji?</string>\n    <string name=\"check_for_new_chapters\">Szukanie nowych rozdziałów</string>\n    <string name=\"auth_required\">Zaloguj się, aby wyświetlić tę zawartość</string>\n    <string name=\"default_s\">Domyślnie: %s</string>\n    <string name=\"protect_application_subtitle\">Wprowadź hasło, aby uruchomić aplikację</string>\n    <string name=\"password_length_hint\">Hasło musi mieć co najmniej 4 znaki</string>\n    <string name=\"text_clear_search_history_prompt\">Trwale usunąć wszystkie ostatnie zapytania wyszukiwania?</string>\n    <string name=\"backup_saved\">Zapisano kopię zapasową</string>\n    <string name=\"tracker_warning\">Systemy niektórych urządzeń inaczej się zachowują. Może to zakłócać wykonywanie zadań w tle.</string>\n    <string name=\"queued\">W kolejce</string>\n    <string name=\"chapter_is_missing\">Brak rozdziału</string>\n    <string name=\"auth_complete\">Uprawniony</string>\n    <string name=\"auth_not_supported_by\">Logowanie na %s nie jest obsługiwane</string>\n    <string name=\"text_clear_cookies_prompt\">Zostaniesz wylogowany ze wszystkich źródeł</string>\n    <string name=\"exclude_nsfw_from_history\">Wyklucz mangi NSFW z historii</string>\n    <string name=\"screenshots_block_nsfw\">Zablokuj na NSFW</string>\n    <string name=\"suggestions_summary\">Proponuj mangi na podstawie Twoich preferencji</string>\n    <string name=\"suggestions_info\">Wszystkie dane są analizowane tylko lokalnie na tym urządzeniu i nigdy nie są nigdzie wysyłane.</string>\n    <string name=\"text_suggestion_holder\">Zacznij czytać mangę, a otrzymasz spersonalizowane sugestie</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Nie proponuj mang NSFW</string>\n    <string name=\"reset_filter\">Zresetuj filtr</string>\n    <string name=\"preload_pages\">Ładuj wstępnie strony</string>\n    <string name=\"suggestions_updating\">Aktualizowanie sugestii</string>\n    <string name=\"text_delete_local_manga_batch\">Trwale usunąć wybrane elementy z urządzenia?</string>\n    <string name=\"removal_completed\">Usuwanie zakończone</string>\n    <string name=\"download_slowdown_summary\">Pomaga uniknąć blokowania Twojego adresu IP</string>\n    <string name=\"local_manga_processing\">Przetwarzanie zapisanej mangi</string>\n    <string name=\"chapters_will_removed_background\">Rozdziały zostaną usunięte w tle</string>\n    <string name=\"email_enter_hint\">Wpisz swój adres e-mail, aby kontynuować</string>\n    <string name=\"new_sources_text\">Dostępne są nowe źródła mang</string>\n    <string name=\"check_new_chapters_title\">Sprawdzaj dostępność nowych rozdziałów i informuj o nich</string>\n    <string name=\"show_notification_new_chapters_on\">Będziesz otrzymywać powiadomienia o aktualizacjach mang, które czytasz</string>\n    <string name=\"show_notification_new_chapters_off\">Nie będziesz otrzymywać powiadomień, ale nowe rozdziały będą podświetlane na listach</string>\n    <string name=\"tracking\">Śledzenie</string>\n    <string name=\"detect_reader_mode_summary\">Automatycznie wykryj, czy manga to webtoon</string>\n    <string name=\"disable_battery_optimization_summary\">Pomaga w sprawdzaniu aktualizacji w tle</string>\n    <string name=\"crash_text\">Coś poszło nie tak. Zgłoś błąd programistom, aby pomóc nam go naprawić.</string>\n    <string name=\"clear_cookies_summary\">Może pomóc w przypadku niektórych problemów. Wszystkie autoryzacje zostaną unieważnione</string>\n    <string name=\"no_manga_sources\">Brak źródeł mang</string>\n    <string name=\"no_manga_sources_text\">Włącz źródła mang do czytania mang online</string>\n    <string name=\"categories_delete_confirm\">Czy na pewno chcesz usunąć wybrane ulubione kategorie? \\nWszystkie w nich mangi zostaną usunięte i nie będzie można tego cofnąć.</string>\n    <string name=\"confirm_exit\">Naciśnij ponownie Wstecz, aby wyjść</string>\n    <string name=\"exit_confirmation_summary\">Naciśnij dwukrotnie przycisk Wstecz, aby wyjść z aplikacji</string>\n    <string name=\"not_found_404\">Treść nie została znaleziona lub została usunięta</string>\n    <string name=\"reader_info_bar\">Pokaż pasek informacji w czytniku</string>\n    <string name=\"comics_archive\">Archiwum komiksów</string>\n    <string name=\"folder_with_images\">Folder z obrazami</string>\n    <string name=\"importing_manga\">Importowanie mangi</string>\n    <string name=\"import_completed\">Importowanie zakończone</string>\n    <string name=\"import_completed_hint\">Możesz usunąć oryginalny plik z pamięci, aby zaoszczędzić miejsce</string>\n    <string name=\"import_will_start_soon\">Import rozpocznie się wkrótce</string>\n    <string name=\"history_shortcuts\">Pokaż ostatnie skróty do mang</string>\n    <string name=\"history_shortcuts_summary\">Pokaż ostatnie mangi po długim naciśnięciu ikony aplikacji</string>\n    <string name=\"reader_control_ltr_summary\">Nie dostosowuj kierunku przełączania strony do trybu czytnika, np. naciśnięcie prawego zawsze powoduje przejście do następnej strony. Ta opcja ma wpływ tylko na sprzętowe urządzenia wejściowe</string>\n    <string name=\"reader_control_ltr\">Ergonomiczne sterowanie czytnikiem</string>\n    <string name=\"text_unsaved_changes_prompt\">Zapisać czy odrzucić niezapisane zmiany?</string>\n    <string name=\"error_no_space_left\">Brak miejsca w urządzeniu</string>\n    <string name=\"reader_slider\">Pokaż suwak przełączania stron</string>\n    <string name=\"webtoon_zoom\">Powiększanie webtoon</string>\n    <string name=\"clear_new_chapters_counters\">Wyczyść też informacje o nowych rozdziałach</string>\n    <string name=\"reset\">Resetuj</string>\n    <string name=\"manga_error_description_pattern\">Szczegóły błędu:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Spróbuj &lt;a href=%2$s&gt;otworzyć mangę w przeglądarce internetowej&lt;/a&gt;, aby upewnić się, że jest dostępna w swoim źródle&lt;br&gt;2. Upewnij się, że używasz &lt;a href=kotatsu://about&gt;najnowszej wersji Kotatsu&lt;/a&gt;&lt;br&gt;3. Jeśli jest dostępny, wyślij raport o błędzie do programistów.</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"services\">Usługi</string>\n    <string name=\"nothing_here\">Tutaj nic nie ma</string>\n    <string name=\"scrobbling_empty_hint\">By śledzić postęp w czytaniu, wybierz Menu → Śledź na ekranie detali mangi.</string>\n    <string name=\"find_similar\">Znajdź podobne</string>\n    <string name=\"user_agent\">Nagłówek UserAgent</string>\n    <string name=\"share_logs\">Udostępnij dzienniki</string>\n    <string name=\"enable_logging_summary\">Zapisz niektóre działania do celów debugowania. Nie włączaj go, jeśli nie jesteś pewien, co robisz</string>\n    <string name=\"allow_unstable_updates\">Zezwól na niestabilne aktualizacje</string>\n    <string name=\"got_it\">Rozumiem</string>\n    <string name=\"settings_apply_restart_required\">Uruchom ponownie aplikację by wprowadzić te zmiany</string>\n    <string name=\"speed\">Prędkość</string>\n    <string name=\"sources_reorder_tip\">Stuknij i przytrzymaj element, aby zmienić jego kolejność</string>\n    <string name=\"comics_archive_import_description\">Możesz wybrać jeden lub więcej plików .cbz lub .zip, każdy plik zostanie rozpoznany jako osobna manga.</string>\n    <string name=\"folder_with_images_import_description\">Możesz wybrać katalog z archiwami lub obrazami. Każde archiwum (lub podkatalog) zostanie rozpoznane jako rozdział.</string>\n    <string name=\"enable\">Włączać</string>\n    <string name=\"prefetch_content\">Wstępne ładowanie treści</string>\n    <string name=\"mark_as_current\">Oznacz jako aktualne</string>\n    <string name=\"enable_logging\">Włącz logowanie</string>\n    <string name=\"show_suspicious_content\">Pokaż podejrzane treści</string>\n    <string name=\"color_theme\">Schemat kolorów</string>\n    <string name=\"show_in_grid_view\">Pokaż w widoku siatki</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_dynamic\">Dynamiczne</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"ignore_ssl_errors\">Ignoruj błędy SSL</string>\n    <string name=\"mirror_switching\">Wybierz lustro automatycznie</string>\n    <string name=\"show_on_shelf\">Pokaż na półce</string>\n    <string name=\"mirror_switching_summary\">Automatycznie przełączaj domeny źródeł mangi w przypadku błędów, jeśli dostępne są serwery lustrzane</string>\n    <string name=\"pause\">Pauza</string>\n    <string name=\"resume\">Wznawiać</string>\n    <string name=\"paused\">Wstrzymane</string>\n    <string name=\"cancel_all\">Anulować całość</string>\n    <string name=\"downloads_wifi_only\">Pobieraj tylko przez Wi-Fi</string>\n    <string name=\"downloads_wifi_only_summary\">Zatrzymaj pobieranie po przełączeniu na sieć komórkową</string>\n    <string name=\"suggestion_manga\">Sugestia: %s</string>\n    <string name=\"suggestions_notifications_summary\">Czasami wyświetlaj powiadomienia z sugerowaną mangą</string>\n    <string name=\"more\">Więcej</string>\n    <string name=\"no_thanks\">Nie, dziękuję</string>\n    <string name=\"cancel_all_downloads_confirm\">Wszystkie aktywne pobrania zostaną anulowane, częściowo pobrane dane zostaną utracone</string>\n    <string name=\"remove_completed\">Usuwanie zakończone</string>\n    <string name=\"language\">Język</string>\n    <string name=\"allow_unstable_updates_summary\">Otrzymuj powiadomienia o niestabilnych kompilacjach</string>\n    <string name=\"download_started\">Pobieranie rozpoczęte</string>\n    <string name=\"sync_settings\">Ustawienia synchronizacji</string>\n    <string name=\"server_address\">Adres serwera</string>\n    <string name=\"sync_host_description\">Możesz użyć samoobsługowego serwera synchronizacji lub serwera domyślnego. Nie zmieniaj tego, jeśli nie jesteś pewien, co robisz.</string>\n    <string name=\"remove_completed_downloads_confirm\">Twoja historia pobierania zostanie trwale usunięta. Nie będzie to miało wpływu na żadne pobrane pliki</string>\n    <string name=\"text_downloads_list_holder\">Nie masz żadnych pobrań</string>\n    <string name=\"downloads_paused\">Pobieranie zostało wstrzymane</string>\n    <string name=\"downloads_resumed\">Pobieranie zostało wznowione</string>\n    <string name=\"downloads_cancelled\">Pobieranie zostało anulowane</string>\n    <string name=\"downloads_removed\">Pliki do pobrania zostały usunięte</string>\n    <string name=\"suggestions_enable_prompt\">Czy chcesz otrzymywać spersonalizowane sugestie dotyczące mangi\\?</string>\n    <string name=\"web_view_unavailable\">WebView niedostępny: sprawdź, czy dostawca WebView jest zainstalowany</string>\n    <string name=\"clear_network_cache\">Wyczyść pamięć podręczną sieci</string>\n    <string name=\"type\">Typ</string>\n    <string name=\"address\">Adres</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"sync_auth_hint\">Możesz zalogować się na istniejące konto lub utworzyć nowe</string>\n    <string name=\"password\">Hasło</string>\n    <string name=\"invalid_value_message\">Nieprawidłowa wartość</string>\n    <string name=\"images_proxy_title\">Proxy optymalizacji obrazów</string>\n    <string name=\"images_procy_description\">Użyj wsrv.nl usługa zmniejszająca zużycie ruchu i przyspieszająca ładowanie obrazu, jeśli to możliwe</string>\n    <string name=\"downloaded\">Pobrane</string>\n    <string name=\"username\">Nazwa użytkownika</string>\n    <string name=\"authorization_optional\">Autoryzacja (opcjonalnie)</string>\n    <string name=\"invert_colors\">Odwróć kolory</string>\n    <string name=\"invalid_port_number\">Nieprawidłowy numer portu</string>\n    <string name=\"network\">Sieć</string>\n    <string name=\"data_and_privacy\">Dane i prywatność</string>\n    <string name=\"restore_summary\">Przywróć wcześniej utworzoną kopię zapasową</string>\n    <string name=\"webtoon_zoom_summary\">Zezwalaj na powiększanie gestu w trybie webtoon</string>\n    <string name=\"show_pages_numbers_summary\">Pokaż numery stron w dolnym rogu</string>\n    <string name=\"reader_info_bar_summary\">Pokaż aktualny czas i postęp czytania u góry ekranu</string>\n    <string name=\"clear_source_cookies_summary\">Wyczyść pliki cookie tylko dla określonej domeny. W większości przypadków unieważni autoryzację</string>\n    <string name=\"download_option_all_chapters\">Wszystkie rozdziały z tłumaczeniem %s</string>\n    <string name=\"download_option_first_n_chapters\">Pierwszy %s</string>\n    <string name=\"download_option_all_unread\">Wszystkie nieprzeczytane rozdziały</string>\n    <string name=\"download_option_all_unread_b\">Wszystkie nieprzeczytane rozdziały (%s)</string>\n    <string name=\"download_option_whole_manga\">Cała manga</string>\n    <string name=\"download_option_next_unread_n_chapters\">Następna nieprzeczytana %s</string>\n    <string name=\"download_option_manual_selection\">Wybierz rozdziały ręcznie</string>\n    <string name=\"pick_custom_directory\">Wybierz katalog niestandardowy</string>\n    <string name=\"no_access_to_file\">Nie masz dostępu do tego pliku lub katalogu</string>\n    <string name=\"local_manga_directories\">Lokalne katalogi mangi</string>\n    <string name=\"advanced\">Zaawansowane</string>\n    <string name=\"this_month\">Ten miesiąc</string>\n    <string name=\"voice_search\">Wyszukiwanie głosowe</string>\n    <string name=\"color_light\">Jasny</string>\n    <string name=\"color_dark\">Ciemny</string>\n    <string name=\"color_white\">Biały</string>\n    <string name=\"color_black\">Czarny</string>\n    <string name=\"background\">Tło</string>\n    <string name=\"progress\">Postęp</string>\n    <string name=\"order_added\">Dodano</string>\n    <string name=\"description\">Opis</string>\n    <string name=\"languages\">Języki</string>\n    <string name=\"unknown\">Nieznane</string>\n    <string name=\"captcha_required_summary\">Do poprawnego działania %s wymagana jest weryfikacja Captcha</string>\n    <string name=\"error_corrupted_file\">Zwracane są nieprawidłowe dane lub plik jest uszkodzony</string>\n    <string name=\"related_manga_summary\">Pokaż listę podobnych mang. W niektórych przypadkach może być niedokładny lub brakować</string>\n    <string name=\"tracker_wifi_only_summary\">Nie sprawdzaj dostępności nowych partycji, jeśli korzystasz z ograniczonego połączenia internetowego</string>\n    <string name=\"on_device\">Na urządzeniu</string>\n    <string name=\"data_not_restored_text\">Upewnij się, że wybrałeś właściwy plik kopii zapasowej</string>\n    <string name=\"in_progress\">W trakcie</string>\n    <string name=\"data_not_restored\">Dane nie zostały przywrócone</string>\n    <string name=\"directories\">Katalogi</string>\n    <string name=\"manage_categories\">Zarządzaj kategoriami</string>\n    <string name=\"search_hint\">Wpisz tytuł mangi, gatunek lub nazwę źródła</string>\n    <string name=\"main_screen_sections\">Sekcje ekranu głównego</string>\n    <string name=\"too_many_requests_message\">Zbyt dużo próśb. Spróbuj ponownie później</string>\n    <string name=\"related_manga\">Podobna manga</string>\n    <string name=\"suggestions_wifi_only_summary\">Nie aktualizuj rekomendacji, jeśli korzystasz z ograniczonego połączenia internetowego</string>\n    <string name=\"manga_list\">Lista mang</string>\n    <string name=\"disable_nsfw\">Wyłącz NSFW</string>\n    <string name=\"show\">Pokazywać</string>\n    <string name=\"email_password_enter_hint\">Wpisz swój adres e-mail i hasło, aby kontynuować</string>\n    <string name=\"items_limit_exceeded\">Nie można dodać więcej elementów</string>\n    <string name=\"to_top\">W górę</string>\n    <string name=\"moved_to_top\">Przeniesiono na górę</string>\n    <string name=\"zoom_out\">Pomniejsz</string>\n    <string name=\"zoom_in\">Zbliżenie</string>\n    <string name=\"reader_zoom_buttons\">Pokaż przyciski powiększania</string>\n    <string name=\"reader_zoom_buttons_summary\">Określa, czy wyświetlać elementy sterujące powiększeniem w prawym dolnym rogu</string>\n    <string name=\"keep_screen_on\">Pozostaw ekran włączony</string>\n    <string name=\"keep_screen_on_summary\">Nie wyłączaj ekranu podczas czytania mangi</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"source_enabled\">Źródło włączone</string>\n    <string name=\"manage_sources\">Zarządzaj źródłami</string>\n    <string name=\"reader_fullscreen_summary\">Ukryj systemowe belki statusu i nawigacji</string>\n    <string name=\"genres_exclude\">Wykluczone gatunki</string>\n    <string name=\"enhanced_colors\">tryb koloru 32-bit</string>\n    <string name=\"periodic_backups\">Cykliczna kopia zapasowa</string>\n    <string name=\"suggest_new_sources\">Sugeruj nowe źródła po aktualizacji aplikacji</string>\n    <string name=\"periodic_backups_enable\">Włącz cykliczną kopię zapasową</string>\n    <string name=\"frequency_every_2_days\">Co 2 dni</string>\n    <string name=\"frequency_every_day\">Codziennie</string>\n    <string name=\"frequency_once_per_month\">Raz w miesiącu</string>\n    <string name=\"frequency_once_per_week\">Raz w tygodniu</string>\n    <string name=\"frequency_twice_per_month\">Dwa razy w miesiącu</string>\n    <string name=\"backup_frequency\">Częstotliwość tworzenia kopii zapasowej</string>\n    <string name=\"content_type_comics\">Komiks</string>\n    <string name=\"content_type_other\">Inne</string>\n    <string name=\"reader_optimize\">Zmniejsz użycie pamięci (beta)</string>\n    <string name=\"sources_catalog\">Katalog źródeł</string>\n    <string name=\"skip\">Pomiń</string>\n    <string name=\"grayscale\">Odcienie szarości</string>\n    <string name=\"mark_as_completed\">Oznacz jako kompletne</string>\n    <string name=\"mark_as_completed_prompt\">Zaznaczyć wybraną mangę jako przeczytaną?\n\\n\n\\nUwaga: bieżący postęp czytania zostanie utracony.</string>\n    <string name=\"prev_chapter\">Poprzedni rozdział</string>\n    <string name=\"next_chapter\">Następny rozdział</string>\n    <string name=\"prev_page\">Poprzednia strona</string>\n    <string name=\"next_page\">Następna strona</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Użyj przycisków głośności do przełączania stron</string>\n    <string name=\"switch_pages_volume_buttons\">Włącz przyciski głośności</string>\n    <string name=\"fullscreen_mode\">Tryb pełnoekranowy</string>\n    <string name=\"apply\">Zastosuj</string>\n    <string name=\"available_d\">Dostępne: %1$d</string>\n    <string name=\"disable_nsfw_summary\">Wyłącz źródła NSFW i ukryj mangi dla dorosłych z list jeśli to możliwe</string>\n    <string name=\"single_cbz_file\">Pojedynczy plik CBZ</string>\n    <string name=\"suggestions_unavailable_text\">Funkcjonalność sugestii jest wyłączona</string>\n    <string name=\"reading_time_estimation\">Pokaż przewidywany czas czytania</string>\n    <string name=\"reading_time_estimation_summary\">Przewidywany czas może być niedokładny</string>\n    <string name=\"categories\">Kategorie</string>\n    <string name=\"location\">Lokacja</string>\n    <string name=\"no_manga_sources_found\">Brak dostępnych źródeł mangi znalezionych przez zapytanie</string>\n    <string name=\"show_labels_in_navbar\">Pokaż etykiety na pasku narzędziowym</string>\n    <string name=\"ask_for_dest_dir_every_time\">Za każdym razem pytaj o katalog docelowy</string>\n    <string name=\"default_page_save_dir\">Domyślny katalog zapisu strony</string>\n    <string name=\"remove_from_history\">Usuń z historii</string>\n    <string name=\"pages_saving\">Zapisywanie stron</string>\n    <string name=\"enhanced_colors_summary\">Zmniejsza pasmo, ale może mieć wpływ na wydajność</string>\n    <string name=\"suggest_new_sources_summary\">Zapytaj o włączenie nowododanych źródeł po aktualizacji aplikacji</string>\n    <string name=\"backups_output_directory\">Katalog kopii zapasowej</string>\n    <string name=\"last_successful_backup\">Ostatnia pomyślna kopia zapasowa: %s</string>\n    <string name=\"lock_screen_rotation\">Zablokuj obracanie ekranu</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"state\">Stan</string>\n    <string name=\"error_multiple_genres_not_supported\">Filtrowanie według wielu gatunków nie jest obsługiwane przez to źródło mangi</string>\n    <string name=\"error_search_not_supported\">Wyszukiwanie nie jest wspierane przez to źródło mangi</string>\n    <string name=\"show_menu\">Pokaż menu</string>\n    <string name=\"toggle_ui\">Pokaż/ukryj UI</string>\n    <string name=\"downloads_settings_info\">Jeśli masz problemy z blokowaniem po stronie serwera, możesz włączyć spowolnienie pobierania dla każdego źródła mangi indywidualnie w ustawieniach tego źródła</string>\n    <string name=\"content_rating\">Klasyfikacja treści</string>\n    <string name=\"rating_suggestive\">Sugestywne</string>\n    <string name=\"last_read\">Ostatnio czytane</string>\n    <string name=\"online_variant\">Wariant online</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"no_manga_sources_catalog_text\">Nie ma źródeł dostępnych w tej sekcji, lub wszystkie mogły już zostać dodane.\n\\nSprawdź wkrótce</string>\n    <string name=\"state_paused\">Wstrzymano</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"reader_optimize_summary\">Zredukuj jakość stron poza ekranem, aby używać mniej pamięci</string>\n    <string name=\"error_multiple_states_not_supported\">Filtrowanie przy pomocy wielu stanów nie jest obsługiwane przez to źródło mangi</string>\n    <string name=\"globally\">Globalnie</string>\n    <string name=\"this_manga\">Ta manga</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Filtrowanie według gatunków, jak i ustawień regionalnych nie jest obsługiwane przez to źródło</string>\n    <string name=\"genres_search_hint\">Zacznij wpisywać nazwę gatunku</string>\n    <string name=\"sync_auth\">Zaloguj się, aby zsynchronizować konto</string>\n    <string name=\"restore\">Przywróć</string>\n    <string name=\"backup_date_\">Data kopii zapasowej: %s</string>\n    <string name=\"vertical\">Pionowy</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"reader_actions_summary\">Konfigurowanie działań dla obszarów ekranu, które można dotknąć</string>\n    <string name=\"reader_actions\">Akcje podczas czytania</string>\n    <string name=\"tap_action\">Dotknięcie</string>\n    <string name=\"long_tap_action\">Przytrzymanie</string>\n    <string name=\"none\">Brak</string>\n    <string name=\"use_two_pages_landscape\">Używanie układu dwóch stron w orientacji poziomej (beta)</string>\n    <string name=\"stats_cleared\">Statystyki wyczyszczono</string>\n    <string name=\"other_manga\">Inne mangi</string>\n    <string name=\"less_than_minute\">Mniej niż minutę</string>\n    <string name=\"clear_stats\">Wyczyść statystyki</string>\n    <string name=\"all_time\">Cały czas</string>\n    <string name=\"day\">Dzień</string>\n    <string name=\"three_months\">Trzy miesiące</string>\n    <string name=\"empty_stats_text\">Nie ma statystyk dla wybranego okresu</string>\n    <string name=\"statistics\">Statystyki</string>\n    <string name=\"week\">Tydzień</string>\n    <string name=\"clear_stats_confirm\">Czy na pewno chcesz wyczyścić wszystkie statystyki czytania? Tej czynności nie można cofnąć.</string>\n    <string name=\"month\">Miesiąc</string>\n    <string name=\"pages_read_s\">Strony przeczytane: %s</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Może pomóc w rozpoczęciu pobierania, jeśli masz z nim jakiekolwiek problemy</string>\n    <string name=\"welcome_text\">Wybierz źródła treści, jakie chcesz włączyć. Można to również skonfigurować później w ustawieniach</string>\n    <string name=\"by_name_reverse\">Odwrócona nazwa</string>\n    <string name=\"state_upcoming\">Nadchodzące</string>\n    <string name=\"rating_safe\">Bezpieczny</string>\n    <string name=\"rating_adult\">Dorosły</string>\n    <string name=\"default_tab\">Domyślna zakładka</string>\n    <string name=\"incognito_mode_hint\">Twoje postępy w czytaniu nie zostaną zapisane</string>\n    <string name=\"reading_stats\">Statystyki czytania</string>\n    <string name=\"default_webtoon_zoom_out\">Domyślne pomniejszenie webtoona</string>\n    <string name=\"category_hidden_done\">Ta kategoria została ukryta na ekranie głównym i jest dostępna poprzez Menu → Zarządzaj kategoriami</string>\n    <string name=\"color_correction_apply_text\">Te ustawienia mogą być stosowane globalnie lub tylko dla bieżącej mangi. W przypadku zastosowania globalnego, indywidualne ustawienia nie zostaną nadpisane.</string>\n    <string name=\"preferred_download_format\">Preferowany format pobierania</string>\n    <string name=\"automatic\">Automatyczny</string>\n    <string name=\"multiple_cbz_files\">Wiele plików CBZ</string>\n    <string name=\"error_filter_states_genre_not_supported\">Filtrowanie według gatunków jak i stanów nie jest obsługiwane przez to źródło</string>\n    <string name=\"check_for_new_chapters_disabled\">Sprawdzanie nowych rozdziałów jest wyłączone</string>\n    <string name=\"list_options\">Opcje listy</string>\n    <string name=\"config_reset_confirm\">Zresetować ustawienia do wartości domyślnych? Tej czynności nie można cofnąć.</string>\n    <string name=\"volume_\">Wolumin %d</string>\n    <string name=\"volume_unknown\">Nieznany wolumin</string>\n    <string name=\"state_abandoned\">Porzucony</string>\n    <string name=\"manual\">Ręcznie</string>\n    <string name=\"by_relevance\">Istotność</string>\n    <string name=\"alternatives\">Alternatywy</string>\n    <string name=\"migrate\">Migruj</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" z \\\"%2$s\\\" zostanie zamieniona przez \\\"%3$s\\\" z \\\"%4$s\\\" w historii i ulubionych (jeśli jest obecna)</string>\n    <string name=\"manga_migration\">Migracja mangi</string>\n    <string name=\"migration_completed\">Migracja zakończona</string>\n    <string name=\"delete_read_chapters_summary\">Usuń przeczytane rozdziały z pamięci lokalnej, aby zwolnić miejsce</string>\n    <string name=\"delete_read_chapters\">Usuń przeczytane rozdziały</string>\n    <string name=\"no_chapters_deleted\">Żadne rozdziały nie zostały usunięte</string>\n    <string name=\"chapters_deleted_pattern\">Usunięto %1$s, wyczyszczono %2$s</string>\n    <string name=\"delete_read_chapters_prompt\">Spowoduje to trwałe usunięcie wszystkich rozdziałów oznaczonych jako przeczytane z pamięci lokalnej. Możesz je pobrać ponownie później, ale zaimportowane rozdziały mogą zostać utracone na zawsze</string>\n    <string name=\"chapters_grid_view\">Widok siatki</string>\n    <string name=\"delete_read_chapters_auto\">Automatyczne usuwanie przeczytanych rozdziałów</string>\n    <string name=\"runs_on_app_start\">Wykonaj podczas uruchamiania aplikacji</string>\n    <string name=\"split_by_translations\">Podziel według tłumaczeń</string>\n    <string name=\"split_by_translations_summary\">Wyświetlaj rozdziały z różnymi tłumaczeniami osobno, a nie razem w jednej liście</string>\n    <string name=\"order_oldest\">Najstarszy</string>\n    <string name=\"long_ago_read\">Przeczytane dawno temu</string>\n    <string name=\"unread\">Nieprzeczytany</string>\n    <string name=\"enable_source\">Włącz źródło</string>\n    <string name=\"unsupported_source\">To źródło mangi nie jest obsługiwane</string>\n    <string name=\"show_pages_thumbs\">Pokaż miniatury stron</string>\n    <string name=\"error_no_data_received\">Nie odebrano żadnych danych z serwera</string>\n    <string name=\"show_pages_thumbs_summary\">Włącz zakładkę \\\"Strony\\\" na ekranie szczegółów</string>\n    <string name=\"unsupported_backup_message\">Proszę wybrać odpowiedni plik kopii zapasowej Kotatsu</string>\n    <string name=\"hours_minutes_short\">%1$d g %2$d m</string>\n    <string name=\"hours_short\">%d g</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"fix\">Napraw</string>\n    <string name=\"missing_storage_permission\">Nie ma uprawień dostępu do mangi w pamięci zewnętrznej</string>\n    <string name=\"last_used\">Ostatnie użycie</string>\n    <string name=\"webtoon_gaps\">Przerwy w trybie webtoon</string>\n    <string name=\"webtoon_gaps_summary\">Pokaż pionowe przerwy pomiędzy stronami w trybie webtoon</string>\n    <string name=\"show_updated\">Pokaż zaktualizowane</string>\n    <string name=\"suggested_queries\">Sugerowane zapytania</string>\n    <string name=\"authors\">Autorzy</string>\n    <string name=\"recent_queries\">Ostatnie zapytania</string>\n    <string name=\"less_frequently\">Rzadziej</string>\n    <string name=\"more_frequently\">Częściej</string>\n    <string name=\"frequency_of_check\">Częstotliwość kontroli</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Przypnij interfejs nawigacji</string>\n    <string name=\"pin_navigation_ui_summary\">Nie ukrywaj paska nawigacji i paska wyszukiwania podczas przewijania</string>\n    <string name=\"search_suggestions\">Sugestie wyszukiwania</string>\n    <string name=\"blocked_by_server_message\">Jesteś zablokowany przez serwer. Spróbuj użyć innego połączenia sieciowego (VPN, proxy itp.)</string>\n    <string name=\"sources_disabled\">Źródła wyłączone</string>\n    <string name=\"disable\">Wyłączone</string>\n    <string name=\"disable_connectivity_check\">Wyłącz sprawdzanie łączności</string>\n    <string name=\"ignore_ssl_errors_summary\">Możesz wyłączyć weryfikację certyfikatów SSL w przypadku wystąpienia problemów związanych z SSL podczas uzyskiwania dostępu do zasobów sieciowych. Może to mieć wpływ na Twoje bezpieczeństwo. Po zmianie tego ustawienia wymagane jest ponowne uruchomienie aplikacji.</string>\n    <string name=\"disable_connectivity_check_summary\">Pomiń sprawdzanie łączności w przypadku problemów z nią (np. przejście do trybu offline, mimo że sieć jest podłączona)</string>\n    <string name=\"disable_nsfw_notifications\">Wyłącz powiadomienia NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">Nie pokazuj powiadomień o aktualizacjach mangi NSFW</string>\n    <string name=\"tracker_debug_info_summary\">Debuguj informacje o sprawdzaniu dostępności nowych rozdziałów</string>\n    <string name=\"tracker_debug_info\">Dziennik sprawdzania nowych rozdziałów</string>\n    <string name=\"_new\">Nowy</string>\n    <string name=\"screenshots_block_incognito\">Blokuj w trybie incognito</string>\n    <string name=\"all_languages\">Wszystkie języki</string>\n    <string name=\"image_server\">Preferowany serwer obrazów</string>\n    <string name=\"crop_pages\">Kadrowanie stron</string>\n    <string name=\"pin\">Przypnij</string>\n    <string name=\"unpin\">Odepnij</string>\n    <string name=\"source_pinned\">Przypięte źródło</string>\n    <string name=\"source_unpinned\">Źródło odpięte</string>\n    <string name=\"sources_unpinned\">Odpięte źródła</string>\n    <string name=\"sources_pinned\">Przypięte źródła</string>\n    <string name=\"recent_sources\">Najnowsze źródła</string>\n    <string name=\"percent_read\">Procent przeczytania</string>\n    <string name=\"percent_left\">Pozostały procent</string>\n    <string name=\"chapters_read\">Przeczytane rozdziały</string>\n    <string name=\"chapters_left\">Pozostałe rozdziały</string>\n    <string name=\"show_quick_filters\">Pokaż szybkie filtry</string>\n    <string name=\"show_quick_filters_summary\">Zapewnia możliwość filtrowania list mangi według określonych parametrów</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Nie ma mangi pasującej do wybranych filtrów</string>\n    <string name=\"connection_ok\">Połączenie jest OK</string>\n    <string name=\"invalid_proxy_configuration\">Błędna konfiguracja serwera proxy</string>\n    <string name=\"external_source\">Zewnętrzne/wtyczka</string>\n    <string name=\"plugin_incompatible\">Niekompatybilna wtyczka lub błąd wewnętrzny. Upewnij się, że używasz najnowszej wersji wtyczki i Kotatsu</string>\n    <string name=\"invalid_server_address_message\">Nieprawidłowy adres serwera</string>\n    <string name=\"too_many_requests_message_retry\">Zbyt wiele żądań. Spróbuj ponownie za %s</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"minutes_seconds_short\">%1$d min %2$d s</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"skip_all\">Pomiń wszystko</string>\n    <string name=\"stuck\">Utknął</string>\n    <string name=\"not_in_favorites\">Nie dodany do ulubionych</string>\n    <string name=\"retry\">Ponów</string>\n    <string name=\"unpopular\">Niepopularny</string>\n    <string name=\"low_rating\">Niska ocena</string>\n    <string name=\"by_date\">Data</string>\n    <string name=\"popularity\">Popularność</string>\n    <string name=\"updated_long_ago\">Zaktualizowano dawno temu</string>\n    <string name=\"scrobbler_auth_required\">Zaloguj się do %s aby kontynuować</string>\n    <string name=\"scrobbler_auth_intro\">Zaloguj się, aby skonfigurować integrację z %s. Umożliwi to śledzenie postępów i statusu czytania mangi</string>\n    <string name=\"unstable_feature\">Niestabilna funkcja</string>\n    <string name=\"unstable_feature_summary\">Ta funkcja jest eksperymentalna. Upewnij się, że masz kopię zapasową, aby uniknąć utraty danych</string>\n    <string name=\"sort_order_asc\">Rosnąco</string>\n    <string name=\"sort_order_desc\">Malejąco</string>\n    <string name=\"recently_added\">Ostatnio dodane</string>\n    <string name=\"added_long_ago\">Dodane dawno temu</string>\n    <string name=\"popular_in_hour\">Popularne w tej chwili</string>\n    <string name=\"popular_in_week\">Popularne w tym tygodniu</string>\n    <string name=\"popular_in_year\">Popularne w tym roku</string>\n    <string name=\"original_language\">Oryginalny język</string>\n    <string name=\"year\">Rok</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"demographics\">Pokolenie</string>\n    <string name=\"years\">Lata</string>\n    <string name=\"any\">Dowolny</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"content_type_one_shot\">Jeden strzał</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_image_set\">Zestaw obrazów</string>\n    <string name=\"content_type_artist_cg\">Grafika komputerowa</string>\n    <string name=\"content_type_game_cg\">Grafika z gry</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"source_code\">Kod źródłowy</string>\n    <string name=\"user_manual\">Instrukcja użytkownika</string>\n    <string name=\"telegram_group\">Grupa Telegram</string>\n    <string name=\"debug\">Debugowanie</string>\n    <string name=\"error_image_format\">Niewspierany format obrazu: %s</string>\n    <string name=\"downloads_background\">Pobieranie w tle</string>\n    <string name=\"download_new_chapters\">Pobierz nowe rozdziały</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga z pobranymi rozdziałami</string>\n    <string name=\"fixing_manga\">Naprawianie mangi</string>\n    <string name=\"fixed\">Naprawiono pomyślnie</string>\n    <string name=\"manga_fix_prompt\">Ta funkcja znajdzie alternatywne źródła dla wybranej mangi. Zadanie zajmie trochę czasu i będzie przebiegać w tle</string>\n    <string name=\"no_alternatives_found\">Nie znaleziono alternatyw dla \\\"%s\\\"</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) zamieniona przez \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"no_fix_required\">Nie jest wymagana naprawa dla \\\"%s\\\"</string>\n    <string name=\"start_download\">Rozpocznij pobieranie</string>\n    <string name=\"save_manga_confirm\">Zapisać wybraną mangę? Może to zużyć transfer i miejsce na dysku</string>\n    <string name=\"save_manga\">Zapisz mangę</string>\n    <string name=\"genre\">Gatunek</string>\n    <string name=\"download_added\">Dodano pobieranie</string>\n    <string name=\"more_options\">Więcej opcji</string>\n    <string name=\"destination_directory\">Katalog docelowy</string>\n    <string name=\"chapter_selection_hint\">Możesz wybrać rozdziały do pobrania, klikając i przytrzymując element na liście rozdziałów.</string>\n    <string name=\"chapters_all\">Cała</string>\n    <string name=\"content_type_novel\">Powieść</string>\n    <string name=\"popular_today\">Popularne dziś</string>\n    <string name=\"popular_in_month\">Popularne w tym miesiącu</string>\n    <string name=\"filter_search_warning\">To źródło nie obsługuje wyszukiwania z filtrami. Twoje filtry zostały wyczyszczone</string>\n    <string name=\"download_over_cellular\">Pobieranie przez sieć komórkową</string>\n    <string name=\"download_cellular_confirm\">Zezwolić na pobieranie przez sieć komórkową?</string>\n    <string name=\"dont_allow\">Nie zezwalaj</string>\n    <string name=\"allow_always\">Zezwalaj zawsze</string>\n    <string name=\"allow_once\">Zezwól raz</string>\n    <string name=\"ask_every_time\">Pytaj za każdym razem</string>\n    <string name=\"pages_saved\">Zapisane strony</string>\n    <string name=\"portrait\">Portret</string>\n    <string name=\"access_denied_403\">Odmowa dostępu (403)</string>\n    <string name=\"captcha_required_message\">To źródło wymaga rozwiązania captcha, aby kontynuować</string>\n    <string name=\"handle_links\">Obsługa linków</string>\n    <string name=\"email\">E-mail</string>\n    <string name=\"max_backups_count\">Maksymalna liczba kopii zapasowych</string>\n    <string name=\"delete_old_backups\">Usuń stare kopie zapasowe</string>\n    <string name=\"delete_old_backups_summary\">Automatycznie usuwaj stare pliki kopii zapasowych, aby zaoszczędzić miejsce na dysku</string>\n    <string name=\"plugin_incompatible_with_cause\">Błąd wtyczki: %s\\nUpewnij się, że używasz najnowszej wersji wtyczki i Kotatsu</string>\n    <string name=\"error_not_image\">Błędny format: oczekiwany obraz, ale pobrano %s</string>\n    <string name=\"screen_orientation\">Orientacja ekranu</string>\n    <string name=\"landscape\">Poziomo</string>\n    <string name=\"handle_links_summary\">Obsługuj linki do mangi z zewnętrznych aplikacji (np. przeglądarki internetowej). Może być również konieczne ręczne włączenie go w ustawieniach systemowych aplikacji</string>\n    <string name=\"error_disclaimer_report\">Możesz zgłosić usterkę programistom. To pomoże nam znaleźć i naprawić problem.</string>\n    <string name=\"translation\">Tłumaczenie</string>\n    <string name=\"backup_tg_echo\">Próbna wiadomość</string>\n    <string name=\"clear_database\">Wyczyść bazę danych</string>\n    <string name=\"send_backups_telegram\">Wysyłaj kopie zapasowe do Telegram</string>\n    <string name=\"error_details\">Szczegóły błędu</string>\n    <string name=\"error_disclaimer_app_outdated\">Wygląda na to, że twoja wersja Kotatsu jest nieaktualna. Proszę zainstalować najnowszą wersję, aby mieć dostęp do wszystkich dostępnych poprawek.</string>\n    <string name=\"backup_restored_background\">Kopia zapasowa zostanie przywrócona w tle</string>\n    <string name=\"restoring_backup\">Przywracanie kopii zapasowej</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Manga dla dorosłych nie będzie pokazywana w sugestiach. Ta opcja może działać niepoprawnie z niektórymi źródłami</string>\n    <string name=\"backup_tg_check\">Sprawdź czy API działa poprawnie</string>\n    <string name=\"enable_all_sources\">Włącz wszystkie źródła mangi</string>\n    <string name=\"enable_all_sources_summary\">Wszystkie dostępne źródła mangi zostaną włączone na stałe</string>\n    <string name=\"all_sources_enabled\">Wszystkie źródła są włączone</string>\n    <string name=\"telegram_chat_id_summary\">Wpisz ID chatu do którego będą wysyłane kopie zapasowe</string>\n    <string name=\"test_connection\">Sprawdź połączenie</string>\n    <string name=\"chapters_and_pages\">Rozdziały i strony</string>\n    <string name=\"reader_controls_in_bottom_bar\">Kontrolki czytnika w dolnym pasku</string>\n    <string name=\"screen_rotation_locked\">Obracanie ekranu zostało zablokowane</string>\n    <string name=\"screen_rotation_unlocked\">Obracanie ekranu zostało odblokowane</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"source\">Źródło</string>\n    <string name=\"search_everywhere\">Wyszukaj wszędzie</string>\n    <string name=\"disable_captcha_notifications\">Wyłącz powiadomenia o captcha</string>\n    <string name=\"clear_browser_data_summary\">Wyczyść dane przeglądarki takie jak pliki cookie i cache. Uwaga: Upoważnienia w źródłach mangi mogą stać się nieaktualne</string>\n    <string name=\"reader_info_bar_transparent\">Przeźroczysty pasek informacji w czytniku</string>\n    <string name=\"tags_warnings_summary\">Wyróżnij gatunki, które mogą być nieodpowiednie dla większości użytkowników</string>\n    <string name=\"error_connection_reset\">Połączenie zresetowane przez zdalny host</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"open_telegram_bot\">Otwórz bota z Telegram</string>\n    <string name=\"show_slider\">Pokaż suwak</string>\n    <string name=\"incognito\">Incognito</string>\n    <string name=\"backup_tg_id_not_set\">Nie ustawiono ID czatu</string>\n    <string name=\"telegram_chat_id\">ID czatu Telegrama</string>\n    <string name=\"error_disclaimer_manga\">Spróbuj otworzyć mangę w przeglądarce internetowej, aby upewnić się, że jest dostępna w swoim źródle.</string>\n    <string name=\"link_to_manga_in_app\">Link do mangi w Kotatsu</string>\n    <string name=\"clear_browser_data\">Wyczyść dane przeglądarki</string>\n    <string name=\"include_disabled_sources\">Uwzględnij wyłączone źródła</string>\n    <string name=\"suggestions_disabled_sources_summary\">Pokaż sugestie ze wszystkich źródeł mangi, uwzględniając też wyłączone</string>\n    <string name=\"tags_warnings\">Wyróżnij niebezpieczne gatunki</string>\n    <string name=\"use_default_cover\">Użyj domyślnej okładki</string>\n    <string name=\"pick_manga_page\">Wybierz stronę mangi</string>\n    <string name=\"pick_custom_file\">Wybierz plik niestandardowy</string>\n    <string name=\"change_cover\">Zmień okładkę</string>\n    <string name=\"dont_ask_again\">Nie pytaj więcej</string>\n    <string name=\"incognito_for_nsfw\">Tryb incognito dla mangi NSFW</string>\n    <string name=\"incognito_mode_hint_nsfw\">Ta manga może zawierać treści dla dorosłych. Czy chcesz korzystać z trybu incognito?</string>\n    <string name=\"search_disabled_sources\">Przeszukiwanie wyłączonych źródeł</string>\n    <string name=\"chapter_volume_number\">Wolumin %1$s Rozdział %2$s</string>\n    <string name=\"chapter_number\">Rozdział %s</string>\n    <string name=\"unnamed_chapter\">Nienazwany rozdział</string>\n    <string name=\"additional_action_required\">Wymagane dodatkowe działanie</string>\n    <string name=\"clear_database_summary\">Usuń informacje o mandze, które nie są używane</string>\n    <string name=\"theme_name_expressive\">Wyraźny (Test)</string>\n    <string name=\"no_write_permission_to_file\">Nie ma uprawnień do zapisu pliku</string>\n    <string name=\"simple\">Prosty</string>\n    <string name=\"manga_override_hint\">Te zmiany wpłyną na sposób wyświetlania mangi w aplikacji</string>\n    <string name=\"open_telegram_bot_summary\">Naciśnij, aby otworzyć czat z Kotatsu Backup Bot</string>\n    <string name=\"pages_slider\">Suwak przełączania stron</string>\n    <string name=\"link_to_manga_on_s\">Link do mangi na %s</string>\n    <string name=\"disable_captcha_notifications_summary\">Nie będziesz otrzymywać powiadomień o rozwiązaniu CAPTCHA dla tego źródła, ale może to prowadzić do przerwania operacji w tle (sprawdzanie nowych rozdziałów, uzyskiwanie rekomendacji itp.)</string>\n    <string name=\"global_search\">Wyszukiwanie globalne</string>\n    <string name=\"rating\">Ocena</string>\n    <string name=\"page_switch_timer\">Strona będzie się przełączać co ~%d Sekund</string>\n    <string name=\"error_non_file_uri\">Wybrana ścieżka nie może być użyta, ponieważ nie oznacza pliku ani katalogu</string>\n    <string name=\"badges_in_lists\">Plakietki na listach</string>\n    <string name=\"collapse\">Zwiń</string>\n    <string name=\"expand\">Rozwiń</string>\n    <string name=\"adblock\">Blokuj reklamy w przeglądarce</string>\n    <string name=\"adblock_summary\">Blokuj reklamy we wbudowanej przeglądarce (beta)</string>\n    <string name=\"creating_backup\">Tworzenie kopii zapasowej</string>\n    <string name=\"collapse_long_description\">Zwiń długi opis</string>\n    <string name=\"changelog\">Lista zmian</string>\n    <string name=\"changelog_summary\">Historia zmian dla ostatnio wydanych wersji</string>\n    <string name=\"reader_multitask_summary\">Pozwala na jednoczesne trzymanie wielu czytelników z różnymi mangami otwartymi</string>\n    <string name=\"share_backup\">Udostępnianie kopii zapasowej</string>\n    <string name=\"reader_multitask\">Otwórz czytnik w osobnym zadaniu</string>\n    <string name=\"hide_from_main_screen\">Ukryj na ekranie głównym</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"book_effect\">Żółtawe tło (niebieski filtr)</string>\n    <string name=\"packup_creation_failed\">Nie udało się stworzyć kopii zapasowej</string>\n    <string name=\"main_screen\">Ekran główny</string>\n    <string name=\"main_screen_fab_summary\">Pozwala wznowić czytanie jednym kliknięciem. Ten przycisk nie będzie pojawiał się w trybie inkognito, albo kiedy historia będzie pusta</string>\n    <string name=\"unavailable\">Niedostępne</string>\n    <string name=\"no_chapters_in_manga\">Ta manga nie zawiera żadnych rozdziałów</string>\n    <string name=\"chapters_load_failed\">Nie udało się załadować listy rozdziałów</string>\n    <string name=\"telegram_integration\">Integracja z Telegramem</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pt/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minuto atrás</item>\n        <item quantity=\"many\">%1$d minutos atrás</item>\n        <item quantity=\"other\">%1$d minutos atrás</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d elemento</item>\n        <item quantity=\"many\">%1$d elementos</item>\n        <item quantity=\"other\">%1$d elementos</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d capítulo</item>\n        <item quantity=\"many\">%1$d capítulos</item>\n        <item quantity=\"other\">%1$d capítulos</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d novo capítulo</item>\n        <item quantity=\"many\">%1$d novos capítulos</item>\n        <item quantity=\"other\">%1$d novos capítulos</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d mês atrás</item>\n        <item quantity=\"many\">%1$d meses atrás</item>\n        <item quantity=\"other\">%1$d meses atrás</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d dia atrás</item>\n        <item quantity=\"many\">%1$d dias atrás</item>\n        <item quantity=\"other\">%1$d dias atrás</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d hora atrás</item>\n        <item quantity=\"many\">%1$d horas atrás</item>\n        <item quantity=\"other\">%1$d horas atrás</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d hora</item>\n        <item quantity=\"many\">%1$d horas</item>\n        <item quantity=\"other\">%1$d horas</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuto</item>\n        <item quantity=\"many\">%1$d minutos</item>\n        <item quantity=\"other\">%1$d minutos</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"local_storage\">Armazenamento local</string>\n    <string name=\"favourites\">Favoritos</string>\n    <string name=\"error_occurred\">Ocorreu um erro</string>\n    <string name=\"network_error\">Erro de rede</string>\n    <string name=\"details\">Detalhes</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"detailed_list\">Lista detalhada</string>\n    <string name=\"grid\">Grade</string>\n    <string name=\"list_mode\">Modo lista</string>\n    <string name=\"settings\">Configurações</string>\n    <string name=\"loading_\">Carregando…</string>\n    <string name=\"chapter_d_of_d\">Capítulo %1$d de %2$d</string>\n    <string name=\"try_again\">Tente novamente</string>\n    <string name=\"clear_history\">Limpar histórico</string>\n    <string name=\"nothing_found\">Nada encontrado</string>\n    <string name=\"history_is_empty\">Sem histórico ainda</string>\n    <string name=\"you_have_not_favourites_yet\">Ainda não há favoritos</string>\n    <string name=\"add_to_favourites\">Adicionar aos favoritos</string>\n    <string name=\"add\">Adicionar</string>\n    <string name=\"save\">Salvar</string>\n    <string name=\"share\">Compartilhar</string>\n    <string name=\"create_shortcut\">Criar atalho</string>\n    <string name=\"share_s\">Compartilhar %s</string>\n    <string name=\"search\">Procurar</string>\n    <string name=\"search_manga\">Pesquisar mangá</string>\n    <string name=\"manga_downloading_\">Baixando…</string>\n    <string name=\"download_complete\">Baixado</string>\n    <string name=\"downloads\">Baixados</string>\n    <string name=\"by_name\">Nome</string>\n    <string name=\"popular\">Populares</string>\n    <string name=\"by_rating\">Avaliação</string>\n    <string name=\"sort_order\">Ordem de classificação</string>\n    <string name=\"filter\">Filtro</string>\n    <string name=\"dark\">Escuro</string>\n    <string name=\"follow_system\">Siga o sistema</string>\n    <string name=\"pages\">Páginas</string>\n    <string name=\"clear\">Limpar</string>\n    <string name=\"remove\">Remover</string>\n    <string name=\"_s_deleted_from_local_storage\">«%s» deletado do armazenamento local</string>\n    <string name=\"save_page\">Salvar página</string>\n    <string name=\"page_saved\">Página salva</string>\n    <string name=\"share_image\">Compartilhar imagem</string>\n    <string name=\"_import\">Importar</string>\n    <string name=\"updated\">Atualizado</string>\n    <string name=\"delete\">Excluir</string>\n    <string name=\"operation_not_supported\">Essa operação não é suportada</string>\n    <string name=\"clear_pages_cache\">Limpar cache de página</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Padrão</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Modo leitura</string>\n    <string name=\"grid_size\">Tamanho da grade</string>\n    <string name=\"search_on_s\">Pesquisar em %s</string>\n    <string name=\"delete_manga\">Deletar mangá</string>\n    <string name=\"reader_settings\">Configurações de leitura</string>\n    <string name=\"switch_pages\">Mudar páginas</string>\n    <string name=\"error\">Erro</string>\n    <string name=\"clear_thumbs_cache\">Limpar cache de miniaturas</string>\n    <string name=\"search_history_cleared\">Limpo</string>\n    <string name=\"internal_storage\">Armazenamento interno</string>\n    <string name=\"external_storage\">Armazenamento externo</string>\n    <string name=\"domain\">Domínio</string>\n    <string name=\"app_update_available\">Uma nova versão do app está disponível</string>\n    <string name=\"open_in_browser\">Abrir no navegador da web</string>\n    <string name=\"notifications\">Notificações</string>\n    <string name=\"new_chapters\">Novos capítulos</string>\n    <string name=\"download\">Download</string>\n    <string name=\"notifications_settings\">Configurações das notificações</string>\n    <string name=\"light_indicator\">Indicador LED</string>\n    <string name=\"remote_sources\">Fontes de mangá</string>\n    <string name=\"close\">Fechar</string>\n    <string name=\"light\">Claro</string>\n    <string name=\"history\">Histórico</string>\n    <string name=\"read\">Ler</string>\n    <string name=\"processing_\">Processando…</string>\n    <string name=\"newest\">Novos</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"no_description\">Sem descrição</string>\n    <string name=\"_continue\">Continuar</string>\n    <string name=\"chapters\">Capítulos</string>\n    <string name=\"add_new_category\">Nova categoria</string>\n    <string name=\"text_delete_local_manga\">Apagar permanentemente «%s» do dispositivo?</string>\n    <string name=\"text_file_not_supported\">Escolha um ficheiro ZIP ou CBZ.</string>\n    <string name=\"clear_search_history\">Limpar histórico de pesquisa</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Ativado %1$d de %2$d</string>\n    <string name=\"notification_sound\">Som de notificação</string>\n    <string name=\"show_pages_numbers\">Páginas numeradas</string>\n    <string name=\"state_finished\">Concluído</string>\n    <string name=\"state_ongoing\">Em andamento</string>\n    <string name=\"remove_category\">Remover</string>\n    <string name=\"text_empty_holder_primary\">Está meio vazio aqui…</string>\n    <string name=\"manga_shelf\">Estante</string>\n    <string name=\"done\">Feito</string>\n    <string name=\"zoom_mode_keep_start\">Manter no início</string>\n    <string name=\"clear_updates_feed\">Limpar atualizações de fluxo</string>\n    <string name=\"updates_feed_cleared\">Limpo</string>\n    <string name=\"update\">Atualizar</string>\n    <string name=\"feed_will_update_soon\">A atualização do fluxo começará em breve</string>\n    <string name=\"track_sources\">Procure atualizações</string>\n    <string name=\"dont_check\">Não verifique</string>\n    <string name=\"enter_password\">Digite a senha</string>\n    <string name=\"wrong_password\">Senha incorreta</string>\n    <string name=\"repeat_password\">Repita a senha</string>\n    <string name=\"passwords_mismatch\">Senhas incompatíveis</string>\n    <string name=\"about\">Cerca de</string>\n    <string name=\"app_version\">Versão %s</string>\n    <string name=\"check_for_updates\">Verifique se há atualizações</string>\n    <string name=\"no_update_available\">Nenhuma atualização disponível</string>\n    <string name=\"right_to_left\">Da direita para a esquerda (←)</string>\n    <string name=\"create_category\">Nova categoria</string>\n    <string name=\"scale_mode\">Modo de escala</string>\n    <string name=\"zoom_mode_fit_center\">Centro de ajuste</string>\n    <string name=\"zoom_mode_fit_width\">Ajustar à largura</string>\n    <string name=\"backup_restore\">Backup e restauração</string>\n    <string name=\"create_backup\">Criar backup de dados</string>\n    <string name=\"restore_backup\">Restaurar do backup</string>\n    <string name=\"data_restored\">Restaurado</string>\n    <string name=\"preparing_\">Preparando…</string>\n    <string name=\"file_not_found\">Ficheiro não encontrado</string>\n    <string name=\"data_restored_success\">Todos os dados foram restaurados</string>\n    <string name=\"data_restored_with_errors\">Os dados foram restaurados, mas há erros</string>\n    <string name=\"just_now\">Agora mesmo</string>\n    <string name=\"yesterday\">Ontem</string>\n    <string name=\"long_ago\">Muito tempo atrás</string>\n    <string name=\"group\">Grupo</string>\n    <string name=\"today\">Hoje</string>\n    <string name=\"tap_to_try_again\">Toque para tentar novamente</string>\n    <string name=\"silent\">Silencioso</string>\n    <string name=\"captcha_required\">CAPTCHA obrigatório</string>\n    <string name=\"captcha_solve\">Resolver</string>\n    <string name=\"cookies_cleared\">Todos os cookies foram removidos</string>\n    <string name=\"clear_feed\">Limpar o fluxo</string>\n    <string name=\"text_clear_updates_feed_prompt\">Limpar todo o histórico de atualizações permanentemente\\?</string>\n    <string name=\"check_for_new_chapters\">Em busca de novos capítulos</string>\n    <string name=\"reverse\">Reverter</string>\n    <string name=\"sign_in\">Entrar</string>\n    <string name=\"default_s\">Padrão: %s</string>\n    <string name=\"next\">Próximo</string>\n    <string name=\"protect_application_subtitle\">Digite a senha que será necessária quando a app for iniciado</string>\n    <string name=\"confirm\">Confirme</string>\n    <string name=\"password_length_hint\">A senha deve ter 4 caracteres ou mais</string>\n    <string name=\"backup_saved\">Backup salvo</string>\n    <string name=\"tracker_warning\">Alguns aparelhos têm um comportamento de sistema diferente, o que pode interromper as tarefas em segundo plano.</string>\n    <string name=\"read_more\">Leia mais</string>\n    <string name=\"welcome\">Bem vindo</string>\n    <string name=\"queued\">Enfileirado</string>\n    <string name=\"about_app_translation_summary\">Traduzir esta aplicação</string>\n    <string name=\"chapter_is_missing\">O capítulo está em falta</string>\n    <string name=\"auth_complete\">Autorizado</string>\n    <string name=\"auth_not_supported_by\">O login em %s não é suportado</string>\n    <string name=\"genres\">Gêneros</string>\n    <string name=\"about_app_translation\">Tradução</string>\n    <string name=\"text_clear_cookies_prompt\">Será desconectado de todas as fontes</string>\n    <string name=\"vibration\">Vibração</string>\n    <string name=\"cannot_find_available_storage\">Sem armazenamento disponível</string>\n    <string name=\"favourites_categories\">Categorias favoritas</string>\n    <string name=\"text_history_holder_secondary\">Encontre o que ler na seção «Explorar»</string>\n    <string name=\"text_local_holder_secondary\">Salve-o de catálogos online ou importe de um arquivo.</string>\n    <string name=\"recent_manga\">Recente</string>\n    <string name=\"other_storage\">Outro armazenamento</string>\n    <string name=\"text_search_holder_secondary\">Tente reformular a consulta.</string>\n    <string name=\"not_available\">Não disponível</string>\n    <string name=\"size_s\">Tamanho: %s</string>\n    <string name=\"text_history_holder_primary\">O que ler será exibido aqui</string>\n    <string name=\"text_local_holder_primary\">Salve algo primeiro</string>\n    <string name=\"pages_animation\">Animação de página</string>\n    <string name=\"favourites_category_empty\">Categoria vazia</string>\n    <string name=\"read_later\">Leia mais tarde</string>\n    <string name=\"updates\">Atualizações</string>\n    <string name=\"all_favourites\">Todos os favoritos</string>\n    <string name=\"search_results\">Resultados da pesquisa</string>\n    <string name=\"text_feed_holder\">Novos capítulos de que está lendo são mostrados aqui</string>\n    <string name=\"new_version_s\">Nova versão: %s</string>\n    <string name=\"rotate_screen\">Girar o ecrã</string>\n    <string name=\"protect_application\">Proteja a app</string>\n    <string name=\"protect_application_summary\">Peça a senha ao iniciar o Kotatsu</string>\n    <string name=\"zoom_mode_fit_height\">Ajustar à altura</string>\n    <string name=\"black_dark_theme\">Preto</string>\n    <string name=\"black_dark_theme_summary\">Usa menos energia em telas AMOLED</string>\n    <string name=\"reader_mode_hint\">A configuração escolhida será lembrada para este mangá</string>\n    <string name=\"backup_information\">Pode criar um backup do seu histórico e favoritos e restaurá-lo</string>\n    <string name=\"clear_cookies\">Limpar cookies</string>\n    <string name=\"text_clear_search_history_prompt\">Remover todas as consultas de pesquisa recentes permanentemente\\?</string>\n    <string name=\"auth_required\">Faça login para ver este conteúdo</string>\n    <string name=\"manga_save_location\">Pasta de downloads</string>\n    <string name=\"exclude_nsfw_from_history\">Excluir mangá NSFW do histórico</string>\n    <string name=\"system_default\">Padrão</string>\n    <string name=\"computing_\">Computando…</string>\n    <string name=\"screenshots_allow\">Permitir</string>\n    <string name=\"screenshots_block_nsfw\">Bloquear no NSFW</string>\n    <string name=\"screenshots_policy\">Política de captura de ecrã</string>\n    <string name=\"screenshots_block_all\">Sempre bloquear</string>\n    <string name=\"suggestions_summary\">Sugira mangá com base nas suas preferências</string>\n    <string name=\"suggestions_info\">Todos os dados são analisados apenas localmente neste dispositivo e nunca são enviados para qualquer lugar.</string>\n    <string name=\"text_suggestion_holder\">Comece a ler mangá e receberá sugestões personalizadas</string>\n    <string name=\"suggestions\">Sugestões</string>\n    <string name=\"suggestions_enable\">Ativar sugestões</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Não sugira mangá NSFW</string>\n    <string name=\"enabled\">Habilitado</string>\n    <string name=\"disabled\">Desabilitado</string>\n    <string name=\"only_using_wifi\">Somente em Wi-Fi</string>\n    <string name=\"onboard_text\">Selecione os idiomas que deseja ler mangá. Pode alterá-lo mais tarde nas configurações.</string>\n    <string name=\"always\">Sempre</string>\n    <string name=\"reset_filter\">Redefinir filtro</string>\n    <string name=\"never\">Nunca</string>\n    <string name=\"preload_pages\">Pré-carregar páginas</string>\n    <string name=\"logged_in_as\">Conectado como %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Vários idiomas</string>\n    <string name=\"suggestions_updating\">Atualização das sugestões</string>\n    <string name=\"appearance\">Aparência</string>\n    <string name=\"search_chapters\">Encontrar capítulo</string>\n    <string name=\"chapters_empty\">Não há capítulos nesta manga</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"color_correction\">Correção de cor</string>\n    <string name=\"server_error\">Erro do servidor (%1$d). Por favor, tente novamente mais tarde</string>\n    <string name=\"clear_new_chapters_counters\">Apagar informações sobre novos capítulos</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"text_delete_local_manga_batch\">Excluir os itens selecionados do dispositivo permanentemente\\?</string>\n    <string name=\"compact\">Compactar</string>\n    <string name=\"bookmark_added\">Marcador adicionado</string>\n    <string name=\"prefetch_content\">Pré-carregamento de conteúdo</string>\n    <string name=\"invalid_domain_message\">Domínio inválido</string>\n    <string name=\"use_fingerprint\">Usar biometria se disponível</string>\n    <string name=\"appwidget_shelf_description\">Mangás favoritos</string>\n    <string name=\"appwidget_recent_description\">Mangás lidos recentemente</string>\n    <string name=\"suggestions_excluded_genres_summary\">Especifique os gêneros que você não deseja ver nas sugestões</string>\n    <string name=\"removal_completed\">Remoção concluída</string>\n    <string name=\"check_new_chapters_title\">Verifique se há novos capítulos e notifique se houver</string>\n    <string name=\"default_mode\">Modo padrão</string>\n    <string name=\"mark_as_current\">Marcar como atual</string>\n    <string name=\"error_no_space_left\">Não há espaço disponível no dispositivo</string>\n    <string name=\"network_unavailable\">A rede não está disponível</string>\n    <string name=\"network_unavailable_hint\">Ative o Wi-Fi ou a rede móvel para ler o mangá online</string>\n    <string name=\"name\">Nome</string>\n    <string name=\"logout\">Sair</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"edit_category\">Editar categoria</string>\n    <string name=\"tracking\">Monitoramento</string>\n    <string name=\"empty_favourite_categories\">Nenhuma categoria favorita</string>\n    <string name=\"removed_from_history\">Removido do histórico</string>\n    <string name=\"send\">Enviar</string>\n    <string name=\"suggestions_excluded_genres\">Excluir gêneros</string>\n    <string name=\"download_slowdown\">Download lento</string>\n    <string name=\"download_slowdown_summary\">Ajude a evitar o bloqueio do seu endereço IP</string>\n    <string name=\"local_manga_processing\">Processando mangá salvo</string>\n    <string name=\"chapters_will_removed_background\">Os capítulos serão removidos em segundo plano</string>\n    <string name=\"canceled\">Cancelado</string>\n    <string name=\"email_enter_hint\">Digite seu e-mail para continuar</string>\n    <string name=\"new_sources_text\">Novas fontes de mangá estão disponíveis</string>\n    <string name=\"show_notification_new_chapters_on\">Você receberá notificações sobre atualizações do mangá que está lendo</string>\n    <string name=\"notifications_enable\">Ativar notificações</string>\n    <string name=\"crash_text\">Algo deu errado. Por favor, envie um relatório de bug para os desenvolvedores para nos ajudar a corrigi-lo.</string>\n    <string name=\"status_planned\">Planejado</string>\n    <string name=\"status_reading\">Leitura</string>\n    <string name=\"status_re_reading\">Relendo</string>\n    <string name=\"status_completed\">Concluído</string>\n    <string name=\"status_on_hold\">Em espera</string>\n    <string name=\"show_reading_indicators\">Mostrar indicadores de progresso de leitura</string>\n    <string name=\"data_deletion\">Exclusão de dados</string>\n    <string name=\"clear_cookies_summary\">Pode ajudar em caso de alguns problemas. Todas as autorizações serão invalidadas</string>\n    <string name=\"show_all\">Mostrar tudo</string>\n    <string name=\"clear_all_history\">Apagar todo o histórico</string>\n    <string name=\"last_2_hours\">Últimas 2 horas</string>\n    <string name=\"categories_delete_confirm\">Tem certeza de que deseja excluir as categorias favoritas selecionadas? \\nTodos os mangás serão perdidos e isso não poderá ser desfeito.</string>\n    <string name=\"reorder\">Reorganizar</string>\n    <string name=\"empty\">Vazio</string>\n    <string name=\"explore\">Explorar</string>\n    <string name=\"comics_archive\">Arquivos de quadrinhos</string>\n    <string name=\"folder_with_images\">Pasta com imagens</string>\n    <string name=\"importing_manga\">Importando mangá</string>\n    <string name=\"saved_manga\">Mangás salvos</string>\n    <string name=\"history_shortcuts\">Mostrar atalhos de mangá recentes</string>\n    <string name=\"history_shortcuts_summary\">Disponibilizar mangás recentes pressionando por um curto período de tempo o ícone do aplicativo</string>\n    <string name=\"brightness\">Brilho</string>\n    <string name=\"contrast\">Contraste</string>\n    <string name=\"reset\">Redefinir</string>\n    <string name=\"text_unsaved_changes_prompt\">Salvar ou descartar alterações não salvas\\?</string>\n    <string name=\"select_range\">Selecionar intervalo</string>\n    <string name=\"reader_slider\">Mostrar controle de leitura deslizante</string>\n    <string name=\"source_disabled\">Fonte desativada</string>\n    <string name=\"account_already_exists\">Essa conta já existe</string>\n    <string name=\"back\">Voltar</string>\n    <string name=\"sync\">Sincronização</string>\n    <string name=\"sync_title\">Sincronizar os seus dados</string>\n    <string name=\"show_notification_new_chapters_off\">Você não receberá notificações, mas novos capítulos serão destacados nas listas</string>\n    <string name=\"bookmark_add\">Adicionar marcador</string>\n    <string name=\"bookmark_remove\">Remover marcador</string>\n    <string name=\"bookmarks\">Marcadores</string>\n    <string name=\"bookmark_removed\">Marcador removido</string>\n    <string name=\"undo\">Desfazer</string>\n    <string name=\"dns_over_https\">DNS sobre HTTPS</string>\n    <string name=\"detect_reader_mode\">Modo de leitor de detecção automática</string>\n    <string name=\"detect_reader_mode_summary\">Detectar automaticamente se o mangá é webtoon</string>\n    <string name=\"disable_battery_optimization\">Desativar otimização de bateria</string>\n    <string name=\"disable_battery_optimization_summary\">Ajuda com verificações de atualizações em segundo plano</string>\n    <string name=\"status_dropped\">Dropado</string>\n    <string name=\"disable_all\">Desativar tudo</string>\n    <string name=\"report\">Relatório</string>\n    <string name=\"show_reading_indicators_summary\">Mostrar porcentagem lida no histórico e favoritos</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Os mangás marcados como NSFW nunca serão adicionados ao histórico e o seu progresso não será guardado</string>\n    <string name=\"history_cleared\">Histórico deletado</string>\n    <string name=\"manage\">Gerenciar</string>\n    <string name=\"no_bookmarks_yet\">Ainda não há marcadores</string>\n    <string name=\"no_bookmarks_summary\">Você pode criar marcadores enquanto lê o mangá</string>\n    <string name=\"bookmarks_removed\">Marcadores removidos</string>\n    <string name=\"no_manga_sources\">Sem fontes de mangá</string>\n    <string name=\"no_manga_sources_text\">Habilitar fontes de mangá para ler o mangá online</string>\n    <string name=\"random\">Aleatório</string>\n    <string name=\"confirm_exit\">Pressione Voltar novamente para sair</string>\n    <string name=\"exit_confirmation_summary\">Pressione Voltar duas vezes para sair do aplicativo</string>\n    <string name=\"exit_confirmation\">Confirmação de saída</string>\n    <string name=\"pages_cache\">Cache de páginas</string>\n    <string name=\"other_cache\">Outros cache</string>\n    <string name=\"storage_usage\">Armazenamento usado</string>\n    <string name=\"available\">Disponível</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"removed_from_favourites\">Removido dos favoritos</string>\n    <string name=\"options\">Opções</string>\n    <string name=\"not_found_404\">Conteúdo não encontrado ou removido</string>\n    <string name=\"incognito_mode\">Modo de navegação anônima</string>\n    <string name=\"no_chapters\">Sem capítulos</string>\n    <string name=\"automatic_scroll\">Rolagem automática</string>\n    <string name=\"reader_info_pattern\">Cap. %1$d/%2$d Pág. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Mostrar barra de informações no leitor</string>\n    <string name=\"import_completed\">Importação concluída</string>\n    <string name=\"import_completed_hint\">Você pode excluir o arquivo original do armazenamento para economizar espaço</string>\n    <string name=\"import_will_start_soon\">A importação começará em breve</string>\n    <string name=\"feed\">Fluxo</string>\n    <string name=\"manga_error_description_pattern\">Detalhes do erro:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Tente &lt;a href=%2$s&gt;abra a página do mangá em um navegador da web&lt;/a&gt; para garantir que o mesmo esteja disponível em sua fonte&lt;br&gt;2. Se estiver disponível, envie um relatório de erro para os desenvolvedores.</string>\n    <string name=\"reader_control_ltr_summary\">Não ajustar a direção da mudança de página no modo de editor, por exemplo, pressionar a tecla direita muda sempre para a página seguinte. Esta opção só afeta os dispositivos de hardware de entrada</string>\n    <string name=\"reader_control_ltr\">Controle ergonômico do leitor</string>\n    <string name=\"discard\">Descartar</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"nothing_here\">Não há nada aqui</string>\n    <string name=\"services\">Serviços</string>\n    <string name=\"enable_logging_summary\">Gravar algumas ações para propósitos de depuração. Não ative se você não sabe o que está fazendo</string>\n    <string name=\"allow_unstable_updates\">Permitir atualizações instáveis</string>\n    <string name=\"download_started\">Descarga iniciada</string>\n    <string name=\"share_logs\">Compartilhar registos</string>\n    <string name=\"enable_logging\">Ativar registos</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"show_suspicious_content\">Exibir conteúdo suspeito</string>\n    <string name=\"theme_name_dynamic\">Dinâmico</string>\n    <string name=\"color_theme\">Esquema de cores</string>\n    <string name=\"show_in_grid_view\">Mostrar na visualização em grade</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"settings_apply_restart_required\">Por favor, reinicie o app para aplicar essas mudanças</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"scrobbling_empty_hint\">Para acompanhar o progresso da leitura, selecione Menu → Vá até a tela de detalhes do mangá.</string>\n    <string name=\"webtoon_zoom\">Zoom Webtoon</string>\n    <string name=\"allow_unstable_updates_summary\">Receber notificações sobre versões instáveis</string>\n    <string name=\"got_it\">Entendi</string>\n    <string name=\"sources_reorder_tip\">Toque e segure em um item para reordená-lo</string>\n    <string name=\"comics_archive_import_description\">Você pode selecionar um ou mais arquivos .cbz ou .zip, cada arquivo será reconhecido como um mangá separado.</string>\n    <string name=\"folder_with_images_import_description\">Você pode selecionar um diretório com arquivos ou imagens. Cada arquivo (ou subdiretório) será reconhecido como um capítulo.</string>\n    <string name=\"sync_host_description\">Você pode usar um servidor de sincronização auto-hospedado ou um padrão. Não mude isso se não tiver certeza do que está fazendo.</string>\n    <string name=\"address\">Endereço</string>\n    <string name=\"port\">Porta</string>\n    <string name=\"data_and_privacy\">Dados e privacidade</string>\n    <string name=\"invalid_port_number\">Número de porta inválido</string>\n    <string name=\"network\">Rede</string>\n    <string name=\"restore_summary\">Restaurar backup criado anteriormente</string>\n    <string name=\"webtoon_zoom_summary\">Permitir zoom no gesto no modo webtoon</string>\n    <string name=\"reader_info_bar_summary\">Mostrar a hora atual e o progresso da leitura na parte superior da tela</string>\n    <string name=\"show_on_shelf\">Mostrar na prateleira</string>\n    <string name=\"sync_auth_hint\">Você pode entrar em uma conta existente ou criar uma nova</string>\n    <string name=\"find_similar\">Encontrar semelhante</string>\n    <string name=\"sync_settings\">Configurações de sincronização</string>\n    <string name=\"server_address\">Endereço do servidor</string>\n    <string name=\"ignore_ssl_errors\">Ignorar erros de SSL</string>\n    <string name=\"mirror_switching\">Escolher espelho automaticamente</string>\n    <string name=\"mirror_switching_summary\">Troca automática de domínios para fontes de mangá em caso de erro, se houver espelhos disponíveis</string>\n    <string name=\"pause\">Pausa</string>\n    <string name=\"downloads_wifi_only\">Baixe apenas via Wi-Fi</string>\n    <string name=\"downloads_wifi_only_summary\">Interrompa o download ao mudar para uma rede móvel</string>\n    <string name=\"cancel_all_downloads_confirm\">Todos os downloads ativos serão cancelados, dados parcialmente baixados serão perdidos</string>\n    <string name=\"text_downloads_list_holder\">Você não tem nenhum download</string>\n    <string name=\"downloads_resumed\">Os downloads foram retomados</string>\n    <string name=\"downloads_paused\">Os downloads foram pausados</string>\n    <string name=\"downloads_removed\">Os downloads foram removidos</string>\n    <string name=\"downloads_cancelled\">Os downloads foram cancelados</string>\n    <string name=\"suggestions_enable_prompt\">Quer receber sugestões personalizadas de mangás\\?</string>\n    <string name=\"web_view_unavailable\">WebView não disponível: verifique se o provedor WebView está instalado</string>\n    <string name=\"clear_network_cache\">Limpar cache de rede</string>\n    <string name=\"invalid_value_message\">Valor inválido</string>\n    <string name=\"downloaded\">Baixado</string>\n    <string name=\"authorization_optional\">Autorização (opcional)</string>\n    <string name=\"show_pages_numbers_summary\">Mostrar números de página no canto inferior</string>\n    <string name=\"user_agent\">Cabeçalho UserAgent</string>\n    <string name=\"speed\">Velocidade</string>\n    <string name=\"images_proxy_title\">Proxy de otimização de imagens</string>\n    <string name=\"images_procy_description\">Use o serviço wsrv.nl para reduzir o uso de tráfego e acelerar o carregamento de imagens, se possível</string>\n    <string name=\"invert_colors\">Cores invertidas</string>\n    <string name=\"username\">Nome de usuário</string>\n    <string name=\"password\">Senha</string>\n    <string name=\"type\">Tipo</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"resume\">Retomar</string>\n    <string name=\"paused\">Pausado</string>\n    <string name=\"cancel_all\">Cancelar tudo</string>\n    <string name=\"suggestion_manga\">Sugestão: %s</string>\n    <string name=\"suggestions_notifications_summary\">Às vezes, mostra notificações com mangás sugeridos</string>\n    <string name=\"remove_completed_downloads_confirm\">Seu histórico de downloads será excluído permanentemente. Nenhum arquivo baixado será afetado</string>\n    <string name=\"more\">Mais</string>\n    <string name=\"enable\">Activar</string>\n    <string name=\"no_thanks\">Não obrigado</string>\n    <string name=\"remove_completed\">Remoção concluída</string>\n    <string name=\"download_option_all_unread\">Todos os capítulos não lidos</string>\n    <string name=\"clear_source_cookies_summary\">Limpa os cookies apenas para o domínio especificado. Na maioria dos casos, a autorização será invalidada</string>\n    <string name=\"download_option_manual_selection\">Selecionar os capítulos manualmente</string>\n    <string name=\"download_option_all_unread_b\">Todos os capítulos não lidos (%s)</string>\n    <string name=\"download_option_first_n_chapters\">Primeiro %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Próximo não lido %s</string>\n    <string name=\"download_option_all_chapters\">Todos os capítulos com tradução %s</string>\n    <string name=\"languages\">Línguas</string>\n    <string name=\"zoom_in\">Aumentar o zoom</string>\n    <string name=\"captcha_required_summary\">%s requer que um captcha seja resolvido para funcionar corretamente</string>\n    <string name=\"progress\">Progresso</string>\n    <string name=\"error_corrupted_file\">São devolvidos dados inválidos ou o ficheiro está corrompido</string>\n    <string name=\"pick_custom_directory\">Escolher diretório personalizado</string>\n    <string name=\"related_manga_summary\">Mostrar uma lista de mangas relacionadas. Em alguns casos, pode estar incorrecta ou em falta</string>\n    <string name=\"reader_zoom_buttons_summary\">Mostrar ou não os botões de controlo do zoom no canto inferior direito</string>\n    <string name=\"tracker_wifi_only_summary\">Não verificar a existência de novos capítulos utilizando ligações de rede com contador</string>\n    <string name=\"order_added\">Adicionado</string>\n    <string name=\"on_device\">No dispositivo</string>\n    <string name=\"download_option_whole_manga\">Todo o mangá</string>\n    <string name=\"moved_to_top\">Movido para o topo</string>\n    <string name=\"data_not_restored_text\">Certifique-se de que seleccionou o ficheiro de cópia de segurança correto</string>\n    <string name=\"unknown\">Desconhecido</string>\n    <string name=\"in_progress\">Em curso</string>\n    <string name=\"items_limit_exceeded\">Não podem ser adicionados mais itens</string>\n    <string name=\"data_not_restored\">Os dados não foram restaurados</string>\n    <string name=\"directories\">Directórios</string>\n    <string name=\"local_manga_directories\">Directórios locais de manga</string>\n    <string name=\"manage_categories\">Gerir categorias</string>\n    <string name=\"color_light\">Claro</string>\n    <string name=\"search_hint\">Introduzir o título da manga, o género ou o nome da fonte</string>\n    <string name=\"description\">Descrição</string>\n    <string name=\"reader_zoom_buttons\">Mostrar botões de zoom</string>\n    <string name=\"main_screen_sections\">Secções do ecrã principal</string>\n    <string name=\"advanced\">Avançado</string>\n    <string name=\"color_dark\">Escuro</string>\n    <string name=\"too_many_requests_message\">Demasiados pedidos. Tentar novamente mais tarde</string>\n    <string name=\"related_manga\">Mangá relacionado</string>\n    <string name=\"suggestions_wifi_only_summary\">Não atualizar sugestões utilizando ligações de rede com contador</string>\n    <string name=\"background\">Fundo</string>\n    <string name=\"no_access_to_file\">Não tem acesso a este ficheiro ou diretório</string>\n    <string name=\"zoom_out\">Reduzir o zoom</string>\n    <string name=\"voice_search\">Pesquisa por voz</string>\n    <string name=\"manga_list\">Lista de mangás</string>\n    <string name=\"disable_nsfw\">Desativar NSFW</string>\n    <string name=\"color_white\">Branco</string>\n    <string name=\"to_top\">Para cima</string>\n    <string name=\"show\">Mostrar</string>\n    <string name=\"color_black\">Preto</string>\n    <string name=\"this_month\">Este mês</string>\n    <string name=\"categories\">Categorias</string>\n    <string name=\"list_options\">Listar opções</string>\n    <string name=\"suggest_new_sources\">Sugira novas fontes após atualização do app</string>\n    <string name=\"enhanced_colors_summary\">Reduz faixas, mas pode afetar o desempenho</string>\n    <string name=\"online_variant\">Variante online</string>\n    <string name=\"by_relevance\">Relevância</string>\n    <string name=\"enhanced_colors\">Modo de cor 32-bit</string>\n    <string name=\"suggest_new_sources_summary\">Solicitar a ativação de fontes recém-adicionadas após atualizar o aplicativo</string>\n    <string name=\"state_abandoned\">Caiu</string>\n    <string name=\"keep_screen_on\">Manter a tela ligada</string>\n    <string name=\"keep_screen_on_summary\">Não desligue a tela enquanto estiver lendo mangá</string>\n    <string name=\"sources_catalog\">Catálogo de fontes</string>\n    <string name=\"frequency_every_day\">Diariamente</string>\n    <string name=\"content_type_manga\">Mangá</string>\n    <string name=\"error_multiple_states_not_supported\">A filtragem por múltiplos estados não é suportada por esta fonte</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"backup_frequency\">Frequência de criação de backup</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"error_filter_states_genre_not_supported\">A filtragem por gênero e estado não é suportado por esta fonte</string>\n    <string name=\"error_filter_locale_genre_not_supported\">A filtragem por gênero e localidade não é suportada por esta fonte</string>\n    <string name=\"periodic_backups_enable\">Ativar backups periódicos</string>\n    <string name=\"content_type_comics\">Quadrinhos</string>\n    <string name=\"catalog\">Catálogo</string>\n    <string name=\"welcome_text\">Por favor, selecione quais fontes de conteúdo gostaria de ativar. Isso também pode ser configurado posteriormente nas configurações</string>\n    <string name=\"frequency_every_2_days\">A cada 2 dias</string>\n    <string name=\"reader_optimize\">Reduzir o consumo de memória (beta)</string>\n    <string name=\"apply\">Aplicar</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"manage_sources\">Gerir fontes</string>\n    <string name=\"no_manga_sources_found\">Nenhuma fonte de mangá disponível encontrada da sua pesquisa</string>\n    <string name=\"genres_search_hint\">Comece a digitar o nome do gênero</string>\n    <string name=\"frequency_once_per_week\">Semanalmente</string>\n    <string name=\"periodic_backups\">Backups periódicos</string>\n    <string name=\"globally\">Globalmente</string>\n    <string name=\"frequency_twice_per_month\">Duas vezes ao mês</string>\n    <string name=\"downloads_settings_info\">Pode ativar a desaceleração de descargas para cada fonte de mangá individualmente nas configurações da fonte, se enfrentar problemas com bloqueios do lado do servidor</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Pode ajudar a iniciar a descarga caso tenha algum problema com isso</string>\n    <string name=\"error_multiple_genres_not_supported\">A filtragem por múltiplos gêneros não é suportada por esta fonte</string>\n    <string name=\"this_manga\">Este mangá</string>\n    <string name=\"lock_screen_rotation\">Travar rotação de ecrã</string>\n    <string name=\"skip\">Pular</string>\n    <string name=\"error_search_not_supported\">A busca não é suportada por esta fonte</string>\n    <string name=\"frequency_once_per_month\">Uma vez por mês</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"reader_optimize_summary\">Reduzir a qualidade das páginas fora do ecrã para usar menos memória</string>\n    <string name=\"color_correction_apply_text\">Essas configurações podem ser aplicadas globalmente ou apenas ao mangá atual. Se aplicadas globalmente, as configurações individuais não serão substituídas.</string>\n    <string name=\"source_enabled\">Fonte ativada</string>\n    <string name=\"disable_nsfw_summary\">Desative as fontes NSFW e oculte os mangás adultos da lista, se possível</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"backup_date_\">Data de backup: %s</string>\n    <string name=\"no_manga_sources_catalog_text\">Não há fontes disponíveis nesta secção, ou todas elas podem já ter sido adicionadas.\n\\nFique atento</string>\n    <string name=\"available_d\">Disponível: %1$d</string>\n    <string name=\"state\">Estado</string>\n    <string name=\"grayscale\">Tons de Cinzento</string>\n    <string name=\"last_successful_backup\">Último backup bem-sucedido: %s</string>\n    <string name=\"state_paused\">Pausado</string>\n    <string name=\"backups_output_directory\">Local de saída de backups</string>\n    <string name=\"content_type_other\">Outros</string>\n    <string name=\"sync_auth\">Faça login para sincronizar a conta</string>\n    <string name=\"state_upcoming\">Próximo</string>\n    <string name=\"content_rating\">Avaliação do conteúdo</string>\n    <string name=\"genres_exclude\">Excluir gêneros</string>\n    <string name=\"rating_safe\">Seguro</string>\n    <string name=\"rating_suggestive\">Sugestivo</string>\n    <string name=\"rating_adult\">Adulto</string>\n    <string name=\"default_tab\">Guia padrão</string>\n    <string name=\"by_name_reverse\">Nome invertido</string>\n    <string name=\"volume_\">Volume %d</string>\n    <string name=\"volume_unknown\">Volume desconhecido</string>\n    <string name=\"incognito_mode_hint\">Seu progresso de leitura não será salvo</string>\n    <string name=\"vertical\">Vertical</string>\n    <string name=\"email_password_enter_hint\">Coloque seu email e senha para continuar</string>\n    <string name=\"mark_as_completed_prompt\">Marcar o mangá selecionado como lido completamente?\n\\n\n\\nAviso: o progresso da leitura atual será perdido.</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"show_menu\">Mostrar menu</string>\n    <string name=\"toggle_ui\">Mostrar/ocultar UI</string>\n    <string name=\"prev_chapter\">Capítulo anterior</string>\n    <string name=\"next_chapter\">Próximo capítulo</string>\n    <string name=\"prev_page\">Página anterior</string>\n    <string name=\"next_page\">Próxima página</string>\n    <string name=\"reader_actions\">Ações do leitor</string>\n    <string name=\"reader_actions_summary\">Configurar ações para áreas de tela ajustáveis</string>\n    <string name=\"switch_pages_volume_buttons\">Ativar botões de volume</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Usar botões de volume para alternar páginas</string>\n    <string name=\"tap_action\">Ação de toque</string>\n    <string name=\"long_tap_action\">Ação de toque longo</string>\n    <string name=\"config_reset_confirm\">Redefinir as configurações para valores padrões? Essa ação não pode ser desfeita.</string>\n    <string name=\"use_two_pages_landscape\">Usar layout de duas páginas na orientação paisagem (beta)</string>\n    <string name=\"category_hidden_done\">Essa categoria foi ocultada da tela principal e pode ser acessada através do Menu → Gerenciar categorias</string>\n    <string name=\"mark_as_completed\">Marcar como concluído</string>\n    <string name=\"none\">nenhum</string>\n    <string name=\"last_read\">Última leitura</string>\n    <string name=\"default_webtoon_zoom_out\">Diminuir para zoom padrão do webtoon</string>\n    <string name=\"fullscreen_mode\">Modo de tela cheia</string>\n    <string name=\"reader_fullscreen_summary\">Ocultar a barra de status e navegação</string>\n    <string name=\"suggestions_unavailable_text\">O recurso de sugestões está desativado</string>\n    <string name=\"check_for_new_chapters_disabled\">A verificação de novos capítulos está desativada</string>\n    <string name=\"reading_time_estimation\">Mostrar tempo estimado de leitura</string>\n    <string name=\"reading_time_estimation_summary\">O valor do tempo estimado pode ser impreciso</string>\n    <string name=\"remove_from_history\">Remover do histórico</string>\n    <string name=\"show_labels_in_navbar\">Mostrar rótulos na barra de navegação</string>\n    <string name=\"default_page_save_dir\">Diretório de salvamento de página padrão</string>\n    <string name=\"location\">Localização</string>\n    <string name=\"pages_saving\">Salvando páginas</string>\n    <string name=\"ask_for_dest_dir_every_time\">Sempre pedir diretório de destinação</string>\n    <string name=\"automatic\">Automatico</string>\n    <string name=\"single_cbz_file\">Ficheiro CBZ único</string>\n    <string name=\"preferred_download_format\">Formato de download preferida</string>\n    <string name=\"multiple_cbz_files\">Múltiplos ficheiros CBZ</string>\n    <string name=\"chapters_grid_view\">visualizar em grade</string>\n    <string name=\"unsupported_backup_message\">Selecione um arquivo de backup do Kotatsu</string>\n    <string name=\"reading_stats\">Estatísticas de leitura</string>\n    <string name=\"other_manga\">Outros mangás</string>\n    <string name=\"statistics\">Estatística</string>\n    <string name=\"stats_cleared\">Estatísticas limpas</string>\n    <string name=\"clear_stats\">Limpar estatísticas</string>\n    <string name=\"clear_stats_confirm\">Você realmente quer limpar todas as estatísticas de leitura? Essa ação não pode ser desfeita.</string>\n    <string name=\"week\">Semana</string>\n    <string name=\"month\">Mês</string>\n    <string name=\"all_time\">Todo tempo</string>\n    <string name=\"alternatives\">Alternativos</string>\n    <string name=\"migrate\">Migrar</string>\n    <string name=\"manga_migration\">Migrar mangá</string>\n    <string name=\"migration_completed\">Migração completa</string>\n    <string name=\"delete_read_chapters\">Apagar todos os capítulos lidos</string>\n    <string name=\"no_chapters_deleted\">Nenhum capítulo foi apagado</string>\n    <string name=\"delete_read_chapters_auto\">Excluir capítulos lidos automaticamente</string>\n    <string name=\"runs_on_app_start\">É executado quando o aplicativo é iniciado</string>\n    <string name=\"three_months\">Três meses</string>\n    <string name=\"day\">Dia</string>\n    <string name=\"empty_stats_text\">Não há estatísticas para o período selecionado</string>\n    <string name=\"pages_read_s\">Páginas lidas: %s</string>\n    <string name=\"show_pages_thumbs\">Mostrar miniaturas de páginas</string>\n    <string name=\"show_pages_thumbs_summary\">Ative a aba \\\"Páginas\\\" na tela de detalhes</string>\n    <string name=\"less_than_minute\">Menos de um minuto</string>\n    <string name=\"migrate_confirmation\">O mangá \\\"%1$s\\\" de \\\"%2$s\\\" será substituído por \\\"%3$s\\\" de \\\"%4$s\\\" em seu histórico e favoritos (se houver)</string>\n    <string name=\"chapters_deleted_pattern\">%1$s removido, %2$s limpo</string>\n    <string name=\"delete_read_chapters_summary\">Excluir capítulos lidos do armazenamento local para liberar espaço</string>\n    <string name=\"missing_storage_permission\">Sem permissão para acessar mangás no armazenamento externo</string>\n    <string name=\"split_by_translations\">Dividido por traduções</string>\n    <string name=\"split_by_translations_summary\">Mostrar capítulos com traduções diferentes separadamente, em vez de em uma lista</string>\n    <string name=\"order_oldest\">Dos mais antigos</string>\n    <string name=\"long_ago_read\">Lidos há bastante tempo</string>\n    <string name=\"unread\">Não lidos</string>\n    <string name=\"fix\">Fixar</string>\n    <string name=\"error_no_data_received\">Nenhum dado foi recebido do servidor</string>\n    <string name=\"enable_source\">Ativar fonte</string>\n    <string name=\"unsupported_source\">Esta fonte de mangá não é suportada</string>\n    <string name=\"delete_read_chapters_prompt\">Isso irá apagar permanentemente todos os capítulos marcados como lidos do armazenamento local. Você pode baixá-los novamente mais tarde, mas os capítulos importados podem ser perdidos para sempre</string>\n    <string name=\"last_used\">Usado por último</string>\n    <string name=\"show_updated\">Mostrar atualização</string>\n    <string name=\"webtoon_gaps\">Lacunas no modo webtoon</string>\n    <string name=\"authors\">Autores</string>\n    <string name=\"disable\">Desativar</string>\n    <string name=\"sources_disabled\">Fontes desativadas</string>\n    <string name=\"webtoon_gaps_summary\">Mostrar espaços verticais entre páginas no modo webtoon</string>\n    <string name=\"recent_queries\">Consultas recentes</string>\n    <string name=\"search_suggestions\">Sugestões de pesquisa</string>\n    <string name=\"suggested_queries\">Consultas sugeridas</string>\n    <string name=\"blocked_by_server_message\">Você está bloqueado pelo servidor. Tente usar uma conexão de rede diferente (VPN, Proxy, etc.)</string>\n    <string name=\"less_frequently\">Menos frequência</string>\n    <string name=\"more_frequently\">Mais frequência</string>\n    <string name=\"frequency_of_check\">Frequência de verificação</string>\n    <string name=\"pin_navigation_ui\">Fixar interface de navegação</string>\n    <string name=\"pin_navigation_ui_summary\">Ao rolar, não esconda a barra de navegação e a visualização de busca</string>\n    <string name=\"hours_short\">%d hr</string>\n    <string name=\"minutes_short\">%d min</string>\n    <string name=\"all_languages\">Todos os idiomas</string>\n    <string name=\"screenshots_block_incognito\">Bloquear quando em modo anônimo</string>\n    <string name=\"percent_read\">Percentual lido</string>\n    <string name=\"percent_left\">Percentual restante</string>\n    <string name=\"chapters_read\">Capítulos lidos</string>\n    <string name=\"chapters_left\">Capítulos restantes</string>\n    <string name=\"image_server\">Servidor de imagens preferencial</string>\n    <string name=\"crop_pages\">Recortar páginas</string>\n    <string name=\"pin\">Fixar</string>\n    <string name=\"source_pinned\">Fonte fixada</string>\n    <string name=\"source_unpinned\">Fonte desfixada</string>\n    <string name=\"sources_unpinned\">Fontes desfixadas</string>\n    <string name=\"sources_pinned\">Fontes fixadas</string>\n    <string name=\"disable_connectivity_check\">Desativar verificação de conectividade</string>\n    <string name=\"external_source\">Externo/plugin</string>\n    <string name=\"disable_nsfw_notifications\">Desativar notificações NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">Não mostrar notificações sobre atualizações de mangás NSFW</string>\n    <string name=\"tracker_debug_info\">Verificação de log de novos capítulos</string>\n    <string name=\"tracker_debug_info_summary\">Informações de depuração sobre verificações em segundo plano para novos capítulos</string>\n    <string name=\"_new\">Novo</string>\n    <string name=\"unpin\">Desafixar</string>\n    <string name=\"recent_sources\">Fontes recentes</string>\n    <string name=\"hours_minutes_short\">%1$d hrs %2$d min</string>\n    <string name=\"ignore_ssl_errors_summary\">Você pode desativar a verificação de certificados SSL caso enfrente problemas relacionados ao SSL ao acessar recursos de rede. Isso pode afetar sua segurança. É necessário reiniciar o aplicativo após alterar essa configuração.</string>\n    <string name=\"disable_connectivity_check_summary\">Pule a verificação de conectividade caso tenha problemas com ela (por exemplo, entrar no modo offline mesmo estando conectado à rede)</string>\n    <string name=\"plugin_incompatible\">Plugin incompatível ou erro interno. Certifique-se de que está usando a versão mais recente do plugin e do Kotatsu</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Não há mangás que correspondam aos filtros selecionados</string>\n    <string name=\"connection_ok\">A conexão está OK</string>\n    <string name=\"invalid_proxy_configuration\">Configuração de proxy inválida</string>\n    <string name=\"scrobbler_auth_required\">Entre em %s para continuar</string>\n    <string name=\"scrobbler_auth_intro\">Entre para configurar a integração com %s. Isso permitirá que você acompanhe o progresso e o status da sua leitura</string>\n    <string name=\"unstable_feature\">Característica instável</string>\n    <string name=\"unstable_feature_summary\">Esta função é experimental. Certifique-se de ter um backup para evitar perda de dados</string>\n    <string name=\"content_type_novel\">Romance</string>\n    <string name=\"recently_added\">Adicionado recentemente</string>\n    <string name=\"added_long_ago\">Adicionado há muito tempo</string>\n    <string name=\"popular_today\">Popular hoje</string>\n    <string name=\"popular_in_week\">Popular essa semana</string>\n    <string name=\"popular_in_month\">Popular esse mês</string>\n    <string name=\"popular_in_year\">Popular esse ano</string>\n    <string name=\"popular_in_hour\">Popular neste momento</string>\n    <string name=\"original_language\">Idioma original</string>\n    <string name=\"year\">Ano</string>\n    <string name=\"demographics\">Demografia</string>\n    <string name=\"years\">Anos</string>\n    <string name=\"any\">Qualquer</string>\n    <string name=\"invalid_server_address_message\">Endereço de Servidor Inválido</string>\n    <string name=\"too_many_requests_message_retry\">Muitas solicitações. Tente novamente depois %s</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"show_quick_filters\">Mostrar filtros rápidos</string>\n    <string name=\"skip_all\">Pular tudo</string>\n    <string name=\"not_in_favorites\">Não está nos favoritos</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"show_quick_filters_summary\">Oferece a capacidade de filtrar listas de mangás por determinados parâmetros</string>\n    <string name=\"unpopular\">Impopular</string>\n    <string name=\"sort_order_desc\">Descendente</string>\n    <string name=\"filter_search_warning\">Esta fonte não suporta pesquisa com filtros. Seus filtros foram limpos</string>\n    <string name=\"downloads_background\">Baixar em segundo plano</string>\n    <string name=\"download_new_chapters\">Baixar novos capítulos</string>\n    <string name=\"manga_with_downloaded_chapters\">Mangá com capítulos baixados</string>\n    <string name=\"fixing_manga\">Corrigindo mangá</string>\n    <string name=\"fixed\">Corrigido com sucesso</string>\n    <string name=\"no_fix_required\">Nenhuma correção necessária para \\\"%s\\\"</string>\n    <string name=\"no_alternatives_found\">Nenhuma alternativa encontrada para \\\"%s\\\"</string>\n    <string name=\"manga_fix_prompt\">Esta função encontrará fontes alternativas para o mangá selecionado. A tarefa levará algum tempo e prosseguirá em segundo plano</string>\n    <string name=\"manga_replaced\">Mangá \\\"%1$s\\\" (%2$s) substituído por \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"retry\">Tentar Novamente</string>\n    <string name=\"stuck\">Preso</string>\n    <string name=\"updated_long_ago\">Atualizado há muito tempo</string>\n    <string name=\"low_rating\">Classificação baixa</string>\n    <string name=\"sort_order_asc\">Ascendente</string>\n    <string name=\"by_date\">Data</string>\n    <string name=\"popularity\">Popularidade</string>\n    <string name=\"content_type_artist_cg\">Artista CG</string>\n    <string name=\"debug\">Depurar</string>\n    <string name=\"source_code\">Código fonte</string>\n    <string name=\"user_manual\">Manual do usuário</string>\n    <string name=\"telegram_group\">Grupo Telegram</string>\n    <string name=\"content_type_image_set\">Conjunto de imagens</string>\n    <string name=\"content_type_game_cg\">Jogo CG</string>\n    <string name=\"sfw\">sfw</string>\n    <string name=\"download_over_cellular\">Baixando pela rede celular</string>\n    <string name=\"error_image_format\">Formato de imagem não suportado: %s</string>\n    <string name=\"start_download\">Iniciar download</string>\n    <string name=\"save_manga_confirm\">Salvar mangá selecionado? Isso pode consumir tráfego e espaço em disco</string>\n    <string name=\"save_manga\">Salvar mangá</string>\n    <string name=\"genre\">Gênero</string>\n    <string name=\"download_added\">Download adicionado</string>\n    <string name=\"more_options\">Mais opções</string>\n    <string name=\"destination_directory\">Diretório de destino</string>\n    <string name=\"chapter_selection_hint\">Você pode selecionar capítulos para baixar clicando longamente no item na lista de capítulos.</string>\n    <string name=\"chapters_all\">Todos</string>\n    <string name=\"download_cellular_confirm\">Permitir downloads pela rede celular?</string>\n    <string name=\"dont_allow\">Não permitido</string>\n    <string name=\"allow_always\">Permitir sempre</string>\n    <string name=\"allow_once\">Permitir uma vez</string>\n    <string name=\"ask_every_time\">Perguntar sempre</string>\n    <string name=\"show_slider\">Mostrar slider</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"plugin_incompatible_with_cause\">Erro de plug-in: %s\\nCertifique-se de estar usando a versão mais recente do plugin e do Kotatsu</string>\n    <string name=\"screen_orientation\">Orientação da tela</string>\n    <string name=\"error_connection_reset\">Conexão redefinida pelo host remoto</string>\n    <string name=\"incognito\">Incógnita</string>\n    <string name=\"content_type_one_shot\">Um tiro</string>\n    <string name=\"demographic_kodomo\">Criança</string>\n    <string name=\"backup_tg_check\">Verifique se a API funciona</string>\n    <string name=\"backup_tg_echo\">Mensagem de teste</string>\n    <string name=\"backup_tg_id_not_set\">O ID do bate-papo não está definido</string>\n    <string name=\"telegram_chat_id\">ID do bate-papo do Telegram</string>\n    <string name=\"open_telegram_bot\">Abra o bot do Telegram</string>\n    <string name=\"pages_saved\">Páginas salvas</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Jovem</string>\n    <string name=\"demographic_josei\">Mulher</string>\n    <string name=\"portrait\">Retrato</string>\n    <string name=\"landscape\">Paisagem</string>\n    <string name=\"access_denied_403\">Acesso negado (403)</string>\n    <string name=\"content_type_doujinshi\">Fanzine</string>\n    <string name=\"captcha_required_message\">Esta fonte requer a resolução de um captcha para continuar</string>\n    <string name=\"handle_links\">Lidar com links</string>\n    <string name=\"handle_links_summary\">Lidar com links de mangá de aplicativos externos (por exemplo, navegador da web). Você também pode precisar habilitá-lo manualmente nas configurações do sistema do aplicativo</string>\n    <string name=\"email\">Email</string>\n    <string name=\"error_not_image\">Formato inválido: imagem esperada, mas obteve %s</string>\n    <string name=\"max_backups_count\">Número máximo de backups</string>\n    <string name=\"send_backups_telegram\">Envie backups no Telegram</string>\n    <string name=\"test_connection\">Conexão de teste</string>\n    <string name=\"telegram_chat_id_summary\">Insira o ID do chat para onde os backups devem ser enviados</string>\n    <string name=\"open_telegram_bot_summary\">Pressione para abrir o bate-papo com o Kotatsu Backup Bot</string>\n    <string name=\"delete_old_backups\">Exclua backups antigos</string>\n    <string name=\"delete_old_backups_summary\">Exclua automaticamente arquivos de backup antigos para economizar espaço de armazenamento</string>\n    <string name=\"translation\">Tradução</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"rating\">Avaliação</string>\n    <string name=\"source\">Fonte</string>\n    <string name=\"clear_database\">Limpar banco de dados</string>\n    <string name=\"clear_database_summary\">Excluir informações sobre mangás que não são utilizadas</string>\n    <string name=\"enable_all_sources\">Ativar todas as fontes de manga</string>\n    <string name=\"enable_all_sources_summary\">Todas as fontes de manga seram ativadas permanentemente</string>\n    <string name=\"all_sources_enabled\">Todas as fontes estão ativas</string>\n    <string name=\"reader_info_bar_transparent\">Barra de informações do leitor transparente</string>\n    <string name=\"backup_restored_background\">O backup será restaurado em segundo plano</string>\n    <string name=\"restoring_backup\">Restaurando backup</string>\n    <string name=\"reader_controls_in_bottom_bar\">Controles do leitor na barra inferior</string>\n    <string name=\"chapters_and_pages\">Capítulos e páginas</string>\n    <string name=\"screen_rotation_locked\">A rotação da tela foi bloqueada</string>\n    <string name=\"screen_rotation_unlocked\">A rotação da tela foi desbloqueada</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"badges_in_lists\">Emblemas em listas</string>\n    <string name=\"link_to_manga_on_s\">Link para o mangá no %s</string>\n    <string name=\"error_disclaimer_report\">Podes submeter uma denúncia de erro para os desenvolvedores. Isto ajuda–nos a investigar e arranjar o problema.</string>\n    <string name=\"search_disabled_sources\">Pesquisar fontes desabilitadas</string>\n    <string name=\"error_details\">Erro de detalhes</string>\n    <string name=\"error_disclaimer_manga\">Tenta abrir o mangá num web browser para verificar se está disponível na sua fonte.</string>\n    <string name=\"link_to_manga_in_app\">Link para o mangá no Kotatsu</string>\n    <string name=\"clear_browser_data\">Limpar informações do browser</string>\n    <string name=\"clear_browser_data_summary\">Limpar informações do browser como cache e cookies. Aviso: Autorizações em fontes de mangá podem ficar inválidas</string>\n    <string name=\"no_write_permission_to_file\">Não há permissão para modificar um ficheiro</string>\n    <string name=\"include_disabled_sources\">Incluir fontes desabilitadas</string>\n    <string name=\"suggestions_disabled_sources_summary\">Mostrar sugestões de todas as fontes de mangá, incluindo as desabilitadas</string>\n    <string name=\"tags_warnings\">Destacar géneros perigosos</string>\n    <string name=\"tags_warnings_summary\">Destacar géneros que podem ser inapropriados para a maioria dos utilizadores</string>\n    <string name=\"manga_override_hint\">Estás mudanças vão afetar como mangá é apresentado na aplicação</string>\n    <string name=\"use_default_cover\">Usar capa padrão</string>\n    <string name=\"pick_manga_page\">Escolher página do mangá</string>\n    <string name=\"pick_custom_file\">Escolher ficheiro</string>\n    <string name=\"incognito_mode_hint_nsfw\">Este mangá pode conter conteúdo adulto. Queres usar modo anónimo?</string>\n    <string name=\"incognito_for_nsfw\">Modo anónimo para mangá adulto</string>\n    <string name=\"change_cover\">Mudar capa</string>\n    <string name=\"dont_ask_again\">Não perguntar de novo</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Capítulo %2$s</string>\n    <string name=\"unnamed_chapter\">Capítulo sem nome</string>\n    <string name=\"chapter_number\">Capítulo %s</string>\n    <string name=\"additional_action_required\">Ações adicionais são necessárias</string>\n    <string name=\"theme_name_expressive\">Expressivo (Teste)</string>\n    <string name=\"simple\">Simples</string>\n    <string name=\"hide_from_main_screen\">Esconder do ecrã principal</string>\n    <string name=\"changelog\">Registo de mudanças</string>\n    <string name=\"changelog_summary\">Histórico de mudanças para versões recentes</string>\n    <string name=\"disable_captcha_notifications\">Desabilitar notificações de captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Vais parar de receber notificações sobre resolver CAPTCHAs desta fonte, mas isto pode levar a estragar operações do fundo (verificar a existência de novos capítulos, obter recomendações, etc)</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Mangá adulto não aparecerá nas recomendações. Esta opção pode não funcionar corretamente com algumas fontes</string>\n    <string name=\"error_disclaimer_app_outdated\">Parece que a tua versão do kotatsu está desatualizada. Por favor instala a versão mais recente para obter os arranjos mais recentes.</string>\n    <string name=\"pages_slider\">Controlo deslizante para mudança de página</string>\n    <string name=\"global_search\">Pesquisa Global</string>\n    <string name=\"search_everywhere\">Pesquisar em todos</string>\n    <string name=\"page_switch_timer\">A página vai mudar cada ~%d segundos</string>\n    <string name=\"error_non_file_uri\">O caminho escolhido não pode ser usado porque não indica um ficheiro ou diretório</string>\n    <string name=\"collapse\">Recolher</string>\n    <string name=\"expand\">Expandir</string>\n    <string name=\"adblock\">Bloquear anúncios no navegador</string>\n    <string name=\"adblock_summary\">Bloquear anúncios no navegador integrado (beta)</string>\n    <string name=\"collapse_long_description\">Recolher descrição longa</string>\n    <string name=\"creating_backup\">Criando backup</string>\n    <string name=\"share_backup\">Compartilhar backup</string>\n    <string name=\"reader_multitask\">Abra o leitor em uma tarefa separada</string>\n    <string name=\"reader_multitask_summary\">Permite que você mantenha vários leitores com diferentes mangás abertos ao mesmo tempo</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"reader_navigation_inverted\">https://hosted.weblate.org/translate/kotatsu/strings/pt/?checksum=b84ea9f18b9b4dde</string>\n    <string name=\"reader_navigation_inverted_summary\">https://hosted.weblate.org/translate/kotatsu/strings/pt/?checksum=b84ea9f18b9b4dde</string>\n    <string name=\"book_effect\">Fundo amarelaso (filtro de luz azul)</string>\n    <string name=\"local_storage_cleanup\">Limpeza do armazenamento local</string>\n    <string name=\"packup_creation_failed\">Falha ao criar backup</string>\n    <string name=\"main_screen\">Ecrã principal</string>\n    <string name=\"main_screen_fab\">Mostrar botão flutuante \\\"Continuar\\\"</string>\n    <string name=\"discord_token\">Código do Discord</string>\n    <string name=\"main_screen_fab_summary\">Permite que continue lendo em um clique. Esse botão não irá aparecer no modo incógnito or quando o histórico estiver vazio</string>\n    <string name=\"error_corrupted_zip\">Arquivo ZIP corrompido (%s)</string>\n    <string name=\"discord_rpc\">Rich Presence do Discord</string>\n    <string name=\"discord_token_summary\">Entre seu Token do Discord para habilitar Rich Presence</string>\n    <string name=\"discord_token_description\">Entre seu Token do Discord ou clique %s para obtê-lo pelo navegador</string>\n    <string name=\"discord_token_hint\">Cole seu Token do Discord aqui</string>\n    <string name=\"discord_rpc_summary\">Mostrar seu estado de leitura no Discord</string>\n    <string name=\"obtain\">Obter</string>\n    <string name=\"discord_rpc_description\">Lendo manga no Kotatsu - um app leitor de manga</string>\n    <string name=\"reading_s\">Lendo %s</string>\n    <string name=\"read_on_s\">Lendo em %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Não use RPC para conteúdo adulto</string>\n    <string name=\"invalid_token\">Token inválido: %s</string>\n    <string name=\"show_floating_control_button\">Mostrar botão de controle flutuante</string>\n    <string name=\"unavailable\">Indisponível</string>\n    <string name=\"manga_restricted_description\">Esse manga não está disponível para leitura nessa fonte. Tente pesquisá-lo em outras fontes ou abra-o no navegador para mais informação</string>\n    <string name=\"no_chapters_in_manga\">Esse manga não contém capítulos</string>\n    <string name=\"chapters_load_failed\">Falha ao carregar lista de capítulos</string>\n    <string name=\"telegram_integration\">Integração Telegram</string>\n    <string name=\"saved_filters\">Filtros salvos</string>\n    <string name=\"enter_name\">Digite o nome</string>\n    <string name=\"pull_to_prev_chapter\">Solte para abrir o capítulo anterior</string>\n    <string name=\"pull_to_next_chapter\">Solte para abrir o próximo capítulo</string>\n    <string name=\"pull_top_no_prev\">Nenhum capítulo anterior</string>\n    <string name=\"pull_bottom_no_next\">Sem mais capítulos</string>\n    <string name=\"two_page_scroll_sensitivity\">Sensibilidade de rolagem de página dupla</string>\n    <string name=\"enable_pull_gesture_title\">Ativar gesto de puxar</string>\n    <string name=\"enable_pull_gesture_summary\">Use o gesto de puxar para mudar de capítulo na webtoon</string>\n    <string name=\"reader_chapter_toast\">Mostrar pop-up de mudança de capítulo</string>\n    <string name=\"reader_chapter_toast_summary\">Mostrar uma mensagem pop-up com o título do capítulo quando ele for alterado</string>\n    <string name=\"test_parser\">Teste fonte do mangá</string>\n    <string name=\"rename\">Renomear</string>\n    <string name=\"save_filter\">Salvar filtro</string>\n    <string name=\"overwrite\">Sobrescrever</string>\n    <string name=\"filter_overwrite_confirm\">Já existe um filtro chamado \\\"%s\\\". Você quer sobrescrevê-lo?</string>\n    <string name=\"storage_and_network\">Armazenamento e internet</string>\n    <string name=\"create_or_restore_backup\">Criar e restaurar backup</string>\n    <string name=\"data_removal\">Remover dados</string>\n    <string name=\"privacy\">Privacidade</string>\n    <string name=\"source_broken_warning\">Esta fonte de mangá foi marcada como quebrada. Alguns recursos podem não funcionar</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d item</item>\n        <item quantity=\"many\">%1$d itens</item>\n        <item quantity=\"other\">%1$d itens</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d capítulo</item>\n        <item quantity=\"many\">%1$d capítulos</item>\n        <item quantity=\"other\">%1$d capítulos</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d capítulo novo</item>\n        <item quantity=\"many\">%1$d capítulos novos</item>\n        <item quantity=\"other\">%1$d capítulos novos</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minuto atrás</item>\n        <item quantity=\"many\">%1$d minutos atrás</item>\n        <item quantity=\"other\">%1$d minutos atrás</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d hora atrás</item>\n        <item quantity=\"many\">%1$d horas atrás</item>\n        <item quantity=\"other\">%1$d horas atrás</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d dia atrás</item>\n        <item quantity=\"many\">%1$d dias atrás</item>\n        <item quantity=\"other\">%1$d dias atrás</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d mês atrás</item>\n        <item quantity=\"many\">%1$d meses atrás</item>\n        <item quantity=\"other\">%1$d meses atrás</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d hora</item>\n        <item quantity=\"many\">%1$d horas</item>\n        <item quantity=\"other\">%1$d horas</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minuto</item>\n        <item quantity=\"many\">%1$d minutos</item>\n        <item quantity=\"other\">%1$d minutos</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"read\">Ler</string>\n    <string name=\"you_have_not_favourites_yet\">Você ainda não marcou alguma obra como favorita</string>\n    <string name=\"local_storage\">Armazenamento local</string>\n    <string name=\"favourites\">Favoritos</string>\n    <string name=\"history\">Histórico</string>\n    <string name=\"error_occurred\">Ocorreu um erro</string>\n    <string name=\"network_error\">Erro de rede</string>\n    <string name=\"details\">Detalhes</string>\n    <string name=\"chapters\">Capítulos</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"detailed_list\">Lista detalhada</string>\n    <string name=\"grid\">Grade</string>\n    <string name=\"list_mode\">Modo lista</string>\n    <string name=\"settings\">Configurações</string>\n    <string name=\"remote_sources\">Fontes</string>\n    <string name=\"loading_\">Carregando…</string>\n    <string name=\"chapter_d_of_d\">Capítulo %1$d de %2$d</string>\n    <string name=\"close\">Fechar</string>\n    <string name=\"try_again\">Tente novamente</string>\n    <string name=\"clear_history\">Limpar histórico</string>\n    <string name=\"nothing_found\">Nada encontrado</string>\n    <string name=\"add_new_category\">Nova categoria</string>\n    <string name=\"add\">Adicionar</string>\n    <string name=\"save\">Salvar</string>\n    <string name=\"share\">Compartilhar</string>\n    <string name=\"create_shortcut\">Criar atalho</string>\n    <string name=\"share_s\">Compartilhar %s</string>\n    <string name=\"search\">Procurar</string>\n    <string name=\"search_manga\">Procurar obra</string>\n    <string name=\"manga_downloading_\">Baixando…</string>\n    <string name=\"processing_\">Processando…</string>\n    <string name=\"download_complete\">Baixado</string>\n    <string name=\"downloads\">Baixados</string>\n    <string name=\"computing_\">Processando…</string>\n    <string name=\"add_to_favourites\">Favoritar obra</string>\n    <string name=\"by_name\">Nome</string>\n    <string name=\"popular\">Popular</string>\n    <string name=\"updated\">Atualizada</string>\n    <string name=\"newest\">Mais recente</string>\n    <string name=\"by_rating\">Avaliação</string>\n    <string name=\"sort_order\">Ordem de classificação</string>\n    <string name=\"filter\">Filtro</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"light\">Claro</string>\n    <string name=\"dark\">Escuro</string>\n    <string name=\"pages\">Páginas</string>\n    <string name=\"clear\">Limpar</string>\n    <string name=\"remove\">Remover</string>\n    <string name=\"_s_deleted_from_local_storage\">“%s” excluído do armazenamento local</string>\n    <string name=\"save_page\">Salvar página</string>\n    <string name=\"page_saved\">Página salva</string>\n    <string name=\"share_image\">Compartilhar imagem</string>\n    <string name=\"_import\">Importar</string>\n    <string name=\"delete\">Deletar</string>\n    <string name=\"text_file_not_supported\">Escolha um arquivo ZIP ou CBZ.</string>\n    <string name=\"no_description\">Sem descrição</string>\n    <string name=\"clear_pages_cache\">Limpar cache da página</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Padrão</string>\n    <string name=\"read_mode\">Modo de leitura</string>\n    <string name=\"grid_size\">Tamanho da grade</string>\n    <string name=\"search_on_s\">Procurar em %s</string>\n    <string name=\"delete_manga\">Excluir obra</string>\n    <string name=\"text_delete_local_manga\">Excluir “%s” do dispositivo permanentemente?</string>\n    <string name=\"reader_settings\">Configurações do leitor</string>\n    <string name=\"_continue\">Continuar</string>\n    <string name=\"error\">Erro</string>\n    <string name=\"clear_search_history\">Limpar histórico de procura</string>\n    <string name=\"search_history_cleared\">Limpo</string>\n    <string name=\"internal_storage\">Armazenamento interno</string>\n    <string name=\"external_storage\">Armazenamento externo</string>\n    <string name=\"domain\">Domínio</string>\n    <string name=\"app_update_available\">Uma nova versão do aplicativo está disponível</string>\n    <string name=\"open_in_browser\">Abrir no navegador</string>\n    <string name=\"notifications\">Notificações</string>\n    <string name=\"new_chapters\">Novos capítulos</string>\n    <string name=\"download\">Baixar</string>\n    <string name=\"notifications_settings\">Configurações de notificações</string>\n    <string name=\"notification_sound\">Som de notificação</string>\n    <string name=\"light_indicator\">Indicador LED</string>\n    <string name=\"vibration\">Vibração</string>\n    <string name=\"remove_category\">Remover</string>\n    <string name=\"text_empty_holder_primary\">Tá meio vazio aqui…</string>\n    <string name=\"text_search_holder_secondary\">Tente reformular a busca.</string>\n    <string name=\"text_history_holder_primary\">O que você lê vai aparecer aqui</string>\n    <string name=\"text_history_holder_secondary\">Encontre o que ler na seção «Explorar»</string>\n    <string name=\"text_local_holder_primary\">Salve algo primeiro</string>\n    <string name=\"manga_shelf\">Prateleira</string>\n    <string name=\"recent_manga\">Recente</string>\n    <string name=\"pages_animation\">Animação da página</string>\n    <string name=\"manga_save_location\">Pasta de downloads</string>\n    <string name=\"not_available\">Indisponível</string>\n    <string name=\"cannot_find_available_storage\">Sem espaço de armazenamento disponível</string>\n    <string name=\"other_storage\">Outro armazenamento</string>\n    <string name=\"done\">Feito</string>\n    <string name=\"all_favourites\">Todos os favoritos</string>\n    <string name=\"favourites_category_empty\">Categoria vazia</string>\n    <string name=\"read_later\">Ler depois</string>\n    <string name=\"updates\">Atualizações</string>\n    <string name=\"search_results\">Resultados da busca</string>\n    <string name=\"new_version_s\">Nova versão: %s</string>\n    <string name=\"size_s\">Tamanho: %s</string>\n    <string name=\"clear_updates_feed\">Limpar o fluxo de atualizações</string>\n    <string name=\"rotate_screen\">Girar a tela</string>\n    <string name=\"update\">Atualizar</string>\n    <string name=\"feed_will_update_soon\">A atualização do fluxo vai começar em breve</string>\n    <string name=\"track_sources\">Procurar por atualizações</string>\n    <string name=\"dont_check\">Não verificar</string>\n    <string name=\"enter_password\">Digite a senha</string>\n    <string name=\"wrong_password\">Senha incorreta</string>\n    <string name=\"protect_application_summary\">Solicitar senha ao iniciar o Kotatsu</string>\n    <string name=\"repeat_password\">Repita a senha</string>\n    <string name=\"passwords_mismatch\">Senhas incompatíveis</string>\n    <string name=\"about\">Sobre</string>\n    <string name=\"backup_information\">Você pode criar um backup do seu histórico e mangás favoritos e restaurá-lo</string>\n    <string name=\"just_now\">Agora mesmo</string>\n    <string name=\"yesterday\">Ontem</string>\n    <string name=\"long_ago\">Há muito tempo</string>\n    <string name=\"group\">Grupo</string>\n    <string name=\"today\">Hoje</string>\n    <string name=\"tap_to_try_again\">Toque para tentar novamente</string>\n    <string name=\"reader_mode_hint\">A configuração escolhida será lembrada para esta obra</string>\n    <string name=\"silent\">Silencioso</string>\n    <string name=\"captcha_required\">CAPTCHA obrigatório</string>\n    <string name=\"captcha_solve\">Resolver</string>\n    <string name=\"clear_cookies\">Limpar cookies</string>\n    <string name=\"cookies_cleared\">Todos os cookies foram removidos</string>\n    <string name=\"clear_feed\">Limpar fluxo</string>\n    <string name=\"text_clear_updates_feed_prompt\">Limpar todo o histórico de atualizações permanentemente\\?</string>\n    <string name=\"check_for_new_chapters\">Verificar se há novos capítulos</string>\n    <string name=\"reverse\">Reverter</string>\n    <string name=\"sign_in\">Entrar</string>\n    <string name=\"auth_required\">Faça login para ver este conteúdo</string>\n    <string name=\"default_s\">Padrão: %s</string>\n    <string name=\"next\">Próximo</string>\n    <string name=\"protect_application_subtitle\">Digite uma senha para iniciar o aplicativo com</string>\n    <string name=\"confirm\">Confirmar</string>\n    <string name=\"password_length_hint\">A senha precisa ter pelo menos 4 caracteres</string>\n    <string name=\"text_clear_search_history_prompt\">Remover todas as consultas de procuras recentes permanentemente?</string>\n    <string name=\"welcome\">Bem-vindo(a)</string>\n    <string name=\"backup_saved\">Backup salvo</string>\n    <string name=\"tracker_warning\">Alguns dispositivos têm comportamentos de sistema diferentes, o que pode afetar as tarefas em segundo plano.</string>\n    <string name=\"read_more\">Leia mais</string>\n    <string name=\"queued\">Na fila</string>\n    <string name=\"chapter_is_missing\">O capítulo está faltando</string>\n    <string name=\"about_app_translation_summary\">Clique e ajude-nos na tradução deste app</string>\n    <string name=\"about_app_translation\">Sobre a tradução</string>\n    <string name=\"auth_complete\">Autorizado</string>\n    <string name=\"auth_not_supported_by\">O login em %s não é suportado</string>\n    <string name=\"text_clear_cookies_prompt\">Você será desconectado de todas as fontes</string>\n    <string name=\"genres\">Gêneros</string>\n    <string name=\"state_finished\">Finalizada</string>\n    <string name=\"state_ongoing\">Em andamento</string>\n    <string name=\"system_default\">Padrão</string>\n    <string name=\"exclude_nsfw_from_history\">Excluir mangás NSFW do histórico</string>\n    <string name=\"show_pages_numbers\">Páginas numeradas</string>\n    <string name=\"screenshots_policy\">Política de capturas de tela</string>\n    <string name=\"screenshots_allow\">Permitir</string>\n    <string name=\"screenshots_block_nsfw\">Bloquear conteúdo NSFW</string>\n    <string name=\"screenshots_block_all\">Nunca permitir</string>\n    <string name=\"suggestions\">Sugestões</string>\n    <string name=\"suggestions_enable\">Ativar sugestões</string>\n    <string name=\"suggestions_summary\">Sugerir mangás com base nas suas preferências</string>\n    <string name=\"suggestions_info\">Todos os dados são analisados apenas localmente neste dispositivo e nunca são enviados para fora.</string>\n    <string name=\"text_suggestion_holder\">Comece a ler e você receberá sugestões personalizadas</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Não sugerir mangás NSFW</string>\n    <string name=\"history_is_empty\">Você ainda não leu nada, então não tem histórico (por enquanto)</string>\n    <string name=\"enabled\">Habilitado</string>\n    <string name=\"disabled\">Desabilitado</string>\n    <string name=\"reset_filter\">Redefinir filtro</string>\n    <string name=\"onboard_text\">Escolha os idiomas nos quais você quer ler os mangás. Você pode mudar isso depois nas configurações.</string>\n    <string name=\"never\">Nunca</string>\n    <string name=\"only_using_wifi\">Somente em Wi-Fi</string>\n    <string name=\"always\">Sempre</string>\n    <string name=\"preload_pages\">Pré-carregar páginas</string>\n    <string name=\"logged_in_as\">Conectado como %s</string>\n    <string name=\"nsfw\">+18</string>\n    <string name=\"various_languages\">Vários idiomas</string>\n    <string name=\"operation_not_supported\">Esta operação não é suportada</string>\n    <string name=\"follow_system\">Automático (segue o sistema)</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d de %2$d habilitados</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"switch_pages\">Mudar páginas</string>\n    <string name=\"clear_thumbs_cache\">Limpar cache das miniaturas (capas, etc.)</string>\n    <string name=\"favourites_categories\">Categorias favoritas</string>\n    <string name=\"no_update_available\">Nenhuma atualização disponível</string>\n    <string name=\"create_category\">Nova categoria</string>\n    <string name=\"zoom_mode_fit_center\">Ajustar ao centro</string>\n    <string name=\"zoom_mode_keep_start\">Manter no início</string>\n    <string name=\"black_dark_theme_summary\">Consome menos bateria em telas AMOLED</string>\n    <string name=\"create_backup\">Criar backup de dados</string>\n    <string name=\"text_local_holder_secondary\">Salve algo do catálogo online ou importe de um arquivo.</string>\n    <string name=\"check_for_updates\">Verificar se há atualizações</string>\n    <string name=\"text_feed_holder\">Novos capítulos da(s) obra(s) que você lê serão mostrados aqui</string>\n    <string name=\"app_version\">Versão %s</string>\n    <string name=\"zoom_mode_fit_width\">Ajustar à largura</string>\n    <string name=\"preparing_\">Preparando…</string>\n    <string name=\"data_restored_success\">Todos os dados foram restaurados</string>\n    <string name=\"protect_application\">Proteja o aplicativo</string>\n    <string name=\"updates_feed_cleared\">Limpo</string>\n    <string name=\"right_to_left\">Da direita para a esquerda</string>\n    <string name=\"scale_mode\">Modo de escala</string>\n    <string name=\"zoom_mode_fit_height\">Ajustar à altura</string>\n    <string name=\"black_dark_theme\">Preto</string>\n    <string name=\"backup_restore\">Backup e restauração</string>\n    <string name=\"restore_backup\">Restaurar do backup</string>\n    <string name=\"data_restored\">Restaurado</string>\n    <string name=\"data_restored_with_errors\">Os dados foram restaurados, mas há erros</string>\n    <string name=\"file_not_found\">Arquivo não encontrado</string>\n    <string name=\"search_chapters\">Encontrar capítulo</string>\n    <string name=\"chapters_empty\">Esta obra não possui capítulos</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"suggestions_updating\">Sugestões sendo atualizadas</string>\n    <string name=\"appearance\">Aparência</string>\n    <string name=\"suggestions_excluded_genres\">Excluir gêneros</string>\n    <string name=\"suggestions_excluded_genres_summary\">Especifique os gêneros que você não quer ver nas sugestões</string>\n    <string name=\"removal_completed\">Remoção concluída</string>\n    <string name=\"text_delete_local_manga_batch\">Excluir itens selecionados do dispositivo permanentemente\\?</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"download_slowdown\">Desaceleração</string>\n    <string name=\"download_slowdown_summary\">Diminui a velocidade do Wi-Fi enquanto você está baixando algo e ajuda a evitar o bloqueio do seu IP</string>\n    <string name=\"local_manga_processing\">Processando mangás salvos</string>\n    <string name=\"chapters_will_removed_background\">Os capítulos serão removidos em segundo plano</string>\n    <string name=\"new_sources_text\">Novas fontes estão disponíveis</string>\n    <string name=\"check_new_chapters_title\">Verifique novos capítulos e me notifique sobre eles</string>\n    <string name=\"show_notification_new_chapters_on\">Você receberá notificações sobre atualizações dos mangás que está lendo</string>\n    <string name=\"edit_category\">Editar categoria</string>\n    <string name=\"empty_favourite_categories\">Sem categorias favoritas</string>\n    <string name=\"bookmark_add\">Adicionar marcador</string>\n    <string name=\"bookmark_remove\">Remover marcador</string>\n    <string name=\"bookmarks\">Marcadores</string>\n    <string name=\"bookmark_added\">Marcador adicionado</string>\n    <string name=\"show_notification_new_chapters_off\">Você não receberá notificações, mas novos capítulos serão destacados nas listas</string>\n    <string name=\"notifications_enable\">Ativar notificações</string>\n    <string name=\"name\">Nome</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"bookmark_removed\">Marcador removido</string>\n    <string name=\"undo\">Desfazer</string>\n    <string name=\"removed_from_history\">Removida do histórico</string>\n    <string name=\"dns_over_https\">DNS sobre HTTPS</string>\n    <string name=\"default_mode\">Modo padrão</string>\n    <string name=\"detect_reader_mode\">Detecção automática do modo de leitura</string>\n    <string name=\"detect_reader_mode_summary\">Detectar automaticamente se a obra é uma webtoon</string>\n    <string name=\"disable_battery_optimization\">Desativar a otimização da bateria</string>\n    <string name=\"disable_battery_optimization_summary\">Ajuda com as verificações de atualizações em segundo plano</string>\n    <string name=\"invalid_domain_message\">Domínio inválido</string>\n    <string name=\"use_fingerprint\">Usar biometria, se disponível</string>\n    <string name=\"appwidget_shelf_description\">Mangás dos seus favoritos</string>\n    <string name=\"appwidget_recent_description\">Mangás lidos recentemente</string>\n    <string name=\"status_completed\">Concluída</string>\n    <string name=\"status_on_hold\">Em espera</string>\n    <string name=\"status_dropped\">Abandonada</string>\n    <string name=\"canceled\">Cancelado</string>\n    <string name=\"account_already_exists\">Conta já existente</string>\n    <string name=\"back\">Voltar</string>\n    <string name=\"sync\">Sincronização</string>\n    <string name=\"sync_title\">Sincronize seus dados</string>\n    <string name=\"email_enter_hint\">Digite seu e-mail para continuar</string>\n    <string name=\"tracking\">Monitoramento</string>\n    <string name=\"logout\">Sair</string>\n    <string name=\"crash_text\">Vixe, algo deu errado. Por favor, envie um relatório de erro para os desenvolvedores para nos ajudar a corrigir o problema.</string>\n    <string name=\"send\">Enviar</string>\n    <string name=\"status_planned\">Planejado</string>\n    <string name=\"status_reading\">Lendo</string>\n    <string name=\"status_re_reading\">Relendo</string>\n    <string name=\"report\">Reportar</string>\n    <string name=\"clear_cookies_summary\">Pode ajudar em caso de problemas. Todas as autorizações serão invalidadas</string>\n    <string name=\"show_reading_indicators\">Mostrar indicadores de progresso de leitura</string>\n    <string name=\"data_deletion\">Exclusão de dados</string>\n    <string name=\"show_reading_indicators_summary\">Mostrar porcentagem de leitura no histórico e nos favoritos</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Mangás marcados como NSFW nunca serão adicionadas ao histórico e seu progresso não será salvo</string>\n    <string name=\"show_all\">Mostrar tudo</string>\n    <string name=\"clear_all_history\">Limpar todo o histórico</string>\n    <string name=\"last_2_hours\">Últimas 2 horas</string>\n    <string name=\"history_cleared\">Histórico limpo</string>\n    <string name=\"manage\">Gerenciar</string>\n    <string name=\"no_bookmarks_yet\">Sem marcadores ainda</string>\n    <string name=\"no_bookmarks_summary\">Você pode favoritar uma página enquanto lê uma obra</string>\n    <string name=\"bookmarks_removed\">Marcadores removidos</string>\n    <string name=\"no_manga_sources\">Sem fontes</string>\n    <string name=\"select_range\">Selecionar intervalo</string>\n    <string name=\"disable_all\">Desativar tudo</string>\n    <string name=\"no_chapters\">Sem capítulos</string>\n    <string name=\"manga_error_description_pattern\">Detalhes do erro:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Tente &lt;a href=%2$s&gt;abrir a obra no navegador web&lt;/a&gt; para ver se ela ainda está disponível nesta fonte.&lt;br&gt;2. Verifique se você está usando a &lt;a href=kotatsu://about&gt;versão mais recente do Kotatsu&lt;/a&gt;.&lt;br&gt;3. Se a obra estiver lá, envie um relatório de erro para os desenvolvedores.</string>\n    <string name=\"feed\">Fluxo</string>\n    <string name=\"history_shortcuts_summary\">Torne os mangás recentes disponíveis ao pressionar e segurar o ícone do aplicativo</string>\n    <string name=\"no_manga_sources_text\">Habilite fontes para ler mangás online</string>\n    <string name=\"random\">Aleatória</string>\n    <string name=\"categories_delete_confirm\">Você tem certeza de que deseja excluir as categorias favoritas selecionadas?\\nTodos os mangás nelas serão perdidos e isso não pode ser desfeito.</string>\n    <string name=\"reorder\">Reordenar</string>\n    <string name=\"empty\">Vazio</string>\n    <string name=\"explore\">Explorar</string>\n    <string name=\"confirm_exit\">Pressione Voltar novamente para sair</string>\n    <string name=\"exit_confirmation_summary\">Pressione Voltar duas vezes para sair do app</string>\n    <string name=\"exit_confirmation\">Confirmação de saída</string>\n    <string name=\"saved_manga\">Mangás salvos</string>\n    <string name=\"pages_cache\">Cache de páginas</string>\n    <string name=\"other_cache\">Outro cache</string>\n    <string name=\"storage_usage\">Uso do armazenamento</string>\n    <string name=\"available\">Disponível</string>\n    <string name=\"removed_from_favourites\">Removido dos favoritos</string>\n    <string name=\"options\">Opções</string>\n    <string name=\"incognito_mode\">Modo anônimo</string>\n    <string name=\"automatic_scroll\">Rolagem automática</string>\n    <string name=\"reader_info_pattern\">Cap. %1$d/%2$d Pág. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Mostrar barra de informações no leitor</string>\n    <string name=\"folder_with_images\">Pasta com imagens</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"comics_archive\">Arquivo de quadrinhos</string>\n    <string name=\"importing_manga\">Importando obra(s)</string>\n    <string name=\"import_completed\">Importação completa</string>\n    <string name=\"import_completed_hint\">Você pode excluir o arquivo original do armazenamento para economizar espaço</string>\n    <string name=\"import_will_start_soon\">A importação começará em breve</string>\n    <string name=\"history_shortcuts\">Mostrar atalhos de mangás recentes</string>\n    <string name=\"reader_control_ltr_summary\">Não ajustar a direção da troca de páginas para o modo leitor, ex: pressionar a \\\"tecla\\\" direita sempre muda para a próxima página. Essa opção apenas afeta dispositivos de entrada hardware</string>\n    <string name=\"reader_control_ltr\">Controle de leitura ergonômico</string>\n    <string name=\"color_correction\">Correção de cor</string>\n    <string name=\"brightness\">Brilho</string>\n    <string name=\"contrast\">Contraste</string>\n    <string name=\"reset\">Resetar</string>\n    <string name=\"text_unsaved_changes_prompt\">Salvar ou descartar alterações não salvas?</string>\n    <string name=\"discard\">Descartar</string>\n    <string name=\"not_found_404\">Conteúdo não encontrado ou removido</string>\n    <string name=\"services\">Serviços</string>\n    <string name=\"nothing_here\">Não há nada aqui</string>\n    <string name=\"server_error\">Erro do servidor (%1$d). Por favor, tente novamente mais tarde</string>\n    <string name=\"compact\">Compacta</string>\n    <string name=\"enable_logging\">Habilitar registros</string>\n    <string name=\"share_logs\">Compartilhar registros</string>\n    <string name=\"error_no_space_left\">Sem espaço disponível no dispositivo</string>\n    <string name=\"network_unavailable\">Rede não disponível</string>\n    <string name=\"network_unavailable_hint\">Ative o Wi-Fi ou a rede móvel para ler mangás online</string>\n    <string name=\"allow_unstable_updates\">Permitir atualizações instáveis</string>\n    <string name=\"download_started\">Começando a baixar</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"mark_as_current\">Marcar como atual</string>\n    <string name=\"show_in_grid_view\">Mostrar em visualização de grade</string>\n    <string name=\"color_theme\">Esquema de cores</string>\n    <string name=\"show_suspicious_content\">Exibir conteúdo suspeito</string>\n    <string name=\"prefetch_content\">Pré-carregamento de conteúdo</string>\n    <string name=\"theme_name_dynamic\">Dinâmico</string>\n    <string name=\"settings_apply_restart_required\">Por favor, reinicie o aplicativo para aplicar essas mudanças</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"source_disabled\">Fonte desativada</string>\n    <string name=\"enable_logging_summary\">Gravar algumas ações para fins de depuração. Não ative se você não souber o que está fazendo</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"scrobbling_empty_hint\">Para acompanhar o progresso de leitura, selecione Menu → Rastrear na tela de detalhes da obra.</string>\n    <string name=\"clear_new_chapters_counters\">Também limpe as informações sobre novos capítulos</string>\n    <string name=\"sync_auth_hint\">Você pode entrar em uma conta existente ou criar uma nova</string>\n    <string name=\"allow_unstable_updates_summary\">Receber notificações sobre versões beta</string>\n    <string name=\"comics_archive_import_description\">Você pode selecionar um ou mais arquivos .cbz ou .zip; cada arquivo será reconhecido como uma obra separada.</string>\n    <string name=\"folder_with_images_import_description\">Você pode selecionar um diretório com arquivos ou imagens. Cada arquivo (ou subdiretório) será reconhecido como um capítulo.</string>\n    <string name=\"speed\">Velocidade</string>\n    <string name=\"show_on_shelf\">Mostrar na Prateleira</string>\n    <string name=\"got_it\">Entendi</string>\n    <string name=\"sources_reorder_tip\">Toque e segure em um item para reordená-lo</string>\n    <string name=\"user_agent\">Cabeçalho User/Agent</string>\n    <string name=\"reader_slider\">Mostrar o controle deslizante para troca de páginas</string>\n    <string name=\"webtoon_zoom\">Zoom Webtoon</string>\n    <string name=\"show_pages_numbers_summary\">Exibir números de página no canto inferior</string>\n    <string name=\"find_similar\">Encontrar similares</string>\n    <string name=\"resume\">Retomar</string>\n    <string name=\"paused\">Pausado</string>\n    <string name=\"remove_completed\">Remoção concluída</string>\n    <string name=\"cancel_all\">Cancelar tudo</string>\n    <string name=\"suggestions_notifications_summary\">Às vezes, mostra notificações com mangás sugeridos</string>\n    <string name=\"more\">Mais</string>\n    <string name=\"enable\">Permitir</string>\n    <string name=\"no_thanks\">Não, valeu</string>\n    <string name=\"remove_completed_downloads_confirm\">Seu histórico de baixados será excluído permanentemente. Nenhum arquivo baixado será afetado</string>\n    <string name=\"text_downloads_list_holder\">Você ainda não baixou nada</string>\n    <string name=\"downloads_paused\">\\\"Baixando foi pausado\\\"</string>\n    <string name=\"downloads_removed\">Os baixados foram removidos</string>\n    <string name=\"downloads_cancelled\">Os \\\"baixando\\\" foram cancelados</string>\n    <string name=\"clear_network_cache\">Limpar cache de rede</string>\n    <string name=\"type\">Tipo</string>\n    <string name=\"invalid_value_message\">Valor inválido</string>\n    <string name=\"downloaded\">Baixado</string>\n    <string name=\"images_proxy_title\">Proxy de otimização de imagens</string>\n    <string name=\"images_procy_description\">Use o serviço wsrv.nl para reduzir o uso de tráfego e acelerar o carregamento de imagens, se possível</string>\n    <string name=\"invert_colors\">Cores invertidas</string>\n    <string name=\"username\">Nome de usuário</string>\n    <string name=\"password\">Senha</string>\n    <string name=\"invalid_port_number\">Número de porta inválido</string>\n    <string name=\"network\">Rede</string>\n    <string name=\"data_and_privacy\">Dados e privacidade</string>\n    <string name=\"restore_summary\">Restaurar backup criado anteriormente</string>\n    <string name=\"webtoon_zoom_summary\">Permitir zoom em gesto no modo webtoon</string>\n    <string name=\"reader_info_bar_summary\">Mostrar a hora atual e o progresso da leitura na parte superior da tela</string>\n    <string name=\"clear_source_cookies_summary\">Limpar cookies apenas para o domínio especificado. Na maioria dos casos invalidará a autorização</string>\n    <string name=\"download_option_all_chapters\">Todos os capítulos com tradução %s</string>\n    <string name=\"download_option_whole_manga\">A obra toda</string>\n    <string name=\"download_option_first_n_chapters\">Primeiros %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Próximos %s não lidos</string>\n    <string name=\"download_option_all_unread\">Todos os capítulos não lidos</string>\n    <string name=\"download_option_all_unread_b\">Todos os capítulos não lidos (%s)</string>\n    <string name=\"download_option_manual_selection\">Selecionar capítulos manualmente</string>\n    <string name=\"authorization_optional\">Autorização (opcional)</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"sync_settings\">Configurações de sincronização</string>\n    <string name=\"server_address\">Endereço do servidor</string>\n    <string name=\"sync_host_description\">Você pode usar um servidor de sincronização auto-hospedado ou um padrão. Não altere isso se você não souber o que está fazendo.</string>\n    <string name=\"ignore_ssl_errors\">Ignorar erros SSL</string>\n    <string name=\"mirror_switching\">Escolher espelho automaticamente</string>\n    <string name=\"mirror_switching_summary\">Troca automática de domínios para fontes de mangás em caso de erros, se houver espelhos disponíveis</string>\n    <string name=\"pause\">Pausar</string>\n    <string name=\"suggestions_enable_prompt\">Quer receber sugestões personalizadas de mangás?</string>\n    <string name=\"suggestion_manga\">Sugestão: %s</string>\n    <string name=\"downloads_resumed\">\\\"Baixando\\\" foi retomado</string>\n    <string name=\"downloads_wifi_only_summary\">Interrompa o \\\"baixando\\' ao mudar para uma rede móvel</string>\n    <string name=\"cancel_all_downloads_confirm\">Todos os \\\"baixando\\\" ativos serão cancelados, dados parcialmente baixados serão perdidos</string>\n    <string name=\"downloads_wifi_only\">Baixar apenas via Wi-Fi</string>\n    <string name=\"web_view_unavailable\">WebView não disponível: verifique se o provedor WebView está instalado</string>\n    <string name=\"port\">Porta</string>\n    <string name=\"address\">Endereço</string>\n    <string name=\"pick_custom_directory\">Escolha um diretório personalizado</string>\n    <string name=\"no_access_to_file\">Você não tem acesso a esse arquivo ou diretório</string>\n    <string name=\"local_manga_directories\">Diretórios locais de mangás</string>\n    <string name=\"languages\">Idiomas</string>\n    <string name=\"zoom_in\">Aumentar o zoom</string>\n    <string name=\"captcha_required_summary\">%s requer que o captcha seja resolvido para funcionar corretamente</string>\n    <string name=\"progress\">Progresso</string>\n    <string name=\"error_corrupted_file\">Dados inválidos ou o arquivo está corrompido</string>\n    <string name=\"related_manga_summary\">Mostra uma lista de mangás relacionados. Em alguns casos, ela pode estar incorreta ou ausente</string>\n    <string name=\"reader_zoom_buttons_summary\">Se deseja mostrar os botões de controle de zoom no canto inferior direito</string>\n    <string name=\"tracker_wifi_only_summary\">Não procurar por novos capítulos usando dados móveis</string>\n    <string name=\"order_added\">Adicionada</string>\n    <string name=\"on_device\">No dispositivo</string>\n    <string name=\"moved_to_top\">Movido pra cima</string>\n    <string name=\"data_not_restored_text\">Certifique-se de ter selecionado o arquivo de backup correto</string>\n    <string name=\"unknown\">Desconhecido</string>\n    <string name=\"in_progress\">Em andamento</string>\n    <string name=\"items_limit_exceeded\">Não é possível adicionar mais itens</string>\n    <string name=\"data_not_restored\">Os dados não foram restaurados</string>\n    <string name=\"directories\">Diretórios</string>\n    <string name=\"manage_categories\">Gerenciar categorias</string>\n    <string name=\"color_light\">Claro</string>\n    <string name=\"search_hint\">Digite o título da obra, gênero ou o nome da fonte</string>\n    <string name=\"description\">Descrição</string>\n    <string name=\"reader_zoom_buttons\">Mostrar botões de zoom</string>\n    <string name=\"main_screen_sections\">Seções da tela principal</string>\n    <string name=\"advanced\">Avançado</string>\n    <string name=\"color_dark\">Escuro</string>\n    <string name=\"too_many_requests_message\">Muitas tentativas. Tente novamente mais tarde</string>\n    <string name=\"related_manga\">Obra(s) relacionada(s)</string>\n    <string name=\"suggestions_wifi_only_summary\">Não atualizar as sugestões usando dados móveis</string>\n    <string name=\"background\">Fundo</string>\n    <string name=\"zoom_out\">Diminuir o zoom</string>\n    <string name=\"voice_search\">Pesquisa por voz</string>\n    <string name=\"manga_list\">Lista de mangás</string>\n    <string name=\"disable_nsfw\">Desativar NSFW (+18)</string>\n    <string name=\"color_white\">Branco</string>\n    <string name=\"to_top\">Para cima</string>\n    <string name=\"show\">Mostrar</string>\n    <string name=\"color_black\">Preto</string>\n    <string name=\"this_month\">Este mês</string>\n    <string name=\"frequency_every_day\">Diariamente</string>\n    <string name=\"categories\">Categorias</string>\n    <string name=\"list_options\">Opções (favoritas)</string>\n    <string name=\"backup_frequency\">Frequência de criação de backup</string>\n    <string name=\"suggest_new_sources\">Sugerir novas fontes após a atualização do aplicativo</string>\n    <string name=\"periodic_backups_enable\">Ativar backups periódicos</string>\n    <string name=\"enhanced_colors_summary\">Reduz a formação de faixas, mas pode afetar o desempenho</string>\n    <string name=\"frequency_every_2_days\">A cada 2 dias</string>\n    <string name=\"frequency_once_per_week\">Semanalmente</string>\n    <string name=\"periodic_backups\">Backups periódicos</string>\n    <string name=\"frequency_twice_per_month\">Duas vezes ao mês</string>\n    <string name=\"online_variant\">Variante on-line</string>\n    <string name=\"by_relevance\">Relevância</string>\n    <string name=\"state_abandoned\">Abandonada</string>\n    <string name=\"keep_screen_on\">Manter a tela ligada</string>\n    <string name=\"frequency_once_per_month\">Uma vez por mês</string>\n    <string name=\"enhanced_colors\">Modo de cor 32 bits</string>\n    <string name=\"keep_screen_on_summary\">Não desligar a tela enquanto estiver lendo a obra</string>\n    <string name=\"last_successful_backup\">Último backup bem-sucedido: %s</string>\n    <string name=\"backups_output_directory\">Local de saída de backups</string>\n    <string name=\"suggest_new_sources_summary\">Solicitação para ativar fontes recém-adicionadas após a atualização do aplicativo</string>\n    <string name=\"sources_catalog\">Catálogo de fontes</string>\n    <string name=\"content_type_manga\">Obra</string>\n    <string name=\"error_multiple_states_not_supported\">A filtragem por múltiplas situações não é suportada por esta fonte</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Quadrinhos</string>\n    <string name=\"catalog\">Catálogo</string>\n    <string name=\"reader_optimize\">Reduzir o consumo de memória (beta)</string>\n    <string name=\"manage_sources\">Gerenciar fontes</string>\n    <string name=\"no_manga_sources_found\">Nenhuma fonte foi encontrada por sua pesquisa</string>\n    <string name=\"error_multiple_genres_not_supported\">A filtragem por múltiplos gêneros não é suportada por esta fonte</string>\n    <string name=\"lock_screen_rotation\">Travar rotação de tela</string>\n    <string name=\"error_search_not_supported\">Pesquisar não é suportado por esta fonte</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"reader_optimize_summary\">Reduzir a qualidade das páginas fora da tela para usar menos memória</string>\n    <string name=\"source_enabled\">Fonte habilitada</string>\n    <string name=\"disable_nsfw_summary\">Desative as fontes NSFW e oculte os mangás adultos da lista, se possível</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"no_manga_sources_catalog_text\">Não há fontes disponíveis nesta seção, ou todas elas podem já ter sido habilitadas.\\nFique atento</string>\n    <string name=\"available_d\">Disponível: %1$d</string>\n    <string name=\"state\">Situação</string>\n    <string name=\"state_paused\">Pausada</string>\n    <string name=\"content_type_other\">Outros</string>\n    <string name=\"error_filter_states_genre_not_supported\">A filtragem por gênero e situação não é suportado por esta fonte</string>\n    <string name=\"error_filter_locale_genre_not_supported\">A filtragem por gênero e localidade não é suportada por esta fonte</string>\n    <string name=\"welcome_text\">Por favor, selecione quais fontes de conteúdo você gostaria de habilitar. Isso também pode ser configurado posteriormente nas configurações</string>\n    <string name=\"apply\">Aplicar</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"genres_search_hint\">Comece a digitar o nome do gênero</string>\n    <string name=\"globally\">Globalmente</string>\n    <string name=\"downloads_settings_info\">Você pode ativar a desaceleração de downloads para cada fonte individualmente nas configurações da fonte, se estiver enfrentando problemas com bloqueios de IP</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Pode ajudar a iniciar o \\\"baixando\\\" caso você tenha algum problema com isso</string>\n    <string name=\"this_manga\">Esta obra</string>\n    <string name=\"skip\">Pular</string>\n    <string name=\"color_correction_apply_text\">Essas configurações podem ser aplicadas globalmente ou apenas a obra atual. Se aplicadas globalmente, as configurações individuais não serão substituídas.</string>\n    <string name=\"backup_date_\">Data do backup: %s</string>\n    <string name=\"grayscale\">Tons de Cinza</string>\n    <string name=\"sync_auth\">Faça login para sincronizar a conta</string>\n    <string name=\"state_upcoming\">por vir</string>\n    <string name=\"by_name_reverse\">Nome invertido</string>\n    <string name=\"rating_safe\">Segura</string>\n    <string name=\"rating_suggestive\">Sugestiva</string>\n    <string name=\"genres_exclude\">Excluir gênero</string>\n    <string name=\"rating_adult\">Adulta</string>\n    <string name=\"default_tab\">Aba padrão</string>\n    <string name=\"content_rating\">Classificação do Conteúdo</string>\n    <string name=\"mark_as_completed_prompt\">Deseja marcar a obra selecionada como completa?\\n\\nAviso: o progresso de leitura atual será perdido.</string>\n    <string name=\"mark_as_completed\">Marcar como completa</string>\n    <string name=\"category_hidden_done\">Esta categoria foi ocultada da tela inicial e pode ser acessada novamente através de Menu → Gerenciar categorias</string>\n    <string name=\"volume_\">Volume %d</string>\n    <string name=\"volume_unknown\">Volume desconhecido</string>\n    <string name=\"incognito_mode_hint\">Seu progresso de leitura não será salvo</string>\n    <string name=\"vertical\">Vertical</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"email_password_enter_hint\">Digite seu e-mail e senha para continuar</string>\n    <string name=\"show_menu\">Mostrar menu</string>\n    <string name=\"toggle_ui\">Mostrar/ocultar UI</string>\n    <string name=\"prev_chapter\">Capítulo anterior</string>\n    <string name=\"next_chapter\">Próximo capítulo</string>\n    <string name=\"prev_page\">Página anterior</string>\n    <string name=\"next_page\">Próxima página</string>\n    <string name=\"reader_actions\">Ações do leitor</string>\n    <string name=\"reader_actions_summary\">Configurar ações para áreas de tela ajustáveis</string>\n    <string name=\"switch_pages_volume_buttons\">Habilitar botões de volume</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Usar botões de volume para alternar páginas</string>\n    <string name=\"tap_action\">Ação de toque</string>\n    <string name=\"long_tap_action\">Ação de toque longo</string>\n    <string name=\"config_reset_confirm\">Redefinir as configuração para os valores padrões? Essa ação não pode ser desfeita.</string>\n    <string name=\"use_two_pages_landscape\">Usar layout de duas páginas na orientação paisagem (beta)</string>\n    <string name=\"last_read\">Última leitura</string>\n    <string name=\"none\">Nenhum</string>\n    <string name=\"default_webtoon_zoom_out\">Diminuir zoom padrão da webtoon</string>\n    <string name=\"fullscreen_mode\">Modo tela cheia</string>\n    <string name=\"reader_fullscreen_summary\">Ocultar a barra de status e navegação</string>\n    <string name=\"remove_from_history\">Remover do histórico</string>\n    <string name=\"automatic\">Automático</string>\n    <string name=\"other_manga\">Outros mangás</string>\n    <string name=\"reading_stats\">Estatísticas de leitura</string>\n    <string name=\"statistics\">Estatísticas</string>\n    <string name=\"stats_cleared\">Estatísticas limpadas</string>\n    <string name=\"pages_read_s\">Páginas lidas: %s</string>\n    <string name=\"preferred_download_format\">Formato preferido para baixar</string>\n    <string name=\"single_cbz_file\">Único arquivo CBZ</string>\n    <string name=\"multiple_cbz_files\">Vários arquivos CBZ</string>\n    <string name=\"suggestions_unavailable_text\">O recurso de sugestões está desativado</string>\n    <string name=\"check_for_new_chapters_disabled\">A verificação de novos capítulos está desativada</string>\n    <string name=\"reading_time_estimation\">Mostrar tempo estimado para leitura</string>\n    <string name=\"reading_time_estimation_summary\">O valor do tempo estimado pode ser impreciso</string>\n    <string name=\"pages_saving\">Salvando páginas</string>\n    <string name=\"show_labels_in_navbar\">Mostrar rótulos na barra de navegação</string>\n    <string name=\"ask_for_dest_dir_every_time\">Sempre pedir o diretório de destino</string>\n    <string name=\"default_page_save_dir\">Diretório padrão para o salvamento das páginas</string>\n    <string name=\"less_than_minute\">Menos de um minuto</string>\n    <string name=\"clear_stats\">Limpar estatísticas</string>\n    <string name=\"clear_stats_confirm\">Você tem certeza que deseja limpar todas as estatísticas de leitura? A ação não pode ser desfeita.</string>\n    <string name=\"week\">Semana</string>\n    <string name=\"empty_stats_text\">Não há estatísticas para o período selecionado</string>\n    <string name=\"month\">Mês</string>\n    <string name=\"all_time\">Todos os tempos</string>\n    <string name=\"day\">Dia</string>\n    <string name=\"three_months\">Três meses</string>\n    <string name=\"location\">Localização</string>\n    <string name=\"unsupported_backup_message\">Selecione um arquivo de backup do Kotatsu adequado</string>\n    <string name=\"hours_short\">%d h</string>\n    <string name=\"minutes_short\">%d min</string>\n    <string name=\"chapters_grid_view\">Exibição em grade</string>\n    <string name=\"alternatives\">Alternativas</string>\n    <string name=\"manga_migration\">Migração de obra</string>\n    <string name=\"migration_completed\">Migração concluída</string>\n    <string name=\"chapters_deleted_pattern\">%1$s removido, %2$s limpo</string>\n    <string name=\"delete_read_chapters\">Apagar capítulos lidos</string>\n    <string name=\"delete_read_chapters_summary\">Apagar capítulos lidos do armazenamento local para liberar espaço</string>\n    <string name=\"delete_read_chapters_auto\">Apagar capítulos lidos automaticamente</string>\n    <string name=\"runs_on_app_start\">É executado quando o aplicativo é iniciado</string>\n    <string name=\"show_pages_thumbs\">Mostrar miniaturas das páginas</string>\n    <string name=\"show_pages_thumbs_summary\">Ativar aba de \\\"Páginas\\\" na tela de detalhes</string>\n    <string name=\"migrate\">Migrar</string>\n    <string name=\"migrate_confirmation\">A obra \\\"%1$s\\\" de \\\"%2$s\\\" será substituída por \\\"%3$s\\\" de \\\"%4$s\\\" em seu histórico e favoritas (se adicionada)</string>\n    <string name=\"no_chapters_deleted\">Não há capítulos a serem apagados</string>\n    <string name=\"hours_minutes_short\">%1$d h %2$d min</string>\n    <string name=\"missing_storage_permission\">Não há permissão para acessar arquivos em armazenamento externo</string>\n    <string name=\"unread\">Não lido</string>\n    <string name=\"split_by_translations_summary\">Mostrar capítulos com diferentes traduções separadamente, invés do que em uma lista</string>\n    <string name=\"order_oldest\">Mais velho</string>\n    <string name=\"long_ago_read\">Lido há muito tempo atrás</string>\n    <string name=\"split_by_translations\">Separar por traduções</string>\n    <string name=\"fix\">Corrigir</string>\n    <string name=\"error_no_data_received\">Nenhum dado foi recebido do servidor</string>\n    <string name=\"enable_source\">Habilitar fonte</string>\n    <string name=\"unsupported_source\">Esta fonte não é suportada</string>\n    <string name=\"delete_read_chapters_prompt\">Isso irá apagar permanentemente todos os capítulos marcados como lidos do armazenamento local. Você pode baixá-los novamente mais tarde, mas os capítulos importados podem ser perdidos para sempre</string>\n    <string name=\"last_used\">Usado pela última vez</string>\n    <string name=\"show_updated\">Mostrar atualização</string>\n    <string name=\"disable_connectivity_check\">Desativar a verificação de conectividade</string>\n    <string name=\"disable_connectivity_check_summary\">Ignore a verificação de conectividade caso tenha problemas com ela (por exemplo, entrar no modo off-line mesmo que a rede esteja conectada)</string>\n    <string name=\"webtoon_gaps\">Lacunas no modo Webtoon</string>\n    <string name=\"webtoon_gaps_summary\">Mostrar lacunas verticais entre as páginas no modo Webtoon</string>\n    <string name=\"authors\">Autores</string>\n    <string name=\"ignore_ssl_errors_summary\">Você pode desativar a verificação de certificados SSL caso tenha problemas relacionados a SSL ao acessar recursos de rede. Isso pode afetar sua segurança. É necessário reiniciar o aplicativo após alterar essa configuração.</string>\n    <string name=\"search_suggestions\">Sugestões de pesquisa</string>\n    <string name=\"recent_queries\">Consultas recentes</string>\n    <string name=\"suggested_queries\">Consultas sugeridas</string>\n    <string name=\"disable\">Desativar</string>\n    <string name=\"sources_disabled\">Fontes desativadas</string>\n    <string name=\"blocked_by_server_message\">Você está bloqueado pelo servidor. Tente usar uma conexão de internet diferente (VPN, Proxy, etc.)</string>\n    <string name=\"less_frequently\">Com menos frequência</string>\n    <string name=\"more_frequently\">Com mais frequência</string>\n    <string name=\"frequency_of_check\">Frequência de verificação</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Fixar interface de navegação</string>\n    <string name=\"pin_navigation_ui_summary\">Não esconder barra de navegação e visualização de pesquisa ao rolar</string>\n    <string name=\"_new\">Novos</string>\n    <string name=\"all_languages\">Todas os idiomas</string>\n    <string name=\"screenshots_block_incognito\">Bloquear no modo de navegação anônima</string>\n    <string name=\"image_server\">Servidor de imagem preferido</string>\n    <string name=\"crop_pages\">Cortar páginas</string>\n    <string name=\"disable_nsfw_notifications\">Desativar notificações NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">Não mostrar notificações sobre atualizações de mangás NSFW</string>\n    <string name=\"percent_read\">Porcentagem lido</string>\n    <string name=\"percent_left\">Porcentagem restante</string>\n    <string name=\"chapters_read\">Capítulos lidos</string>\n    <string name=\"chapters_left\">Capítulos restantes</string>\n    <string name=\"pin\">Fixar</string>\n    <string name=\"unpin\">Desafixar</string>\n    <string name=\"source_pinned\">Fonte fixada</string>\n    <string name=\"source_unpinned\">Fonte desafixada</string>\n    <string name=\"sources_pinned\">Fontes fixadas</string>\n    <string name=\"recent_sources\">Fontes recentes</string>\n    <string name=\"external_source\">Plugin/Externo</string>\n    <string name=\"sources_unpinned\">Fontes desafixadas</string>\n    <string name=\"tracker_debug_info\">Checando por novos logs de capítulos</string>\n    <string name=\"tracker_debug_info_summary\">Informações de Debug sobre a checagem de fundo para novos capítulos</string>\n    <string name=\"plugin_incompatible\">Plugin incompatível ou erro interno. Certifique-se de que está usando a versão mais recente do plugin e do Kotatsu</string>\n    <string name=\"show_quick_filters_summary\">Habilitar filtros em todas as fontes compatíveis</string>\n    <string name=\"show_quick_filters\">Mostrar filtros</string>\n    <string name=\"scrobbler_auth_required\">Faça login em %s para continuar</string>\n    <string name=\"scrobbler_auth_intro\">Faça login para configurar a integração com %s. Isso permitirá que você acompanhe o progresso e o status de sua leitura</string>\n    <string name=\"unstable_feature_summary\">Essa função é experimental. Certifique-se de que você tenha um backup para evitar a perca de dados</string>\n    <string name=\"recently_added\">Adicionado recentemente</string>\n    <string name=\"added_long_ago\">Adicionado há muito tempo</string>\n    <string name=\"popular_today\">Popular hoje</string>\n    <string name=\"popular_in_week\">Popular nesta semana</string>\n    <string name=\"popular_in_year\">Popular neste ano</string>\n    <string name=\"invalid_server_address_message\">Endereço de servidor inválido</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"low_rating\">Classificação baixa</string>\n    <string name=\"original_language\">Idioma original</string>\n    <string name=\"year\">Ano</string>\n    <string name=\"source_code\">Código fonte</string>\n    <string name=\"user_manual\">Manual do usuário</string>\n    <string name=\"telegram_group\">Grupo do Telegram</string>\n    <string name=\"skip_all\">Pular todas</string>\n    <string name=\"by_date\">Data</string>\n    <string name=\"popularity\">Popularidade</string>\n    <string name=\"popular_in_month\">Popular neste mês</string>\n    <string name=\"sort_order_asc\">Ascendente</string>\n    <string name=\"years\">Anos</string>\n    <string name=\"any\">Qualquer</string>\n    <string name=\"filter_search_warning\">Esta fonte não oferece suporte à pesquisa com filtros. Seus filtros foram limpos</string>\n    <string name=\"start_download\">Comecar a baixar</string>\n    <string name=\"save_manga_confirm\">Salvar a obra selecionada? Isso pode consumir dados e armazenamento</string>\n    <string name=\"save_manga\">Salvar a obra</string>\n    <string name=\"genre\">Gênero</string>\n    <string name=\"error_not_image\">Formato inválido: esperava-se imagem, mas obtivemos %s</string>\n    <string name=\"invalid_proxy_configuration\">Configuração de proxy inválida</string>\n    <string name=\"error_image_format\">Formato de imagem não suportado: %s</string>\n    <string name=\"manga_with_downloaded_chapters\">Mangás com capítulos baixados</string>\n    <string name=\"manga_fix_prompt\">Essa função encontrará fontes alternativas para a obra selecionada. A tarefa levará algum tempo e será executada em segundo plano</string>\n    <string name=\"manga_replaced\">Obra “%1$s” (%2$s) substituída por “%3$s” (%4$s)</string>\n    <string name=\"no_alternatives_found\">Não foi encontrado alternativas para “%s”</string>\n    <string name=\"pages_saved\">Páginas salvas</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Não existem mangás que correspondam aos filtros selecionados</string>\n    <string name=\"plugin_incompatible_with_cause\">Erro de plugin: %s\\n· Certifique-se de que você está usando a versão mais recente do plugin e Kotatsu</string>\n    <string name=\"sort_order_desc\">Descendente</string>\n    <string name=\"download_new_chapters\">Baixar novos capítulos</string>\n    <string name=\"retry\">Tentar novamente</string>\n    <string name=\"connection_ok\">Conexão OK</string>\n    <string name=\"too_many_requests_message_retry\">Muitas solicitações. Tente novamente depois de %s</string>\n    <string name=\"unstable_feature\">Recurso instável</string>\n    <string name=\"popular_in_hour\">Popular no momento</string>\n    <string name=\"updated_long_ago\">Atualizado há muito tempo</string>\n    <string name=\"downloads_background\">Baixando em segundo plano</string>\n    <string name=\"content_type_novel\">Novel</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"minutes_seconds_short\">%1$d min %2$d s</string>\n    <string name=\"unpopular\">Impopular</string>\n    <string name=\"stuck\">Preso</string>\n    <string name=\"not_in_favorites\">Não está nos favoritos</string>\n    <string name=\"fixing_manga\">Corrigindo a obra</string>\n    <string name=\"fixed\">Corrigida</string>\n    <string name=\"no_fix_required\">Nenhuma correção necessária para \\\"%s\\\"</string>\n    <string name=\"handle_links_summary\">Lidar com links de mangá de aplicações externas (ex: navegador). Você talvez precise habilitar isso manualmente nas configurações da aplicação</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_one_shot\">One shot</string>\n    <string name=\"handle_links\">Lidar com links</string>\n    <string name=\"email\">Email</string>\n    <string name=\"demographics\">Demografia</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"access_denied_403\">Acesso negado (403)</string>\n    <string name=\"max_backups_count\">Número máximo de backups</string>\n    <string name=\"delete_old_backups\">Apagar backups antigos</string>\n    <string name=\"delete_old_backups_summary\">Automaticamente apagar backups antigos para liberar espaço</string>\n    <string name=\"screen_orientation\">Orientação de tela</string>\n    <string name=\"portrait\">Portrato</string>\n    <string name=\"landscape\">Paisagem</string>\n    <string name=\"content_type_game_cg\">Jogo CG</string>\n    <string name=\"captcha_required_message\">Essa fonte requisita que você resolva um captcha para continuar</string>\n    <string name=\"content_type_image_set\">Imagens</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"debug\">Depurar (para desenvolvedores)</string>\n    <string name=\"content_type_artist_cg\">Artista CG</string>\n    <string name=\"more_options\">Mais opções</string>\n    <string name=\"download_added\">Baixando</string>\n    <string name=\"chapter_selection_hint\">Você pode selecionar capítulos para baixar pressionando-os na lista de capítulos.</string>\n    <string name=\"destination_directory\">Diretório de destinação</string>\n    <string name=\"chapters_all\">Todos</string>\n    <string name=\"dont_allow\">Não permitir</string>\n    <string name=\"allow_always\">Permitir sempre</string>\n    <string name=\"allow_once\">Permitir uma vez</string>\n    <string name=\"ask_every_time\">Sempre pergunte</string>\n    <string name=\"download_over_cellular\">Baixando pelos dados móveis</string>\n    <string name=\"download_cellular_confirm\">Permitir baixar pelos dados móveis?</string>\n    <string name=\"error_connection_reset\">Redefinição de conexão pelo host remoto</string>\n    <string name=\"show_slider\">Mostrar controle deslizante</string>\n    <string name=\"backup_tg_check\">Verifique se a API funciona</string>\n    <string name=\"backup_tg_id_not_set\">O ID do bate-papo não está definido</string>\n    <string name=\"telegram_chat_id\">ID do bate-papo do telegram</string>\n    <string name=\"open_telegram_bot\">Abra o bot do Telegram</string>\n    <string name=\"clear_database\">Limpar banco de dados</string>\n    <string name=\"clear_database_summary\">Excluir informações sobre mangás que não estão sendo utilizados</string>\n    <string name=\"test_connection\">Teste de conexão</string>\n    <string name=\"backup_tg_echo\">Mensagem de teste</string>\n    <string name=\"translation\">Tradução</string>\n    <string name=\"send_backups_telegram\">Enviar backups no Telegram</string>\n    <string name=\"open_telegram_bot_summary\">Pressione para abrir o chat com o Kotatsu Backup Bot</string>\n    <string name=\"incognito\">Anônimo</string>\n    <string name=\"telegram_chat_id_summary\">Insira o ID do chat para onde os backups devem ser enviados</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"rating\">Avaliação</string>\n    <string name=\"source\">Fonte</string>\n    <string name=\"restoring_backup\">Restaurando backup</string>\n    <string name=\"error_disclaimer_manga\">Tente abrir a obra em um navegador web para ter certeza de que está disponível por esta fonte.</string>\n    <string name=\"error_disclaimer_app_outdated\">Parece que a sua versão do Kotatsu está desatualizada. Por favor instale a última versão para obter todos as correções disponíveis.</string>\n    <string name=\"search_everywhere\">Pesquise em todos os lugares</string>\n    <string name=\"clear_browser_data\">Limpar dados do navegador</string>\n    <string name=\"clear_browser_data_summary\">Limpa dados do navegador como cache e cookies. Aviso: Autorização em fontes de mangá podem se tornar inválidas</string>\n    <string name=\"error_details\">Detalhes do erro</string>\n    <string name=\"error_disclaimer_report\">Você pode submeter um bug report para os desenvolvedores. Isso nos ajudará investigar e solucionar o problema.</string>\n    <string name=\"no_write_permission_to_file\">Não há permissão de escrever um arquivo</string>\n    <string name=\"backup_restored_background\">O backup será restaurado em segundo plano</string>\n    <string name=\"search_disabled_sources\">Pesquisar em fontes desabilitadas</string>\n    <string name=\"reader_info_bar_transparent\">­Barra transparente de informações do leitor</string>\n    <string name=\"enable_all_sources\">Habilitar todas as fontes</string>\n    <string name=\"enable_all_sources_summary\">Todas as fontes disponíveis serão habilitadas permanentemente</string>\n    <string name=\"all_sources_enabled\">Todas as fontes estão habilitadas</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Capítulo %2$s</string>\n    <string name=\"chapter_number\">Capítulo %s</string>\n    <string name=\"unnamed_chapter\">Capítulo sem nome</string>\n    <string name=\"simple\">Simples</string>\n    <string name=\"chapters_and_pages\">Capítulos e páginas</string>\n    <string name=\"screen_rotation_locked\">Rotação de tela foi bloqueada</string>\n    <string name=\"screen_rotation_unlocked\">Rotação de tela foi desbloquada</string>\n    <string name=\"reader_controls_in_bottom_bar\">Controles do leitor na barra inferior</string>\n    <string name=\"pages_slider\">Barra deslizante de troca de página</string>\n    <string name=\"link_to_manga_on_s\">Link para a obra em %s</string>\n    <string name=\"link_to_manga_in_app\">Link para a obra no Kotatsu</string>\n    <string name=\"disable_captcha_notifications\">Desabilitar notificações de captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Você não receberá notificações sobre solucionar CAPTCHA para essa fonte, mas isso pode causar falha em operações de segundo plano (checagem de novos capítulos, obtenção de recomendações, etc)</string>\n    <string name=\"global_search\">Pesquisa global</string>\n    <string name=\"badges_in_lists\">Emblemas em listas</string>\n    <string name=\"tags_warnings\">Destacar gêneros perigosos</string>\n    <string name=\"tags_warnings_summary\">Destacar gêneros que podem ser inapropriados para a maioria dos usuários</string>\n    <string name=\"nsfw_16\">+16</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Mangás adultos não serão exibidos nas sugestões. Esta opção pode funcionar de forma imprecisa com algumas fontes</string>\n    <string name=\"include_disabled_sources\">Incluir fontes desabilitadas</string>\n    <string name=\"suggestions_disabled_sources_summary\">Mostrar sugestões de todas as fontes, incluindo as desabilitadas</string>\n    <string name=\"manga_override_hint\">Essas mudanças afetarão como a obra é exibida no aplicativo</string>\n    <string name=\"use_default_cover\">Usar a capa padrão</string>\n    <string name=\"pick_manga_page\">Escolher uma página da obra</string>\n    <string name=\"change_cover\">Mudar a capa</string>\n    <string name=\"incognito_for_nsfw\">Modo anônimo para mangás NSFW</string>\n    <string name=\"dont_ask_again\">Não pergunte novamente</string>\n    <string name=\"incognito_mode_hint_nsfw\">Esta obra pode conter conteúdo adulto. Você quer usar o modo anônimo?</string>\n    <string name=\"pick_custom_file\">Escolher um arquivo personalizado</string>\n    <string name=\"page_switch_timer\">A página mudará a cada ~%d segundos</string>\n    <string name=\"error_non_file_uri\">O caminho selecionado não pode ser usado porque não denota um arquivo ou diretório</string>\n    <string name=\"collapse\">Recolher</string>\n    <string name=\"adblock\">Bloquear anúncios no navegador</string>\n    <string name=\"adblock_summary\">Bloqueia anúncios no navegador embutido (beta)</string>\n    <string name=\"theme_name_expressive\">Expressivo (Teste)</string>\n    <string name=\"additional_action_required\">É necessária uma ação adicional</string>\n    <string name=\"hide_from_main_screen\">Ocultar na tela inicial</string>\n    <string name=\"collapse_long_description\">Encurtar descrições longas</string>\n    <string name=\"changelog\">Histórico de alterações</string>\n    <string name=\"changelog_summary\">Histórico de alterações das últimas versões</string>\n    <string name=\"expand\">Expandir</string>\n    <string name=\"creating_backup\">Criando o backup</string>\n    <string name=\"share_backup\">Compartilhar backup</string>\n    <string name=\"reader_navigation_inverted\">Inverter controles de navegação</string>\n    <string name=\"reader_navigation_inverted_summary\">Inverter o sentido do botão de volume e da navegação por tecla direcional (esquerda/cima/baixo/direita)</string>\n    <string name=\"reader_multitask\">Abrir leitor em uma tarefa separada</string>\n    <string name=\"reader_multitask_summary\">Permite que você mantenha vários leitores com mangás diferentes abertos ao mesmo tempo</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"book_effect\">Fundo amarelado (filtro azul)</string>\n    <string name=\"local_storage_cleanup\">Limpeza do armazenamento local</string>\n    <string name=\"packup_creation_failed\">Falha ao criar backup</string>\n    <string name=\"main_screen\">Tela principal</string>\n    <string name=\"main_screen_fab\">Mostrar botão Continuar flutuante</string>\n    <string name=\"main_screen_fab_summary\">Permite continuar a leitura com um clique. Este botão não aparecerá no modo anônimo ou quando o histórico estiver vazio</string>\n    <string name=\"error_corrupted_zip\">Arquivo ZIP corrompido (%s)</string>\n    <string name=\"discord_token_hint\">Cole seu Token do Discord aqui</string>\n    <string name=\"discord_rpc_summary\">Mostra seu status de leitura no Discord</string>\n    <string name=\"obtain\">Obtenha</string>\n    <string name=\"discord_rpc_description\">Lendo mangá no Kotatsu - um aplicativo de leitura de mangá</string>\n    <string name=\"reading_s\">Lendo %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Não usar RPC para conteúdo adulto</string>\n    <string name=\"invalid_token\">Token inválido: %s</string>\n    <string name=\"show_floating_control_button\">Mostrar botão de controle flutuante</string>\n    <string name=\"unavailable\">Indisponível</string>\n    <string name=\"manga_restricted_description\">Este mangá não está disponível para leitura nessa fonte. Tente pesquisar por ele em outras fontes ou abrir em um navegador para mais informações</string>\n    <string name=\"no_chapters_in_manga\">Este mangá não contém nenhum capítulo</string>\n    <string name=\"chapters_load_failed\">Falha ao carregar lista de capítulos</string>\n    <string name=\"telegram_integration\">Integração com Telegram</string>\n    <string name=\"discord_token\">Token do Discord</string>\n    <string name=\"discord_token_summary\">Insira seu Token do Discord para habilitar a Rich Presence</string>\n    <string name=\"discord_rpc\">Discord Rich Presence</string>\n    <string name=\"discord_token_description\">Insira seu Token do Discord ou clique em %s para obtê-lo usando o navegador</string>\n    <string name=\"read_on_s\">Leia em %s</string>\n    <string name=\"saved_filters\">Filtros salvos</string>\n    <string name=\"pull_to_prev_chapter\">Soltar para abrir o capítulo anterior</string>\n    <string name=\"pull_to_next_chapter\">Soltar para abrir o próximo capítulo</string>\n    <string name=\"pull_top_no_prev\">Capítulo anterior indisponível</string>\n    <string name=\"pull_bottom_no_next\">Próximo capítulo indisponível</string>\n    <string name=\"two_page_scroll_sensitivity\">Sensibilidade de Rolagem Dupla</string>\n    <string name=\"enable_pull_gesture_title\">Ativar gesto de puxar</string>\n    <string name=\"enable_pull_gesture_summary\">Puxe para trocar de capítulo em webtoons</string>\n    <string name=\"reader_chapter_toast\">Exibir mensagem ao mudar de capítulo</string>\n    <string name=\"reader_chapter_toast_summary\">Exibir título do capítulo ao mudar de capítulo</string>\n    <string name=\"rename\">Renomear</string>\n    <string name=\"save_filter\">Salvar filtro</string>\n    <string name=\"overwrite\">Sobrescrever</string>\n    <string name=\"filter_overwrite_confirm\">O filtro \\\"%s\\\" já existe. Deseja substituí-lo?</string>\n    <string name=\"storage_and_network\">Armazenamento e rede</string>\n    <string name=\"create_or_restore_backup\">Criar ou restaurar um backup</string>\n    <string name=\"privacy\">Privacidade</string>\n    <string name=\"enter_name\">Insira um nome</string>\n    <string name=\"test_parser\">Testar fonte de mangás</string>\n    <string name=\"data_removal\">Remoção de dados</string>\n    <string name=\"source_broken_warning\">Esta fonte de mangá está quebrada. Algumas funções podem não funcionar</string>\n    <string name=\"frequency_every_6_hours\">A cada 6 horas</string>\n    <string name=\"download_default_directory\">Diretório padrão onde baixar os mangás</string>\n    <string name=\"private_app_directory_warning\">Esse diretório e todo seu conteúdo será deletado se desinstalar a aplicação</string>\n    <string name=\"available_pattern\">%1$s disponível</string>\n    <string name=\"hide_empty_sources\">Esconder fontes vazias</string>\n    <string name=\"pinned_sources_only\">Apenas fontes fixadas</string>\n    <string name=\"auto_double_foldable\">Duas Páginas Automático em Tela Dobrável</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ro/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-ro/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"search\">Cauta</string>\n    <string name=\"newest\">Cele mai noi</string>\n    <string name=\"light\">Deschis</string>\n    <string name=\"dark\">Inchis</string>\n    <string name=\"share_image\">Distribuie imaginea</string>\n    <string name=\"pages\">Pagini</string>\n    <string name=\"_import\">Importeaza</string>\n    <string name=\"clear_pages_cache\">Goleste cacheul pagini</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"_continue\">Continua</string>\n    <string name=\"nothing_found\">Nu s-a găsit nimic</string>\n    <string name=\"favourites\">Favorite</string>\n    <string name=\"history\">Istoric</string>\n    <string name=\"error_occurred\">A aparut o eroare</string>\n    <string name=\"list_mode\">Modul lista</string>\n    <string name=\"remote_sources\">Surse manga</string>\n    <string name=\"loading_\">Se încarcă…</string>\n    <string name=\"computing_\">Se calculează…</string>\n    <string name=\"close\">Inchide</string>\n    <string name=\"retry\">Reîncearcă</string>\n    <string name=\"read\">Citeste</string>\n    <string name=\"you_have_not_favourites_yet\">Nici un favorit inca</string>\n    <string name=\"create_shortcut\">Creeaza scurtatura…</string>\n    <string name=\"share_s\">Distribuie %s</string>\n    <string name=\"manga_downloading_\">Se descarca…</string>\n    <string name=\"processing_\">Se proceseaza…</string>\n    <string name=\"download_complete\">Descarcat</string>\n    <string name=\"downloads\">Descarcari</string>\n    <string name=\"by_name\">Nume</string>\n    <string name=\"popular\">Popular</string>\n    <string name=\"updated\">Actualizat</string>\n    <string name=\"by_rating\">Evaluare</string>\n    <string name=\"sort_order\">Ordine de sortare</string>\n    <string name=\"filter\">Filtre</string>\n    <string name=\"theme\">Temă</string>\n    <string name=\"follow_system\">Urmeaza setarile implicite</string>\n    <string name=\"clear\">Goleste</string>\n    <string name=\"remove\">Sterge</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" sters din stocarea locala</string>\n    <string name=\"save_page\">Salveaza pagina</string>\n    <string name=\"page_saved\">Salvat</string>\n    <string name=\"delete\">Sterge</string>\n    <string name=\"operation_not_supported\">Această operațiune nu este acceptată</string>\n    <string name=\"text_file_not_supported\">Alege fie un fișier ZIP, fie un fișier CBZ.</string>\n    <string name=\"no_description\">Fara descriptie</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Mod citire</string>\n    <string name=\"grid_size\">Marime grila</string>\n    <string name=\"search_on_s\">Cauta pe %s</string>\n    <string name=\"delete_manga\">Sterge manga</string>\n    <string name=\"text_delete_local_manga\">Sterge permanent \\\"%s\\\" de pe acest dizpozitiv?</string>\n    <string name=\"reader_settings\">Setari cititor</string>\n    <string name=\"switch_pages\">Schimba paginile</string>\n    <string name=\"error\">Erroare</string>\n    <string name=\"clear_thumbs_cache\">Șterge memoria cache a thumbnails</string>\n    <string name=\"clear_search_history\">Sterge istoricul cautarilor</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"detailed_list\">Lista detaliata</string>\n    <string name=\"chapters\">Capitole</string>\n    <string name=\"local_storage\">Stocare Locala</string>\n    <string name=\"details\">Detalii</string>\n    <string name=\"grid\">Grilă</string>\n    <string name=\"network_error\">Eroare de rețea</string>\n    <string name=\"settings\">Setari</string>\n    <string name=\"chapter_d_of_d\">Capitolul %1$d din %2$d</string>\n    <string name=\"try_again\">Incearca din nou</string>\n    <string name=\"clear_history\">Sterge istoric</string>\n    <string name=\"history_is_empty\">Nici un istoric inca</string>\n    <string name=\"share\">Distribuie</string>\n    <string name=\"search_manga\">Cauta manga</string>\n    <string name=\"add_to_favourites\">Adaugă la favorite</string>\n    <string name=\"add_new_category\">Categorie noua</string>\n    <string name=\"add\">Adauga</string>\n    <string name=\"save\">Salveaza</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ru/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d элемент</item>\n        <item quantity=\"few\">%1$d элемента</item>\n        <item quantity=\"many\">%1$d элементов</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d новая глава</item>\n        <item quantity=\"few\">%1$d новых главы</item>\n        <item quantity=\"many\">%1$d новых глав</item>\n        <item quantity=\"other\">%1$d новых глав</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d глава</item>\n        <item quantity=\"few\">%1$d главы</item>\n        <item quantity=\"many\">%1$d глав</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d минуту назад</item>\n        <item quantity=\"few\">%1$d минуты назад</item>\n        <item quantity=\"many\">%1$d минут назад</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d час назад</item>\n        <item quantity=\"few\">%1$d часа назад</item>\n        <item quantity=\"many\">%1$d часов назад</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d день назад</item>\n        <item quantity=\"few\">%1$d дня назад</item>\n        <item quantity=\"many\">%1$d дней назад</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d месяц назад</item>\n        <item quantity=\"few\">%1$d месяца назад</item>\n        <item quantity=\"many\">%1$d месяцев назад</item>\n        <item quantity=\"other\">%1$d месяцев назад</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d час</item>\n        <item quantity=\"few\">%1$d часа</item>\n        <item quantity=\"many\">%1$d часов</item>\n        <item quantity=\"other\">%1$d часов</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d минута</item>\n        <item quantity=\"few\">%1$d минуты</item>\n        <item quantity=\"many\">%1$d минут</item>\n        <item quantity=\"other\">%1$d минут</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"local_storage\">Локальное хранилище</string>\n    <string name=\"favourites\">Избранное</string>\n    <string name=\"history\">История</string>\n    <string name=\"error_occurred\">Произошла ошибка</string>\n    <string name=\"network_error\">Ошибка сети</string>\n    <string name=\"details\">Подробности</string>\n    <string name=\"chapters\">Главы</string>\n    <string name=\"list\">Список</string>\n    <string name=\"detailed_list\">Подробный список</string>\n    <string name=\"grid\">Сетка</string>\n    <string name=\"list_mode\">Вид списка</string>\n    <string name=\"settings\">Настройки</string>\n    <string name=\"remote_sources\">Источники манги</string>\n    <string name=\"loading_\">Загрузка…</string>\n    <string name=\"chapter_d_of_d\">Глава %1$d из %2$d</string>\n    <string name=\"close\">Закрыть</string>\n    <string name=\"try_again\">Повторить</string>\n    <string name=\"clear_history\">Очистить историю</string>\n    <string name=\"nothing_found\">Ничего не найдено</string>\n    <string name=\"history_is_empty\">Истории пока нет</string>\n    <string name=\"read\">Читать</string>\n    <string name=\"you_have_not_favourites_yet\">Избранного пока нет</string>\n    <string name=\"add_to_favourites\">В избранное</string>\n    <string name=\"add_new_category\">Новая категория</string>\n    <string name=\"add\">Добавить</string>\n    <string name=\"save\">Сохранить</string>\n    <string name=\"share\">Поделиться</string>\n    <string name=\"create_shortcut\">Создать ярлык</string>\n    <string name=\"share_s\">Поделиться %s</string>\n    <string name=\"search\">Поиск</string>\n    <string name=\"search_manga\">Поиск манги</string>\n    <string name=\"manga_downloading_\">Загрузка…</string>\n    <string name=\"processing_\">Обработка…</string>\n    <string name=\"download_complete\">Загружено</string>\n    <string name=\"downloads\">Загрузки</string>\n    <string name=\"by_name\">Имя</string>\n    <string name=\"popular\">Популярная</string>\n    <string name=\"updated\">Обновлённая</string>\n    <string name=\"newest\">Новая</string>\n    <string name=\"by_rating\">Рейтинг</string>\n    <string name=\"sort_order\">Порядок сортировки</string>\n    <string name=\"filter\">Фильтр</string>\n    <string name=\"theme\">Тема</string>\n    <string name=\"light\">Светлая</string>\n    <string name=\"dark\">Тёмная</string>\n    <string name=\"follow_system\">Как в системе</string>\n    <string name=\"pages\">Страницы</string>\n    <string name=\"clear\">Очистить</string>\n    <string name=\"remove\">Удалить</string>\n    <string name=\"_s_deleted_from_local_storage\">«%s» удалено с устройства</string>\n    <string name=\"save_page\">Сохранить страницу</string>\n    <string name=\"page_saved\">Страница сохранена</string>\n    <string name=\"share_image\">Поделиться изображением</string>\n    <string name=\"_import\">Импорт</string>\n    <string name=\"delete\">Удалить</string>\n    <string name=\"operation_not_supported\">Операция не поддерживается</string>\n    <string name=\"text_file_not_supported\">Выберите файл в формате CBZ или ZIP.</string>\n    <string name=\"no_description\">Описание отсутствует</string>\n    <string name=\"clear_pages_cache\">Очистить кэш страниц</string>\n    <string name=\"text_file_sizes\">Б|кБ|МБ|ГБ|ТБ</string>\n    <string name=\"standard\">Стандартный</string>\n    <string name=\"webtoon\">Манхва</string>\n    <string name=\"read_mode\">Режим чтения</string>\n    <string name=\"grid_size\">Размер сетки</string>\n    <string name=\"search_on_s\">Поиск по %s</string>\n    <string name=\"delete_manga\">Удалить мангу</string>\n    <string name=\"text_delete_local_manga\">Удалить «%s» с устройства навсегда?</string>\n    <string name=\"reader_settings\">Настройки режима чтения</string>\n    <string name=\"switch_pages\">Листание страниц</string>\n    <string name=\"_continue\">Продолжить</string>\n    <string name=\"error\">Ошибка</string>\n    <string name=\"clear_thumbs_cache\">Очистить кэш миниатюр</string>\n    <string name=\"clear_search_history\">Очистить историю поиска</string>\n    <string name=\"search_history_cleared\">Очищено</string>\n    <string name=\"internal_storage\">Внутренний накопитель</string>\n    <string name=\"external_storage\">Внешний накопитель</string>\n    <string name=\"domain\">Домен</string>\n    <string name=\"app_update_available\">Доступна новая версия приложения</string>\n    <string name=\"open_in_browser\">Открыть в веб-браузере</string>\n    <string name=\"notifications\">Уведомления</string>\n    <string name=\"enabled_d_of_d\">Включено %1$d из %2$d</string>\n    <string name=\"new_chapters\">Новые главы</string>\n    <string name=\"download\">Загрузить</string>\n    <string name=\"notifications_settings\">Настройки уведомлений</string>\n    <string name=\"notification_sound\">Звук уведомления</string>\n    <string name=\"light_indicator\">Светодиодная индикация</string>\n    <string name=\"vibration\">Вибросигнал</string>\n    <string name=\"favourites_categories\">Категории избранного</string>\n    <string name=\"remove_category\">Удалить</string>\n    <string name=\"text_empty_holder_primary\">Как-то здесь пусто…</string>\n    <string name=\"text_search_holder_secondary\">Попробуйте переформулировать запрос.</string>\n    <string name=\"text_history_holder_primary\">То, что вы прочитаете, будет отображено здесь</string>\n    <string name=\"text_history_holder_secondary\">Найдите, что почитать в разделе «Обзор»</string>\n    <string name=\"text_local_holder_primary\">Сохраните что-нибудь</string>\n    <string name=\"text_local_holder_secondary\">Сохраните что-нибудь из онлайн-каталога или импортируйте из файла.</string>\n    <string name=\"manga_shelf\">Полка</string>\n    <string name=\"recent_manga\">Недавнее</string>\n    <string name=\"pages_animation\">Анимация пролистывания</string>\n    <string name=\"manga_save_location\">Папка загрузок</string>\n    <string name=\"not_available\">Недоступно</string>\n    <string name=\"cannot_find_available_storage\">Нет доступного хранилища</string>\n    <string name=\"other_storage\">Другое хранилище</string>\n    <string name=\"done\">Готово</string>\n    <string name=\"all_favourites\">Всё избранное</string>\n    <string name=\"favourites_category_empty\">Категория пуста</string>\n    <string name=\"read_later\">Прочитать позже</string>\n    <string name=\"updates\">Обновления</string>\n    <string name=\"text_feed_holder\">Новые главы того, что вы читаете, будут показаны здесь</string>\n    <string name=\"search_results\">Результаты поиска</string>\n    <string name=\"new_version_s\">Новая версия: %s</string>\n    <string name=\"size_s\">Размер: %s</string>\n    <string name=\"clear_updates_feed\">Очистить ленту обновлений</string>\n    <string name=\"updates_feed_cleared\">Очищено</string>\n    <string name=\"rotate_screen\">Повернуть экран</string>\n    <string name=\"update\">Обновить</string>\n    <string name=\"feed_will_update_soon\">Обновление скоро начнётся</string>\n    <string name=\"track_sources\">Следить за обновлениями</string>\n    <string name=\"dont_check\">Не проверять</string>\n    <string name=\"enter_password\">Введите пароль</string>\n    <string name=\"wrong_password\">Неверный пароль</string>\n    <string name=\"protect_application\">Защитить приложение</string>\n    <string name=\"protect_application_summary\">Запрашивать пароль при запуске Kotatsu</string>\n    <string name=\"repeat_password\">Повторите пароль</string>\n    <string name=\"passwords_mismatch\">Пароли не совпадают</string>\n    <string name=\"about\">О программе</string>\n    <string name=\"app_version\">Версия %s</string>\n    <string name=\"check_for_updates\">Проверить обновления</string>\n    <string name=\"no_update_available\">Нет доступных обновлений</string>\n    <string name=\"right_to_left\">Справа налево</string>\n    <string name=\"create_category\">Создать категорию</string>\n    <string name=\"scale_mode\">Масштабирование</string>\n    <string name=\"zoom_mode_fit_center\">Вписать в экран</string>\n    <string name=\"zoom_mode_fit_height\">Подогнать по высоте</string>\n    <string name=\"zoom_mode_fit_width\">Подогнать по ширине</string>\n    <string name=\"zoom_mode_keep_start\">Исходный размер</string>\n    <string name=\"black_dark_theme\">Чёрная</string>\n    <string name=\"black_dark_theme_summary\">Потребляет меньше энергии на экранах AMOLED</string>\n    <string name=\"backup_restore\">Резервное копирование и восстановление</string>\n    <string name=\"create_backup\">Создать резервную копию</string>\n    <string name=\"restore_backup\">Восстановить данные</string>\n    <string name=\"data_restored\">Восстановлено</string>\n    <string name=\"preparing_\">Подготовка…</string>\n    <string name=\"file_not_found\">Файл не найден</string>\n    <string name=\"data_restored_success\">Все данные были восстановлены</string>\n    <string name=\"data_restored_with_errors\">Данные были восстановлены, но возникли некоторые ошибки</string>\n    <string name=\"backup_information\">Вы можете создать резервную копию избранного и истории и потом восстановить их</string>\n    <string name=\"just_now\">Только что</string>\n    <string name=\"yesterday\">Вчера</string>\n    <string name=\"long_ago\">Давно</string>\n    <string name=\"group\">Группировать</string>\n    <string name=\"today\">Сегодня</string>\n    <string name=\"tap_to_try_again\">Попробовать ещё раз</string>\n    <string name=\"reader_mode_hint\">Выбранный режим будет сохранён для текущей манги</string>\n    <string name=\"silent\">Без звука</string>\n    <string name=\"captcha_required\">Необходимо пройти CAPTCHA</string>\n    <string name=\"captcha_solve\">Пройти</string>\n    <string name=\"clear_cookies\">Очистить куки</string>\n    <string name=\"cookies_cleared\">Все файлы cookie были удалены</string>\n    <string name=\"clear_feed\">Очистить ленту</string>\n    <string name=\"text_clear_updates_feed_prompt\">Удалить всю историю обновлений навсегда\\?</string>\n    <string name=\"check_for_new_chapters\">Проверка новых глав</string>\n    <string name=\"reverse\">В обратном порядке</string>\n    <string name=\"sign_in\">Войти</string>\n    <string name=\"auth_required\">Авторизуйтесь, чтобы просмотреть этот контент</string>\n    <string name=\"default_s\">По умолчанию: %s</string>\n    <string name=\"next\">Далее</string>\n    <string name=\"protect_application_subtitle\">Введите пароль для запуска приложения</string>\n    <string name=\"confirm\">Подтвердить</string>\n    <string name=\"password_length_hint\">Пароль должен состоять из 4 символов или более</string>\n    <string name=\"welcome\">Добро пожаловать</string>\n    <string name=\"text_clear_search_history_prompt\">Удалить все последние поисковые запросы навсегда\\?</string>\n    <string name=\"backup_saved\">Резервная копия сохранена</string>\n    <string name=\"tracker_warning\">Некоторые устройства имеют различное поведение системы, что может привести к нарушению фоновых задач.</string>\n    <string name=\"read_more\">Подробнее</string>\n    <string name=\"queued\">В очереди</string>\n    <string name=\"chapter_is_missing\">Глава отсутствует</string>\n    <string name=\"about_app_translation_summary\">Помочь с переводом приложения</string>\n    <string name=\"about_app_translation\">Перевод</string>\n    <string name=\"auth_complete\">Авторизация выполнена</string>\n    <string name=\"auth_not_supported_by\">Вход в %s не поддерживается</string>\n    <string name=\"text_clear_cookies_prompt\">Вы выйдете из всех источников</string>\n    <string name=\"genres\">Жанры</string>\n    <string name=\"state_finished\">Завершено</string>\n    <string name=\"state_ongoing\">Онгоинг</string>\n    <string name=\"system_default\">По умолчанию</string>\n    <string name=\"exclude_nsfw_from_history\">Исключить NSFW мангу из истории</string>\n    <string name=\"show_pages_numbers\">Нумерация страниц</string>\n    <string name=\"screenshots_policy\">Политика скриншотов</string>\n    <string name=\"screenshots_allow\">Разрешить</string>\n    <string name=\"screenshots_block_nsfw\">Запретить для NSFW</string>\n    <string name=\"screenshots_block_all\">Всегда блокировать</string>\n    <string name=\"suggestions\">Рекомендации</string>\n    <string name=\"suggestions_enable\">Включить рекомендации</string>\n    <string name=\"suggestions_summary\">Предлагать мангу на основе Ваших предпочтений</string>\n    <string name=\"suggestions_info\">Все данные анализируются только локально на этом устройстве и никуда не отправляются.</string>\n    <string name=\"text_suggestion_holder\">Начните читать мангу, чтобы получать персональные предложения</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Не предлагать NSFW мангу</string>\n    <string name=\"enabled\">Включено</string>\n    <string name=\"disabled\">Выкл.</string>\n    <string name=\"computing_\">Вычисление…</string>\n    <string name=\"reset_filter\">Сбросить фильтр</string>\n    <string name=\"onboard_text\">Выберите языки, на которых Вы хотите читать мангу. Это можно будет изменить позже в настройках.</string>\n    <string name=\"never\">Никогда</string>\n    <string name=\"only_using_wifi\">Только по Wi-Fi</string>\n    <string name=\"always\">Всегда</string>\n    <string name=\"preload_pages\">Предварительная загрузка страниц</string>\n    <string name=\"logged_in_as\">Вы авторизованы как %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Разные языки</string>\n    <string name=\"search_chapters\">Найти главу</string>\n    <string name=\"chapters_empty\">В этой манге нет глав</string>\n    <string name=\"appearance\">Оформление</string>\n    <string name=\"suggestions_updating\">Обновление рекомендаций</string>\n    <string name=\"suggestions_excluded_genres\">Исключить жанры</string>\n    <string name=\"suggestions_excluded_genres_summary\">Укажите жанры, которые Вы не хотите видеть в рекомендациях</string>\n    <string name=\"text_delete_local_manga_batch\">Удалить выбранную мангу с накопителя?</string>\n    <string name=\"removal_completed\">Удаление завершено</string>\n    <string name=\"download_slowdown\">Замедление загрузки</string>\n    <string name=\"download_slowdown_summary\">Помогает избежать блокировки IP-адреса</string>\n    <string name=\"local_manga_processing\">Обработка сохранённой манги</string>\n    <string name=\"chapters_will_removed_background\">Главы будут удалены в фоновом режиме</string>\n    <string name=\"hide\">Скрыть</string>\n    <string name=\"new_sources_text\">Доступны новые источники манги</string>\n    <string name=\"check_new_chapters_title\">Проверять новые главы и уведомлять о них</string>\n    <string name=\"show_notification_new_chapters_on\">Вы будете получать уведомления об обновлении манги, которую Вы читаете</string>\n    <string name=\"show_notification_new_chapters_off\">Вы не будете получать уведомления, но новые главы будут отображаться в списке</string>\n    <string name=\"notifications_enable\">Включить уведомления</string>\n    <string name=\"name\">Название</string>\n    <string name=\"edit\">Изменить</string>\n    <string name=\"edit_category\">Изменить категорию</string>\n    <string name=\"tracking\">Отслеживание</string>\n    <string name=\"empty_favourite_categories\">Нет категорий избранного</string>\n    <string name=\"bookmark_add\">Добавить закладку</string>\n    <string name=\"bookmark_remove\">Удалить закладку</string>\n    <string name=\"bookmarks\">Закладки</string>\n    <string name=\"bookmark_removed\">Закладка удалена</string>\n    <string name=\"bookmark_added\">Закладка добавлена</string>\n    <string name=\"undo\">Отменить</string>\n    <string name=\"removed_from_history\">Удалено из истории</string>\n    <string name=\"dns_over_https\">DNS поверх HTTPS</string>\n    <string name=\"default_mode\">Режим по умолчанию</string>\n    <string name=\"detect_reader_mode\">Автоопределение режима чтения</string>\n    <string name=\"detect_reader_mode_summary\">Автоматически определять, является ли манга манхвой</string>\n    <string name=\"disable_battery_optimization\">Отключить оптимизацию батареи</string>\n    <string name=\"disable_battery_optimization_summary\">Помогает с фоновой проверкой обновлений</string>\n    <string name=\"crash_text\">Что-то пошло не так. Пожалуйста, отправьте отчёт разработчикам, чтобы помочь всё исправить.</string>\n    <string name=\"send\">Отправить</string>\n    <string name=\"disable_all\">Отключить все</string>\n    <string name=\"use_fingerprint\">Использовать биометрические данные, если они доступны</string>\n    <string name=\"appwidget_shelf_description\">Манга из Вашего избранного</string>\n    <string name=\"appwidget_recent_description\">Манга, которую Вы недавно читали</string>\n    <string name=\"status_reading\">Читаю</string>\n    <string name=\"status_planned\">Запланировано</string>\n    <string name=\"status_on_hold\">Отложено</string>\n    <string name=\"status_dropped\">Заброшено</string>\n    <string name=\"status_completed\">Завершено</string>\n    <string name=\"show_reading_indicators_summary\">Показать процент прочитанного в истории и избранном</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Манга, помеченная как NSFW, не будет добавлена в историю и ваш прогресс чтения не будет сохранен</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"report\">Отчёт</string>\n    <string name=\"logout\">Выйти</string>\n    <string name=\"status_re_reading\">Перечитываю</string>\n    <string name=\"show_reading_indicators\">Показать индикаторы прогресса чтения</string>\n    <string name=\"data_deletion\">Удаление данных</string>\n    <string name=\"clear_cookies_summary\">Может помочь в случае каких-либо проблем. Все авторизации будут аннулированы</string>\n    <string name=\"show_all\">Показать все</string>\n    <string name=\"invalid_domain_message\">Неверное доменное имя</string>\n    <string name=\"back\">Назад</string>\n    <string name=\"select_range\">Выбрать диапазон</string>\n    <string name=\"history_cleared\">История очищена</string>\n    <string name=\"manage\">Настроить</string>\n    <string name=\"no_bookmarks_yet\">Закладок пока нет</string>\n    <string name=\"bookmarks_removed\">Закладки удалены</string>\n    <string name=\"no_manga_sources\">Нет источников манги</string>\n    <string name=\"no_manga_sources_text\">Включите источники манги для чтения онлайн</string>\n    <string name=\"random\">Случайная</string>\n    <string name=\"reorder\">Упорядочить</string>\n    <string name=\"empty\">Пусто</string>\n    <string name=\"explore\">Обзор</string>\n    <string name=\"confirm_exit\">Нажмите Назад ещё раз, чтобы выйти</string>\n    <string name=\"exit_confirmation_summary\">Нажмите Назад 2 раза для выхода из приложения</string>\n    <string name=\"other_cache\">Другой кэш</string>\n    <string name=\"options\">Опции</string>\n    <string name=\"not_found_404\">Контент не найден или был удален</string>\n    <string name=\"reader_info_pattern\">Гл. %1$d/%2$d Стр. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Показывать информационную панель в режиме чтения</string>\n    <string name=\"comics_archive\">Архив комиксов</string>\n    <string name=\"import_completed_hint\">Вы можете удалить исходный файл из хранилища, чтобы сэкономить место на нём</string>\n    <string name=\"incognito_mode\">Режим инкогнито</string>\n    <string name=\"sync\">Синхронизация</string>\n    <string name=\"sync_title\">Синхронизируйте ваши данные</string>\n    <string name=\"email_enter_hint\">Введите электронную почту, чтобы продолжить</string>\n    <string name=\"account_already_exists\">Аккаунт уже существует</string>\n    <string name=\"no_bookmarks_summary\">Вы можете создавать закладки во время чтения манги</string>\n    <string name=\"exit_confirmation\">Подтверждение выхода</string>\n    <string name=\"canceled\">Отменено</string>\n    <string name=\"clear_all_history\">Очистить всю историю</string>\n    <string name=\"last_2_hours\">Последние 2 часа</string>\n    <string name=\"categories_delete_confirm\">Вы уверены, что хотите удалить выбранные категории избранного? \\nВся манга в них будет потеряна и это не может быть отменено.</string>\n    <string name=\"pages_cache\">Кэш страниц</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"storage_usage\">Использование хранилища</string>\n    <string name=\"available\">Доступно</string>\n    <string name=\"saved_manga\">Сохранённая манга</string>\n    <string name=\"removed_from_favourites\">Удалено из избранного</string>\n    <string name=\"no_chapters\">Нет глав</string>\n    <string name=\"automatic_scroll\">Автоматическое листание</string>\n    <string name=\"folder_with_images\">Папка с изображениями</string>\n    <string name=\"importing_manga\">Импорт манги</string>\n    <string name=\"import_completed\">Импорт завершён</string>\n    <string name=\"import_will_start_soon\">Импорт скоро начнётся</string>\n    <string name=\"feed\">Лента</string>\n    <string name=\"manga_error_description_pattern\">Сведения об ошибке:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Попробуйте &lt;a href=%2$s&gt;открыть мангу в веб-браузере&lt;/a&gt;, чтобы убедиться, что она доступна в источнике&lt;br&gt;2. Убедитесь, что вы используете &lt;a href=kotatsu://about&gt;последнюю версию Kotatsu&lt;/a&gt;&lt;br&gt;3. Если возможно, отправьте отчёт об ошибке разработчикам.</string>\n    <string name=\"history_shortcuts\">Показывать ярлыки последней прочитанной манги</string>\n    <string name=\"history_shortcuts_summary\">Сделать недавно прочитанную мангу доступной по долгому нажатию на иконку приложения</string>\n    <string name=\"reader_control_ltr_summary\">Не настраивайте направление переключения страниц в режим чтения, например, нажатие правой клавиши всегда переключает на следующую страницу. Эта опция влияет только на аппаратные устройства ввода</string>\n    <string name=\"reader_control_ltr\">Эргономичное управление режимом чтения</string>\n    <string name=\"reset\">Сбросить</string>\n    <string name=\"discard\">Отклонить</string>\n    <string name=\"text_unsaved_changes_prompt\">Сохранить или отменить несохранённые изменения\\?</string>\n    <string name=\"contrast\">Контрастность</string>\n    <string name=\"brightness\">Яркость</string>\n    <string name=\"color_correction\">Цветокоррекция</string>\n    <string name=\"error_no_space_left\">Не осталось свободного места на накопителе</string>\n    <string name=\"webtoon_zoom\">Масштабирование в режиме манхвы</string>\n    <string name=\"reader_slider\">Показывать слайдер переключения страниц</string>\n    <string name=\"network_unavailable_hint\">Включите Wi-Fi или передачу данных, чтобы читать мангу онлайн</string>\n    <string name=\"network_unavailable\">Сеть недоступна</string>\n    <string name=\"clear_new_chapters_counters\">Также очистить информацию о новых главах</string>\n    <string name=\"server_error\">Внутренняя ошибка сервера (%1$d). Повторите попытку позже</string>\n    <string name=\"compact\">Компактный</string>\n    <string name=\"source_disabled\">Источник отключён</string>\n    <string name=\"prefetch_content\">Предварительная загрузка содержимого</string>\n    <string name=\"mark_as_current\">Пометить как текущую</string>\n    <string name=\"language\">Язык</string>\n    <string name=\"share_logs\">Поделиться логами</string>\n    <string name=\"enable_logging\">Включить логирование</string>\n    <string name=\"enable_logging_summary\">Записывать некоторые действия для отладки. Включайте только если знаете, что делаете</string>\n    <string name=\"show_suspicious_content\">Отображать сомнительный контент</string>\n    <string name=\"theme_name_dynamic\">Динамическая</string>\n    <string name=\"color_theme\">Цветовая схема</string>\n    <string name=\"show_in_grid_view\">Показать в виде сетки</string>\n    <string name=\"theme_name_miku\">Мику</string>\n    <string name=\"theme_name_asuka\">Аска</string>\n    <string name=\"theme_name_mion\">Мион</string>\n    <string name=\"theme_name_rikka\">Рикка</string>\n    <string name=\"theme_name_sakura\">Сакура</string>\n    <string name=\"services\">Службы</string>\n    <string name=\"theme_name_mamimi\">Мамими</string>\n    <string name=\"theme_name_kanade\">Канадэ</string>\n    <string name=\"scrobbling_empty_hint\">Чтобы отслеживать прогресс чтения, выберите «Меню» → «Отслеживать» на экране сведений о манге.</string>\n    <string name=\"nothing_here\">Здесь ничего нет</string>\n    <string name=\"download_started\">Загрузка началась</string>\n    <string name=\"allow_unstable_updates\">Разрешить нестабильные обновления</string>\n    <string name=\"allow_unstable_updates_summary\">Получать уведомления о нестабильных сборках</string>\n    <string name=\"settings_apply_restart_required\">Пожалуйста, перезапустите приложение, чтобы применить эти изменения</string>\n    <string name=\"user_agent\">Заголовок UserAgent</string>\n    <string name=\"got_it\">Понятно</string>\n    <string name=\"sources_reorder_tip\">Нажмите и удерживайте элемент, чтобы изменить их порядок</string>\n    <string name=\"comics_archive_import_description\">Вы можете выбрать один или несколько файлов .cbz или .zip, каждый файл будет распознан как отдельная манга.</string>\n    <string name=\"folder_with_images_import_description\">Вы можете выбрать каталог с архивами или изображениями. Каждый архив (или подкаталог) будет распознан как глава.</string>\n    <string name=\"speed\">Скорость</string>\n    <string name=\"show_on_shelf\">Показать на полке</string>\n    <string name=\"find_similar\">Найти похожие</string>\n    <string name=\"sync_auth_hint\">Вы можете войти в уже существующий аккаунт или же создать новый</string>\n    <string name=\"ignore_ssl_errors\">Игнорировать ошибки SSL</string>\n    <string name=\"mirror_switching\">Выбрать зеркало автоматически</string>\n    <string name=\"mirror_switching_summary\">Автоматически переключать домены для источников манги при ошибках, если доступны зеркала</string>\n    <string name=\"pause\">Пауза</string>\n    <string name=\"resume\">Возобновить</string>\n    <string name=\"remove_completed\">Удалить завершённые</string>\n    <string name=\"cancel_all\">Отменить все</string>\n    <string name=\"sync_settings\">Настройки синхронизации</string>\n    <string name=\"sync_host_description\">Вы можете использовать собственный сервер синхронизации или сервер по умолчанию. Не изменяйте эту настройку, если не уверены, что делаете.</string>\n    <string name=\"server_address\">Адрес сервера</string>\n    <string name=\"downloads_wifi_only_summary\">Останавливать загрузку при переключении на мобильную сеть</string>\n    <string name=\"paused\">Приостановлено</string>\n    <string name=\"downloads_wifi_only\">Скачивать только через Wi-Fi</string>\n    <string name=\"no_thanks\">Нет, спасибо</string>\n    <string name=\"enable\">Включить</string>\n    <string name=\"more\">Ещё</string>\n    <string name=\"cancel_all_downloads_confirm\">Все активные загрузки будут отменены, частично загруженные данные будут утеряны</string>\n    <string name=\"text_downloads_list_holder\">У Вас нет ни одной загрузки</string>\n    <string name=\"downloads_resumed\">Загрузки возобновлены</string>\n    <string name=\"downloads_paused\">Загрузки приостановлены</string>\n    <string name=\"downloads_removed\">Загрузки удалены</string>\n    <string name=\"downloads_cancelled\">Загрузки отменены</string>\n    <string name=\"suggestion_manga\">Рекомендация: %s</string>\n    <string name=\"suggestions_notifications_summary\">Иногда показывать уведомления с рекомендуемой мангой</string>\n    <string name=\"remove_completed_downloads_confirm\">Ваша история загрузок будет удалена навсегда. Загруженные файлы не будут затронуты</string>\n    <string name=\"suggestions_enable_prompt\">Хотите ли Вы получать персонализированные рекомендации манги\\?</string>\n    <string name=\"web_view_unavailable\">WebView недоступен: проверьте, установлен ли провайдер WebView</string>\n    <string name=\"clear_network_cache\">Очистить сетевой кэш</string>\n    <string name=\"address\">Адрес</string>\n    <string name=\"type\">Тип</string>\n    <string name=\"proxy\">Прокси</string>\n    <string name=\"port\">Порт</string>\n    <string name=\"invalid_value_message\">Неверное значение</string>\n    <string name=\"invalid_port_number\">Неверный номер порта</string>\n    <string name=\"username\">Имя пользователя</string>\n    <string name=\"password\">Пароль</string>\n    <string name=\"authorization_optional\">Авторизация (необязательна)</string>\n    <string name=\"downloaded\">Скачано</string>\n    <string name=\"images_proxy_title\">Прокси для оптимизации изображений</string>\n    <string name=\"images_procy_description\">Используйте службу wsrv.nl, чтобы уменьшить использование трафика и ускорить загрузку изображений, если это возможно</string>\n    <string name=\"invert_colors\">Инвертировать цвета</string>\n    <string name=\"webtoon_zoom_summary\">Разрешить жесты масштабирования в режиме манхвы</string>\n    <string name=\"network\">Сеть</string>\n    <string name=\"data_and_privacy\">Данные и конфиденциальность</string>\n    <string name=\"restore_summary\">Восстановить ранее созданную резервную копию</string>\n    <string name=\"reader_info_bar_summary\">Отображение текущего времени и прогресса чтения в верхней части экрана</string>\n    <string name=\"show_pages_numbers_summary\">Показывать номера страниц в нижнем углу</string>\n    <string name=\"clear_source_cookies_summary\">Очистить куки для указанного домена. В большинстве случаев это аннулирует авторизацию</string>\n    <string name=\"download_option_next_unread_n_chapters\">Первые непрочитанные %s</string>\n    <string name=\"download_option_all_unread\">Все непрочитанные главы</string>\n    <string name=\"download_option_all_chapters\">Все главы с переводом %s</string>\n    <string name=\"download_option_whole_manga\">Мангу целиком</string>\n    <string name=\"download_option_first_n_chapters\">Первые %s</string>\n    <string name=\"download_option_all_unread_b\">Все непрочитанные главы (%s)</string>\n    <string name=\"download_option_manual_selection\">Выбрать главы вручную</string>\n    <string name=\"no_access_to_file\">У вас нет доступа к этому файлу или каталогу</string>\n    <string name=\"local_manga_directories\">Локальные каталоги манги</string>\n    <string name=\"pick_custom_directory\">Выбрать пользовательский каталог</string>\n    <string name=\"this_month\">Этот месяц</string>\n    <string name=\"voice_search\">Голосовой поиск</string>\n    <string name=\"related_manga\">Похожая манга</string>\n    <string name=\"color_light\">Светлый</string>\n    <string name=\"color_dark\">Тёмный</string>\n    <string name=\"color_white\">Белый</string>\n    <string name=\"color_black\">Чёрный</string>\n    <string name=\"background\">Фон</string>\n    <string name=\"data_not_restored\">Данные не были восстановлены</string>\n    <string name=\"suggestions_wifi_only_summary\">Не обновлять рекомендации при использовании лимитного интернет-соединения</string>\n    <string name=\"order_added\">Добавлено</string>\n    <string name=\"data_not_restored_text\">Убедитесь, что вы выбрали правильный файл резервной копии</string>\n    <string name=\"description\">Описание</string>\n    <string name=\"manage_categories\">Управление категориями</string>\n    <string name=\"search_hint\">Введите название манги, жанр или название источника</string>\n    <string name=\"show\">Показать</string>\n    <string name=\"tracker_wifi_only_summary\">Не проверять наличие новых глав при использовании лимитного интернет-соединения</string>\n    <string name=\"progress\">Прогресс</string>\n    <string name=\"captcha_required_summary\">Для правильной работы %s требуется проверка captcha</string>\n    <string name=\"languages\">Языки</string>\n    <string name=\"unknown\">Неизвестный</string>\n    <string name=\"in_progress\">В процессе</string>\n    <string name=\"disable_nsfw\">Отключить NSFW</string>\n    <string name=\"too_many_requests_message\">Слишком много запросов. Попробуйте повторить позже</string>\n    <string name=\"related_manga_summary\">Показывать список связанной манги. В некоторых случаях список может быть нерелевантным или отсутствовать вовсе</string>\n    <string name=\"advanced\">Продвинутая</string>\n    <string name=\"manga_list\">Список манги</string>\n    <string name=\"error_corrupted_file\">Возвращаются неверные данные или файл поврежден</string>\n    <string name=\"on_device\">На устройстве</string>\n    <string name=\"moved_to_top\">Перемещено наверх</string>\n    <string name=\"items_limit_exceeded\">Больше нельзя добавлять элементы</string>\n    <string name=\"directories\">Каталоги</string>\n    <string name=\"main_screen_sections\">Разделы главного экрана</string>\n    <string name=\"to_top\">Наверх</string>\n    <string name=\"zoom_in\">Приблизить</string>\n    <string name=\"reader_zoom_buttons_summary\">Показывать или нет кнопки управления масштабом в правом нижнем углу</string>\n    <string name=\"reader_zoom_buttons\">Отображать кнопки масштабирования</string>\n    <string name=\"zoom_out\">Отдалить</string>\n    <string name=\"keep_screen_on\">Держать экран включённым</string>\n    <string name=\"keep_screen_on_summary\">Не выключать экран во время чтения манги</string>\n    <string name=\"state_abandoned\">Заброшена</string>\n    <string name=\"categories\">Категории</string>\n    <string name=\"list_options\">Параметры списка</string>\n    <string name=\"suggest_new_sources\">Предлагать новые источники после обновления приложения</string>\n    <string name=\"enhanced_colors_summary\">Уменьшает полосы, но может повлиять на производительность</string>\n    <string name=\"by_relevance\">Актуальность</string>\n    <string name=\"enhanced_colors\">32-битный цветовой режим</string>\n    <string name=\"suggest_new_sources_summary\">Предлагать источники манги, добавленные в последнем обновлении приложения</string>\n    <string name=\"online_variant\">Онлайн вариант</string>\n    <string name=\"frequency_every_day\">Каждый день</string>\n    <string name=\"backup_frequency\">Частота создания резервных копий</string>\n    <string name=\"periodic_backups_enable\">Включить резервное копирование по расписанию</string>\n    <string name=\"frequency_every_2_days\">Каждые 2 дня</string>\n    <string name=\"frequency_once_per_week\">Раз в неделю</string>\n    <string name=\"periodic_backups\">Резервное копирование по расписанию</string>\n    <string name=\"frequency_twice_per_month\">Дважды в месяц</string>\n    <string name=\"frequency_once_per_month\">Один раз в месяц</string>\n    <string name=\"backups_output_directory\">Каталог для сохранения резервных копий</string>\n    <string name=\"last_successful_backup\">Последняя резервная копия: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">Блокировка поворота экрана</string>\n    <string name=\"sources_catalog\">Каталог источников</string>\n    <string name=\"content_type_manga\">Манга</string>\n    <string name=\"content_type_hentai\">Хентай</string>\n    <string name=\"content_type_comics\">Комиксы</string>\n    <string name=\"catalog\">Каталог</string>\n    <string name=\"manage_sources\">Управление источниками</string>\n    <string name=\"no_manga_sources_found\">Не найдено доступных источников по вашему запросу</string>\n    <string name=\"manual\">Вручную</string>\n    <string name=\"source_enabled\">Источник включён</string>\n    <string name=\"disable_nsfw_summary\">Отключить NSFW источники и скрывать мангу для взрослых в списках, если это возможно</string>\n    <string name=\"no_manga_sources_catalog_text\">В этом разделе нет доступных источников, или все они могли быть уже добавлены.\n\\nСледите за обновлениями</string>\n    <string name=\"available_d\">Доступно: %1$d</string>\n    <string name=\"content_type_other\">Другое</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"state_paused\">Приостановлено</string>\n    <string name=\"error_multiple_states_not_supported\">Фильтрация по нескольким состояниям не поддерживается этим источником манги</string>\n    <string name=\"reader_optimize\">Уменьшение потребления памяти (бета)</string>\n    <string name=\"error_multiple_genres_not_supported\">Фильтрация по нескольким жанрам не поддерживается этим источником манги</string>\n    <string name=\"error_search_not_supported\">Поиск не поддерживается этим источником манги</string>\n    <string name=\"reader_optimize_summary\">Уменьшить качество закадровых страниц, чтобы использовать меньше памяти</string>\n    <string name=\"state\">Состояние</string>\n    <string name=\"error_filter_states_genre_not_supported\">Этот источник не поддерживает фильтрацию по жанрам и состояниям</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Этот источник не поддерживает фильтрацию по жанрам и локали</string>\n    <string name=\"apply\">Применить</string>\n    <string name=\"genres_search_hint\">Начните вводить название жанра</string>\n    <string name=\"globally\">Глобально</string>\n    <string name=\"downloads_settings_info\">Вы можете включить замедление загрузки для каждого источника манги индивидуально в настройках источника, если у вас возникли проблемы с блокировкой на стороне сервера</string>\n    <string name=\"this_manga\">Эта манга</string>\n    <string name=\"skip\">Пропустить</string>\n    <string name=\"color_correction_apply_text\">Эти настройки можно применить глобально или только к текущей манге. Если применяется глобально, отдельные настройки не будут переопределены.</string>\n    <string name=\"grayscale\">Оттенки серого</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Может помочь с запуском загрузки, если у вас с этим возникают проблемы</string>\n    <string name=\"sync_auth\">Войти в аккаунт синхронизации</string>\n    <string name=\"welcome_text\">Пожалуйста, выберите, какие источники вы хотите включить. Это можно изменить в настройках позже</string>\n    <string name=\"restore\">Восстановить</string>\n    <string name=\"backup_date_\">Дата создания резервной копии: %s</string>\n    <string name=\"by_name_reverse\">Имя (обратно)</string>\n    <string name=\"state_upcoming\">Ожидается</string>\n    <string name=\"genres_exclude\">Исключить жанры</string>\n    <string name=\"default_tab\">Вкладка по умолчанию</string>\n    <string name=\"content_rating\">Рейтинг контента</string>\n    <string name=\"rating_safe\">Безопасный</string>\n    <string name=\"rating_suggestive\">С намёками</string>\n    <string name=\"rating_adult\">Взрослый</string>\n    <string name=\"category_hidden_done\">Эта категория была скрыта с главного экрана и доступна через Меню → Управление категориями</string>\n    <string name=\"mark_as_completed\">Отметить как завершённое</string>\n    <string name=\"mark_as_completed_prompt\">Отметить выбранную мангу как полностью прочитанную?\n\\n\n\\nВнимание: текущий прогресс чтения будет потерян.</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"volume_unknown\">Неизвестный том</string>\n    <string name=\"volume_\">Том %d</string>\n    <string name=\"incognito_mode_hint\">Ваш прогресс чтения не сохранится</string>\n    <string name=\"vertical\">Вертикальный</string>\n    <string name=\"last_read\">Последнее прочитанное</string>\n    <string name=\"prev_page\">Предыдущая страница</string>\n    <string name=\"next_page\">Следующая страница</string>\n    <string name=\"tap_action\">Действие при касании</string>\n    <string name=\"long_tap_action\">Действие при длинном касании</string>\n    <string name=\"none\">Ничего</string>\n    <string name=\"show_menu\">Показать меню</string>\n    <string name=\"toggle_ui\">Показать/Скрыть интерфейс</string>\n    <string name=\"prev_chapter\">Предыдущая глава</string>\n    <string name=\"next_chapter\">Следующая глава</string>\n    <string name=\"switch_pages_volume_buttons\">Включить кнопки громкости</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Использовать кнопки громкости для переключения страниц</string>\n    <string name=\"reader_actions\">Действия в режиме чтения</string>\n    <string name=\"reader_actions_summary\">Настройка действий для сенсорных областей экрана</string>\n    <string name=\"email_password_enter_hint\">Введите свой адрес электронной почты и пароль, чтобы продолжить</string>\n    <string name=\"config_reset_confirm\">Сбросить настройки до значений по умолчанию? Это действие нельзя отменить.</string>\n    <string name=\"use_two_pages_landscape\">Использовать двухстраничный макет в альбомной ориентации (бета)</string>\n    <string name=\"default_webtoon_zoom_out\">Отдаление в режиме манхвы</string>\n    <string name=\"fullscreen_mode\">На весь экран</string>\n    <string name=\"reader_fullscreen_summary\">Скрывать интерфейс системы</string>\n    <string name=\"reading_time_estimation\">Отображать предполагаемое время чтения</string>\n    <string name=\"reading_time_estimation_summary\">Данное значение может быть неточным</string>\n    <string name=\"check_for_new_chapters_disabled\">Проверка новых глав отключена</string>\n    <string name=\"suggestions_unavailable_text\">Функция предложения отключена</string>\n    <string name=\"pages_saving\">Сохранение страниц</string>\n    <string name=\"ask_for_dest_dir_every_time\">Спрашивать папку для сохранения каждый раз</string>\n    <string name=\"remove_from_history\">Убрать из истории</string>\n    <string name=\"show_labels_in_navbar\">Показывать подписи на панели навигации</string>\n    <string name=\"default_page_save_dir\">Папка для сохранений по умолчанию</string>\n    <string name=\"location\">Расположение</string>\n    <string name=\"automatic\">Автоматический</string>\n    <string name=\"single_cbz_file\">Один файл CBZ</string>\n    <string name=\"multiple_cbz_files\">Несколько файлов CBZ</string>\n    <string name=\"preferred_download_format\">Предпочтительный формат загрузок</string>\n    <string name=\"other_manga\">Другая манга</string>\n    <string name=\"month\">Месяц</string>\n    <string name=\"all_time\">Всё время</string>\n    <string name=\"day\">День</string>\n    <string name=\"less_than_minute\">Меньше минуты</string>\n    <string name=\"statistics\">Статистика</string>\n    <string name=\"clear_stats\">Очистить статистику</string>\n    <string name=\"stats_cleared\">Статистика очищена</string>\n    <string name=\"clear_stats_confirm\">Вы действительно хотите очистить всю статистику чтения? Это действие нельзя отменить.</string>\n    <string name=\"three_months\">Три месяца</string>\n    <string name=\"empty_stats_text\">Статистика за выбранный период отсутствует</string>\n    <string name=\"pages_read_s\">Страниц прочитано: %s</string>\n    <string name=\"reading_stats\">Статистика чтения</string>\n    <string name=\"week\">Неделя</string>\n    <string name=\"alternatives\">Альтернативы</string>\n    <string name=\"manga_migration\">Перенос манги</string>\n    <string name=\"migration_completed\">Перенос завершён</string>\n    <string name=\"migrate\">Перенести</string>\n    <string name=\"migrate_confirmation\">Манга «%1$s» из «%2$s» будет заменена на «%3$s» из «%4$s» в вашей истории и в избранных (если есть)</string>\n    <string name=\"chapters_grid_view\">Вид сетки</string>\n    <string name=\"delete_read_chapters\">Удалить прочитанные главы</string>\n    <string name=\"no_chapters_deleted\">Ни одна глава не была удалена</string>\n    <string name=\"chapters_deleted_pattern\">Удалён %1$s, очищен %2$s</string>\n    <string name=\"delete_read_chapters_summary\">Удалите главы, которые вы уже прочитали, из локального хранилища, чтобы освободить место</string>\n    <string name=\"delete_read_chapters_auto\">Автоматически удалять прочитанные главы</string>\n    <string name=\"long_ago_read\">Давно прочитанная</string>\n    <string name=\"runs_on_app_start\">Запускается при запуске приложения</string>\n    <string name=\"split_by_translations\">Разделить по переводам</string>\n    <string name=\"unread\">Непрочитанная</string>\n    <string name=\"split_by_translations_summary\">Показывать главы с разными переводами отдельно, а не одним списком</string>\n    <string name=\"order_oldest\">Самые старые</string>\n    <string name=\"delete_read_chapters_prompt\">Это приведёт к безвозвратному удалению всех глав, помеченных как прочитанные, из вашего локального хранилища. Вы можете повторно загрузить их позже, но импортированные главы могут быть потеряны навсегда</string>\n    <string name=\"enable_source\">Включить источник</string>\n    <string name=\"unsupported_source\">Этот источник манги не поддерживается</string>\n    <string name=\"show_pages_thumbs\">Показать миниатюры страниц</string>\n    <string name=\"show_pages_thumbs_summary\">Включить вкладку «Страницы» на экране сведений</string>\n    <string name=\"error_no_data_received\">Данные не были получены с сервера</string>\n    <string name=\"hours_short\">%d ч.</string>\n    <string name=\"minutes_short\">%d мин.</string>\n    <string name=\"hours_minutes_short\">%1$d ч. %2$d мин.</string>\n    <string name=\"unsupported_backup_message\">Выберите правильный файл резервной копии Kotatsu</string>\n    <string name=\"last_used\">Последний использованный</string>\n    <string name=\"fix\">Исправить</string>\n    <string name=\"missing_storage_permission\">Нет разрешения на доступ к манге на внешнем хранилище</string>\n    <string name=\"show_updated\">Показать обновлённые</string>\n    <string name=\"webtoon_gaps\">Пробелы в режиме манхвы</string>\n    <string name=\"webtoon_gaps_summary\">Показывать вертикальные промежутки между страницами в режиме манхвы</string>\n    <string name=\"recent_queries\">Недавние запросы</string>\n    <string name=\"authors\">Авторы</string>\n    <string name=\"suggested_queries\">Предлагаемые запросы</string>\n    <string name=\"search_suggestions\">Поисковые предложения</string>\n    <string name=\"pin_navigation_ui\">Закрепить интерфейс навигации</string>\n    <string name=\"frequency_of_check\">Частота проверки</string>\n    <string name=\"more_frequently\">Чаще</string>\n    <string name=\"less_frequently\">Реже</string>\n    <string name=\"pin_navigation_ui_summary\">Не прятать навигационную панель и строку поиска при прокрутке</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"blocked_by_server_message\">Вы заблокированы сервером. Попробуйте использовать другое сетевое подключение (VPN, прокси и т. д.)</string>\n    <string name=\"disable_connectivity_check\">Отключить проверку подключения</string>\n    <string name=\"ignore_ssl_errors_summary\">Вы можете отключить проверку сертификата SSL, если при доступе к сетевым ресурсам возникают проблемы, связанные с SSL. Это может повлиять на вашу безопасность. После изменения этого параметра потребуется перезагрузка приложения.</string>\n    <string name=\"tracker_debug_info_summary\">Отладочная информация о фоновой проверке наличия новых глав</string>\n    <string name=\"tracker_debug_info\">Журнал проверки новых глав</string>\n    <string name=\"disable_connectivity_check_summary\">Пропустить проверки подключения в случае проблем с подключением (например, переход в автономный режим, даже если сеть подключена)</string>\n    <string name=\"disable_nsfw_notifications\">Отключить уведомления NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">Не показывать уведомления об обновлениях манги NSFW</string>\n    <string name=\"disable\">Откл.</string>\n    <string name=\"sources_disabled\">Источники отключены</string>\n    <string name=\"_new\">Новое</string>\n    <string name=\"all_languages\">Все языки</string>\n    <string name=\"screenshots_block_incognito\">Блокировать в режиме инкогнито</string>\n    <string name=\"source_pinned\">Источник закреплён</string>\n    <string name=\"sources_unpinned\">Источники откреплены</string>\n    <string name=\"sources_pinned\">Источники закреплены</string>\n    <string name=\"recent_sources\">Недавние источники</string>\n    <string name=\"crop_pages\">Обрезать страницы</string>\n    <string name=\"pin\">Закрепить</string>\n    <string name=\"unpin\">Открепить</string>\n    <string name=\"source_unpinned\">Источник откреплён</string>\n    <string name=\"image_server\">Сервер изображений</string>\n    <string name=\"percent_read\">Процент прочитанного</string>\n    <string name=\"chapters_read\">Глав прочитано</string>\n    <string name=\"percent_left\">Процент оставшегося</string>\n    <string name=\"chapters_left\">Глав осталось</string>\n    <string name=\"external_source\">Внешний/плагин</string>\n    <string name=\"plugin_incompatible\">Несовместимый плагин или внутренняя ошибка. Убедитесь, что вы используете последнюю версию плагина и Kotatsu</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Нет манги, соответствующей выбранным вами фильтрам</string>\n    <string name=\"connection_ok\">Соединение в порядке</string>\n    <string name=\"invalid_proxy_configuration\">Неверная конфигурация прокси</string>\n    <string name=\"invalid_server_address_message\">Неверный адрес сервера</string>\n    <string name=\"seconds_short\">%d с</string>\n    <string name=\"minutes_seconds_short\">%1$d м %2$d с</string>\n    <string name=\"show_quick_filters\">Показать быстрые фильтры</string>\n    <string name=\"show_quick_filters_summary\">Предоставляет возможность фильтровать списки манги по определённым параметрам</string>\n    <string name=\"not_in_favorites\">Нет в избранных</string>\n    <string name=\"updated_long_ago\">Обновлено давно</string>\n    <string name=\"unpopular\">Непопулярно</string>\n    <string name=\"low_rating\">Низкий рейтинг</string>\n    <string name=\"sort_order_asc\">По возрастанию</string>\n    <string name=\"sort_order_desc\">По убыванию</string>\n    <string name=\"retry\">Повторить</string>\n    <string name=\"too_many_requests_message_retry\">Слишком много запросов. Попробуйте ещё раз после %s</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"skip_all\">Пропустить все</string>\n    <string name=\"by_date\">Дата</string>\n    <string name=\"popularity\">Популярность</string>\n    <string name=\"stuck\">Зависло</string>\n    <string name=\"scrobbler_auth_required\">Войдите в %s, чтобы продолжить</string>\n    <string name=\"unstable_feature\">Нестабильная функция</string>\n    <string name=\"scrobbler_auth_intro\">Войдите, чтобы настроить интеграцию с %s. Это позволит вам отслеживать прогресс и статус чтения манги</string>\n    <string name=\"unstable_feature_summary\">Эта функция экспериментальная. Убедитесь, что у вас есть резервная копия, чтобы избежать потери данных</string>\n    <string name=\"content_type_novel\">Новелла</string>\n    <string name=\"content_type_manhua\">Маньхуа</string>\n    <string name=\"popular_in_hour\">Популярно в этот час</string>\n    <string name=\"popular_today\">Популярно сегодня</string>\n    <string name=\"popular_in_week\">Популярно на этой неделе</string>\n    <string name=\"popular_in_month\">Популярно в этом месяце</string>\n    <string name=\"year\">Год</string>\n    <string name=\"demographics\">Демография</string>\n    <string name=\"demographic_shounen\">Сёнэн</string>\n    <string name=\"demographic_seinen\">Сэйнэн</string>\n    <string name=\"demographic_josei\">Дзёсэй</string>\n    <string name=\"years\">Годы</string>\n    <string name=\"any\">Любой</string>\n    <string name=\"demographic_kodomo\">Кодомо</string>\n    <string name=\"content_type_one_shot\">Ваншот</string>\n    <string name=\"content_type_manhwa\">Манхва</string>\n    <string name=\"recently_added\">Недавно добавлено</string>\n    <string name=\"popular_in_year\">Популярно в этом году</string>\n    <string name=\"original_language\">Исходный язык</string>\n    <string name=\"added_long_ago\">Добавлено давно</string>\n    <string name=\"demographic_shoujo\">Сёдзё</string>\n    <string name=\"filter_search_warning\">Этот источник не поддерживает поиск с фильтрами. Ваши фильтры были очищены</string>\n    <string name=\"downloads_background\">Фоновые загрузки</string>\n    <string name=\"download_new_chapters\">Скачать новые главы</string>\n    <string name=\"manga_with_downloaded_chapters\">Манга с загруженными главами</string>\n    <string name=\"fixing_manga\">Исправление манги</string>\n    <string name=\"fixed\">Исправлено успешно</string>\n    <string name=\"manga_fix_prompt\">Эта функция найдёт альтернативные источники для выбранной манги. Задача займёт некоторое время и будет выполняться в фоновом режиме</string>\n    <string name=\"manga_replaced\">Манга «%1$s» (%2$s) заменена на «%3$s» (%4$s)</string>\n    <string name=\"no_fix_required\">Исправление для «%s» не требуется</string>\n    <string name=\"no_alternatives_found\">Альтернативы для «%s» не найдены</string>\n    <string name=\"content_type_doujinshi\">Додзинси</string>\n    <string name=\"content_type_image_set\">Набор изображений</string>\n    <string name=\"content_type_artist_cg\">Художник CG</string>\n    <string name=\"content_type_game_cg\">Игровая CG</string>\n    <string name=\"debug\">Отладка</string>\n    <string name=\"source_code\">Исходный код</string>\n    <string name=\"user_manual\">Руководство пользователя</string>\n    <string name=\"telegram_group\">Группа в Telegram</string>\n    <string name=\"error_image_format\">Неподдерживаемый формат изображения: %s</string>\n    <string name=\"genre\">Жанр</string>\n    <string name=\"save_manga\">Сохранить мангу</string>\n    <string name=\"start_download\">Начать загрузку</string>\n    <string name=\"save_manga_confirm\">Сохранить выбранную мангу? Это может занять трафик и место на диске</string>\n    <string name=\"landscape\">Ландшафтная</string>\n    <string name=\"screen_orientation\">Ориентация экрана</string>\n    <string name=\"portrait\">Портретная</string>\n    <string name=\"download_added\">Загрузка добавлена</string>\n    <string name=\"destination_directory\">Каталог назначения</string>\n    <string name=\"more_options\">Больше опций</string>\n    <string name=\"chapter_selection_hint\">Вы можете выбрать главы для загрузки, удерживая по элементу в списке глав.</string>\n    <string name=\"chapters_all\">Все</string>\n    <string name=\"download_over_cellular\">Загрузка через мобильную сеть</string>\n    <string name=\"download_cellular_confirm\">Разрешить загрузку через мобильную сеть?</string>\n    <string name=\"dont_allow\">Не разрешать</string>\n    <string name=\"allow_always\">Разрешить всегда</string>\n    <string name=\"allow_once\">Разрешить один раз</string>\n    <string name=\"ask_every_time\">Спрашивать каждый раз</string>\n    <string name=\"plugin_incompatible_with_cause\">Ошибка плагина: %s\\nУбедитесь, что вы используете последнюю версию плагина и Kotatsu</string>\n    <string name=\"error_not_image\">Неверный формат: ожидалось изображение, но получено %s</string>\n    <string name=\"max_backups_count\">Максимальное количество резервных копий</string>\n    <string name=\"access_denied_403\">Доступ отклонён (403)</string>\n    <string name=\"pages_saved\">Страницы сохранены</string>\n    <string name=\"delete_old_backups\">Удалить старые резервные копии</string>\n    <string name=\"delete_old_backups_summary\">Автоматически удалять старые резервные копии, чтобы освободить место для данных</string>\n    <string name=\"error_connection_reset\">Сброс соединения удалённым хостом</string>\n    <string name=\"show_slider\">Показать слайдер</string>\n    <string name=\"incognito\">Инкогнито</string>\n    <string name=\"backup_tg_check\">Проверьте, работает ли API</string>\n    <string name=\"backup_tg_echo\">Тестовое сообщение</string>\n    <string name=\"backup_tg_id_not_set\">ID чата не установлен</string>\n    <string name=\"telegram_chat_id\">ID чата Telegram</string>\n    <string name=\"open_telegram_bot\">Откройте Telegram-бота</string>\n    <string name=\"captcha_required_message\">Этот источник требует решения капчи, чтобы продолжить</string>\n    <string name=\"email\">Электронная почта</string>\n    <string name=\"clear_database\">Очистить базу данных</string>\n    <string name=\"clear_database_summary\">Удалить информацию о манге, которая не используется</string>\n    <string name=\"enable_all_sources\">Включить все источники манги</string>\n    <string name=\"test_connection\">Тестовое соединение</string>\n    <string name=\"send_backups_telegram\">Отправлять резервные копии в Telegram</string>\n    <string name=\"telegram_chat_id_summary\">Введите ID чата, куда следует отправлять резервные копии</string>\n    <string name=\"open_telegram_bot_summary\">Нажмите, чтобы открыть чат с Kotatsu Backup Bot</string>\n    <string name=\"handle_links\">Обработка ссылок</string>\n    <string name=\"enable_all_sources_summary\">Все доступные источники манги будут включены навсегда</string>\n    <string name=\"translation\">Перевод</string>\n    <string name=\"all_sources_enabled\">Все источники включены</string>\n    <string name=\"source\">Источник</string>\n    <string name=\"rating\">Рейтинг</string>\n    <string name=\"author\">Автор</string>\n    <string name=\"handle_links_summary\">Обработка ссылок на мангу из внешних приложений (например, веб-браузера). Вам также может потребоваться включить его вручную в системных настройках приложения</string>\n    <string name=\"backup_restored_background\">Резервная копия будет восстановлена в фоновом режиме</string>\n    <string name=\"restoring_backup\">Восстановление резервной копии</string>\n    <string name=\"reader_info_bar_transparent\">Прозрачная информационная панель чтения</string>\n    <string name=\"simple\">Простой</string>\n    <string name=\"reader_controls_in_bottom_bar\">Элементы управления чтением на нижней панели</string>\n    <string name=\"chapters_and_pages\">Главы и страницы</string>\n    <string name=\"pages_slider\">Ползунок переключения страниц</string>\n    <string name=\"screen_rotation_locked\">Поворот экрана был заблокирован</string>\n    <string name=\"screen_rotation_unlocked\">Поворот экрана был разблокирован</string>\n    <string name=\"global_search\">Глобальный поиск</string>\n    <string name=\"search_everywhere\">Поиск повсюду</string>\n    <string name=\"badges_in_lists\">Значки в списках</string>\n    <string name=\"error_details\">Подробности ошибки</string>\n    <string name=\"error_disclaimer_app_outdated\">Похоже, что Ваша версия Kotatsu устарела. Пожалуйста, установите последнюю версию, чтобы получить все доступные исправления.</string>\n    <string name=\"error_disclaimer_report\">Вы можете отправить отчёт об ошибке разработчикам. Это поможет нам исправить проблему.</string>\n    <string name=\"search_disabled_sources\">Поиск по отключённым источникам</string>\n    <string name=\"chapter_volume_number\">Том %1$s Глава %2$s</string>\n    <string name=\"chapter_number\">Глава %s</string>\n    <string name=\"unnamed_chapter\">Безымянная глава</string>\n    <string name=\"error_disclaimer_manga\">Попробуйте открыть мангу в браузере, чтобы убедиться, что она доступна в источнике.</string>\n    <string name=\"disable_captcha_notifications\">Отключить уведомления о CAPTCHA</string>\n    <string name=\"disable_captcha_notifications_summary\">Вы не будете получать уведомления о прохождении CAPTCHA для этого источника, но это может привести к тому, что фоновые операции перестанут работать (проверка новых глав, обновление рекомендаций и т. д.)</string>\n    <string name=\"tags_warnings\">Выделять опасные жанры</string>\n    <string name=\"tags_warnings_summary\">Выделять жанры, которые могут быть неприемлемы для большинства пользователей</string>\n    <string name=\"clear_browser_data\">Очистить данные браузера</string>\n    <string name=\"no_write_permission_to_file\">Нет прав на запись в файл</string>\n    <string name=\"link_to_manga_on_s\">Ссылка на мангу на %s</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Взрослая манга не будет отображаться в рекомендациях. Эта опция может не работать с некоторыми источниками</string>\n    <string name=\"include_disabled_sources\">Включить отключённые источники</string>\n    <string name=\"suggestions_disabled_sources_summary\">Отображать рекомендации из всех источников манги, включая отключённые</string>\n    <string name=\"link_to_manga_in_app\">Ссылка на мангу в Kotatsu</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"clear_browser_data_summary\">Удалить данные встроенного браузера, такие как кэш и куки. Внимание: авторизация в источниках манги может быть потеряна</string>\n    <string name=\"error_non_file_uri\">Выбранный путь не может быть использован, поскольку он не обозначает файл или каталог</string>\n    <string name=\"manga_override_hint\">Эти изменения будут влиять на то, как манга отображается в приложении</string>\n    <string name=\"change_cover\">Изменить обложку</string>\n    <string name=\"incognito_mode_hint_nsfw\">Эта манга может содержать контент для взрослых. Хотите использовать режим инкогнито?</string>\n    <string name=\"dont_ask_again\">Больше не спрашивать</string>\n    <string name=\"incognito_for_nsfw\">Режим инкогнито для NSFW-манги</string>\n    <string name=\"use_default_cover\">Использовать обложку по умолчанию</string>\n    <string name=\"pick_manga_page\">Выбрать страницу манги</string>\n    <string name=\"pick_custom_file\">Выбрать пользовательский файл</string>\n    <string name=\"page_switch_timer\">Страница будет меняться каждые ~%d секунд</string>\n    <string name=\"expand\">Расширять</string>\n    <string name=\"theme_name_expressive\">Выразительный (Тест)</string>\n    <string name=\"additional_action_required\">Требуются дополнительные действия</string>\n    <string name=\"hide_from_main_screen\">Скрыть с главного экрана</string>\n    <string name=\"collapse_long_description\">Свернуть длинное описание</string>\n    <string name=\"adblock_summary\">Блокировка рекламы во встроенном браузере (бета)</string>\n    <string name=\"adblock\">Блокировать рекламу в браузере</string>\n    <string name=\"collapse\">Свёртывание</string>\n    <string name=\"changelog\">Журнал изменений</string>\n    <string name=\"changelog_summary\">История изменений для недавно выпущенных версий</string>\n    <string name=\"reader_navigation_inverted\">Инвертировать элементы управления навигацией</string>\n    <string name=\"creating_backup\">Создание резервной копии</string>\n    <string name=\"share_backup\">Поделиться резервной копией</string>\n    <string name=\"reader_multitask\">Открывать читалку как отдельную задачу</string>\n    <string name=\"theme_name_itsuka\">Ицука</string>\n    <string name=\"theme_name_totoro\">Тоторо</string>\n    <string name=\"book_effect\">Желтоватый фон (фильтр синего)</string>\n    <string name=\"reader_navigation_inverted_summary\">Поменять местами направление кнопки регулировки громкости и аппаратной клавиши навигации (влево/вверх/вниз/вправо)</string>\n    <string name=\"reader_multitask_summary\">Позволяет держать открытыми несколько читалок с разной мангой одновременно</string>\n    <string name=\"local_storage_cleanup\">Очистка локального хранилища</string>\n    <string name=\"packup_creation_failed\">Не удалось создать резервную копию</string>\n    <string name=\"main_screen\">Главный экран</string>\n    <string name=\"main_screen_fab\">Показать плавающую кнопку «Продолжить»</string>\n    <string name=\"main_screen_fab_summary\">Позволяет продолжить чтение одним кликом. Эта кнопка не отображается в режиме инкогнито или при пустой истории</string>\n    <string name=\"error_corrupted_zip\">Повреждённый ZIP-архив (%s)</string>\n    <string name=\"discord_rpc\">Discord Rich Presence</string>\n    <string name=\"discord_token\">Токен Discord</string>\n    <string name=\"discord_token_summary\">Введите свой токен Discord, чтобы включить Rich Presence</string>\n    <string name=\"discord_token_description\">Введите свой токен Discord или нажмите %s, чтобы получить его через браузер</string>\n    <string name=\"discord_token_hint\">Вставьте свой токен Discord сюда</string>\n    <string name=\"discord_rpc_summary\">Покажите свой статус чтения в Discord</string>\n    <string name=\"obtain\">Получить</string>\n    <string name=\"discord_rpc_description\">Чтение манги в Kotatsu — приложение для чтения манги</string>\n    <string name=\"reading_s\">Чтение %s</string>\n    <string name=\"read_on_s\">Читать дальше %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Не использовать RPC для контента для взрослых</string>\n    <string name=\"invalid_token\">Недействительный токен: %s</string>\n    <string name=\"show_floating_control_button\">Показать плавающую кнопку управления</string>\n    <string name=\"unavailable\">Недоступно</string>\n    <string name=\"manga_restricted_description\">Эта манга недоступна для чтения в этом источнике. Попробуйте поискать её в других источниках или открыть в браузере, чтобы узнать больше</string>\n    <string name=\"no_chapters_in_manga\">Эта манга не содержит глав</string>\n    <string name=\"chapters_load_failed\">Не удалось загрузить список глав</string>\n    <string name=\"telegram_integration\">Интеграция с Telegram</string>\n    <string name=\"pull_to_prev_chapter\">Отпустите, чтобы открыть предыдущую главу</string>\n    <string name=\"pull_top_no_prev\">Нет предыдущей главы</string>\n    <string name=\"pull_bottom_no_next\">Нет следующей главы</string>\n    <string name=\"two_page_scroll_sensitivity\">Чувствительность прокрутки двух страниц</string>\n    <string name=\"enable_pull_gesture_title\">Включить жест перетягивания</string>\n    <string name=\"enable_pull_gesture_summary\">Используйте жест перетаскивания для переключения глав в вебтуне</string>\n    <string name=\"test_parser\">Проверка источников манги</string>\n    <string name=\"pull_to_next_chapter\">Отпустите, чтобы открыть следующую главу</string>\n    <string name=\"reader_chapter_toast\">Отображать сообщение о смене главы</string>\n    <string name=\"reader_chapter_toast_summary\">Показывать всплывающее сообщение с названием главы при переходе между главами</string>\n    <string name=\"rename\">Переименовать</string>\n    <string name=\"save_filter\">Сохранить фильтр</string>\n    <string name=\"overwrite\">Перезаписать</string>\n    <string name=\"filter_overwrite_confirm\">Фильтр с именем \\\"%s\\\" уже существует. Перезаписать?</string>\n    <string name=\"storage_and_network\">Хранилище и сеть</string>\n    <string name=\"create_or_restore_backup\">Создать или восстановить резервную копию</string>\n    <string name=\"data_removal\">Удаление данных</string>\n    <string name=\"privacy\">Приватность</string>\n    <string name=\"source_broken_warning\">Данный источник манги помечен как сломанный. Некоторые функции могут не работать</string>\n    <string name=\"download_default_directory\">Каталог по умолчанию для загрузки манги</string>\n    <string name=\"private_app_directory_warning\">Данный каталог вместе со всеми данными будет удалён при удалении приложения</string>\n    <string name=\"available_pattern\">%1$s доступно</string>\n    <string name=\"enter_name\">Введите имя</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-si/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"loading_\">පූරණය වෙමින්…</string>\n    <string name=\"settings\">සැකසුම්</string>\n    <string name=\"chapters\">පරිච්ඡේද</string>\n    <string name=\"details\">විස්තර</string>\n    <string name=\"error_occurred\">දෝෂයක් සිදුවී ඇත</string>\n    <string name=\"history\">ඉතිහාසය</string>\n    <string name=\"favourites\">ප්‍රියතමයන්</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-sr/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d ставка</item>\n        <item quantity=\"few\">%1$d ставке</item>\n        <item quantity=\"other\">%1$d ставки</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">пре %1$d минут</item>\n        <item quantity=\"few\">пре %1$d минута</item>\n        <item quantity=\"other\">пре %1$d минута</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">пре %1$d сат</item>\n        <item quantity=\"few\">пре %1$d сата</item>\n        <item quantity=\"other\">пре %1$d сати</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">пре %1$d дан</item>\n        <item quantity=\"few\">пре %1$d дана</item>\n        <item quantity=\"other\">пре %1$d дана</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d ново поглавље</item>\n        <item quantity=\"few\">%1$d нова поглавља</item>\n        <item quantity=\"other\">%1$d нових поглавља</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d поглавље</item>\n        <item quantity=\"few\">%1$d поглавља</item>\n        <item quantity=\"other\">%1$d поглавља</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">Пре %1$d месец</item>\n        <item quantity=\"few\">Пре %1$d месеца</item>\n        <item quantity=\"other\">Пре %1$d месеци</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d сат</item>\n        <item quantity=\"few\">%1$d сата</item>\n        <item quantity=\"other\">%1$d сати</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d минут</item>\n        <item quantity=\"few\">%1$d минута</item>\n        <item quantity=\"other\">%1$d минута</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-sr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"local_storage\">Локално складиште</string>\n    <string name=\"error_occurred\">Грешка се појавила</string>\n    <string name=\"favourites\">Омиљенo</string>\n    <string name=\"history\">Историја</string>\n    <string name=\"network_error\">Грешка на мрежи</string>\n    <string name=\"details\">Детаљи</string>\n    <string name=\"chapters\">Поглавља</string>\n    <string name=\"list\">Листа</string>\n    <string name=\"detailed_list\">Детаљна листа</string>\n    <string name=\"grid\">Табла</string>\n    <string name=\"list_mode\">Режим листе</string>\n    <string name=\"settings\">Подешавања</string>\n    <string name=\"remote_sources\">Извори манге</string>\n    <string name=\"loading_\">Учитавање…</string>\n    <string name=\"chapter_d_of_d\">Поглавље %1$d од %2$d</string>\n    <string name=\"close\">Затвори</string>\n    <string name=\"try_again\">Покушај поново</string>\n    <string name=\"clear_history\">Избриши историју</string>\n    <string name=\"nothing_found\">Ништа није пронађено</string>\n    <string name=\"history_is_empty\">Још нема историје</string>\n    <string name=\"read\">Читај</string>\n    <string name=\"you_have_not_favourites_yet\">Још нема омиљених</string>\n    <string name=\"add_new_category\">Нова категорија</string>\n    <string name=\"add\">Додај</string>\n    <string name=\"save\">Сачувај</string>\n    <string name=\"share\">Подели</string>\n    <string name=\"create_shortcut\">Направи пречицу</string>\n    <string name=\"invalid_port_number\">Неважећи број порта</string>\n    <string name=\"search_manga\">Претражи мангу</string>\n    <string name=\"by_name\">Имену</string>\n    <string name=\"clear_pages_cache\">Избриши кеш странице</string>\n    <string name=\"manga_downloading_\">Преузимање…</string>\n    <string name=\"switch_pages\">Листање страница</string>\n    <string name=\"delete_manga\">Избриши мангу</string>\n    <string name=\"search_on_s\">Претрага по %s</string>\n    <string name=\"internal_storage\">Унутрашња меморија</string>\n    <string name=\"external_storage\">Спољна меморија</string>\n    <string name=\"language\">Језик</string>\n    <string name=\"allow_unstable_updates\">Дозволи нестабилна ажурирања</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Стандардни</string>\n    <string name=\"webtoon\">Манхва</string>\n    <string name=\"read_mode\">Режим читања</string>\n    <string name=\"grid_size\">Величина мреже</string>\n    <string name=\"clear_thumbs_cache\">Избриши кеш сличица</string>\n    <string name=\"clear_search_history\">Избриши историју претраге</string>\n    <string name=\"app_update_available\">Доступна је нова верзија апликације</string>\n    <string name=\"open_in_browser\">Отвори у прегледачу</string>\n    <string name=\"notification_sound\">Звук обавештења</string>\n    <string name=\"vibration\">Вибрација</string>\n    <string name=\"text_empty_holder_primary\">Овде је некако празно…</string>\n    <string name=\"pages_animation\">Анимација превлачења странице</string>\n    <string name=\"about\">О апликацији</string>\n    <string name=\"app_version\">Верзија %s</string>\n    <string name=\"black_dark_theme\">Црна</string>\n    <string name=\"black_dark_theme_summary\">Троши мање енергије на AMOLED екранима</string>\n    <string name=\"show_pages_numbers\">Приказ бројева на страницама</string>\n    <string name=\"preload_pages\">Преучитавање страница</string>\n    <string name=\"appearance\">Изглед</string>\n    <string name=\"check_new_chapters_title\">Провери да ли постоје нова поглавља и обавести ме</string>\n    <string name=\"show_notification_new_chapters_on\">Добићеш обавештења о ажурирањима манге коју читаш</string>\n    <string name=\"default_mode\">Подразумевани режим</string>\n    <string name=\"network_unavailable\">Мрежа није доступна</string>\n    <string name=\"network_unavailable_hint\">Укључите Wi-Fi или мобилну мрежу да бисте читали мангу на мрежи</string>\n    <string name=\"allow_unstable_updates_summary\">Добијај обавештења о нестабилним верзијама апликације</string>\n    <string name=\"data_and_privacy\">Подаци и приватност</string>\n    <string name=\"network\">Мрежа</string>\n    <string name=\"show_pages_numbers_summary\">Прикажи бројеве страница у доњем углу</string>\n    <string name=\"images_proxy_title\">Посредни послуживач за оптимизацију слика</string>\n    <string name=\"username\">Корисничко име</string>\n    <string name=\"reader_info_bar_summary\">Прикажи тренутно време и напредак читања на врху екрана</string>\n    <string name=\"authorization_optional\">Ауторизација (опционално)</string>\n    <string name=\"images_procy_description\">Користите wsrv.nl услуга за смањење употребе саобраћаја и убрзавање учитавања слика ако је могуће</string>\n    <string name=\"computing_\">Рачунање…</string>\n    <string name=\"search\">Претражи</string>\n    <string name=\"by_rating\">Оцени</string>\n    <string name=\"sort_order\">Распореди по</string>\n    <string name=\"newest\">Најновије</string>\n    <string name=\"light\">Светла</string>\n    <string name=\"dark\">Мрачна</string>\n    <string name=\"follow_system\">Прати систем</string>\n    <string name=\"filter\">Филтер</string>\n    <string name=\"theme\">Тема</string>\n    <string name=\"pages\">Странице</string>\n    <string name=\"operation_not_supported\">Ова операција није подржана</string>\n    <string name=\"error\">Грешка</string>\n    <string name=\"new_chapters\">Нова поглавља</string>\n    <string name=\"notifications_settings\">Подешавања обавештења</string>\n    <string name=\"new_version_s\">Нова верзија: %s</string>\n    <string name=\"about_app_translation_summary\">Преведи ову апликацију</string>\n    <string name=\"about_app_translation\">Превод</string>\n    <string name=\"screenshots_policy\">Политика снимања екрана</string>\n    <string name=\"clear_network_cache\">Избриши мрежну кеш меморију</string>\n    <string name=\"password\">Лозинка</string>\n    <string name=\"show_in_grid_view\">Прикажи као мрежу</string>\n    <string name=\"page_saved\">Страница је сачувана</string>\n    <string name=\"text_delete_local_manga\">Трајно избриши \\\"%s\\\" са уређаја?</string>\n    <string name=\"processing_\">Обрада…</string>\n    <string name=\"download_complete\">Преузето</string>\n    <string name=\"downloads\">Преузимања</string>\n    <string name=\"popular\">Популарности</string>\n    <string name=\"updated\">Ажурирано</string>\n    <string name=\"remove\">Уклони</string>\n    <string name=\"save_page\">Сачувај страницу</string>\n    <string name=\"share_image\">Подели слику</string>\n    <string name=\"text_file_not_supported\">Изаберите ZIP датотеку или CBZ датотеку.</string>\n    <string name=\"no_description\">Нема описа</string>\n    <string name=\"reader_settings\">Подешавања читача</string>\n    <string name=\"notifications\">Обавештења</string>\n    <string name=\"pages_cache\">Кеш страница</string>\n    <string name=\"manga_shelf\">Полица</string>\n    <string name=\"check_for_updates\">Провери ажурирања</string>\n    <string name=\"feed\">Новости</string>\n    <string name=\"services\">Услуге</string>\n    <string name=\"show_on_shelf\">Прикажи на полици</string>\n    <string name=\"explore\">Истражи</string>\n    <string name=\"options\">Опције</string>\n    <string name=\"add_to_favourites\">Додај у Омиљене</string>\n    <string name=\"text_history_holder_secondary\">Пронађи ствари за читање у одељку „Истражи“</string>\n    <string name=\"light_indicator\">Показатељ ЛЕД светла</string>\n    <string name=\"favourites_categories\">Омиљене категорије</string>\n    <string name=\"remove_category\">Уклони</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Омогућено је %1$d од %2$d</string>\n    <string name=\"clear\">Избриши</string>\n    <string name=\"text_history_holder_primary\">Све што читаш биће приказано овде</string>\n    <string name=\"delete\">Избриши</string>\n    <string name=\"search_history_cleared\">Очишћено</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" избрисано из локалне меморије</string>\n    <string name=\"_import\">Увези</string>\n    <string name=\"download\">Преузми</string>\n    <string name=\"share_s\">Подели %s</string>\n    <string name=\"domain\">Домен</string>\n    <string name=\"text_search_holder_secondary\">Покушајте да преформулишете захтев.</string>\n    <string name=\"_continue\">Настави</string>\n    <string name=\"text_local_holder_primary\">Прво сачувајте нешто</string>\n    <string name=\"updates\">Ажурирања</string>\n    <string name=\"languages\">Језици</string>\n    <string name=\"zoom_in\">Увећај</string>\n    <string name=\"captcha_required_summary\">%s захтева да се реши формулар (captcha) да би исправно функционисао</string>\n    <string name=\"status_re_reading\">Поново читам</string>\n    <string name=\"sources_catalog\">Каталог извора</string>\n    <string name=\"download_option_all_unread\">Сва непрочитана поглавља</string>\n    <string name=\"detect_reader_mode\">Самостално уочи режим читача</string>\n    <string name=\"frequency_every_day\">Сваки дан</string>\n    <string name=\"download_started\">Преузимање је почело</string>\n    <string name=\"categories\">Категорије</string>\n    <string name=\"tracking\">Пратим</string>\n    <string name=\"progress\">Напредку</string>\n    <string name=\"cancel_all\">Откажи све</string>\n    <string name=\"sync_host_description\">Можеш да користиш послуживач за синхронизацију који се самостално хостује или подразумевани. Не мењај ово ако ниси сигуран шта радиш.</string>\n    <string name=\"error_corrupted_file\">Враћени су неважећи подаци или је датотека оштећена</string>\n    <string name=\"all_favourites\">Свe омиљено</string>\n    <string name=\"email_enter_hint\">Унеси своју адресу е-поште за наставак</string>\n    <string name=\"pick_custom_directory\">Изабери прилагођени директоријум</string>\n    <string name=\"no_chapters\">Нема поглавља</string>\n    <string name=\"list_options\">Листа опција</string>\n    <string name=\"related_manga_summary\">Прикажи листу сродних Манги. У неким случајевима може бити нетачна или је неће бити</string>\n    <string name=\"remove_completed_downloads_confirm\">Ваша историја преузимања ће бити трајно избрисана. Ниједна преузета датотека неће бити избрисана</string>\n    <string name=\"theme_name_dynamic\">Динамичка</string>\n    <string name=\"text_clear_cookies_prompt\">Бићеш одјављен са свих извора</string>\n    <string name=\"clear_cookies\">Избриши колачиће</string>\n    <string name=\"reader_zoom_buttons_summary\">Прикажи дугмад за управљање увећања и смањивања у доњем десном углу</string>\n    <string name=\"content_type_manga\">Манга</string>\n    <string name=\"reset\">Поништи</string>\n    <string name=\"disable_all\">Онемогући све</string>\n    <string name=\"tracker_wifi_only_summary\">Не проверавај да ли постоје нова поглавља користећи мрежне везе са ограничењем</string>\n    <string name=\"error_multiple_states_not_supported\">Овај извор манга не подржава филтрирање према више стања</string>\n    <string name=\"order_added\">Додатим</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"enable_logging\">Омогући дијагностику</string>\n    <string name=\"on_device\">На уређају</string>\n    <string name=\"download_option_whole_manga\">Целу Мангу</string>\n    <string name=\"settings_apply_restart_required\">Поново покрени апликацију да би применио ове промене</string>\n    <string name=\"rotate_screen\">Окрени екран</string>\n    <string name=\"text_clear_updates_feed_prompt\">Трајно избриши сву историју ажурирања?</string>\n    <string name=\"source_disabled\">Извор је онемогућен</string>\n    <string name=\"backup_frequency\">Учесталост прављења резервне копије</string>\n    <string name=\"clear_cookies_summary\">Може помоћи у случају неких проблема. Сва овлашћења ће бити поништена</string>\n    <string name=\"enable_logging_summary\">Снима неке радње у сврху отклањања грешака. Немојте ово укључивати ако нисте сигурни шта радите</string>\n    <string name=\"suggestions_enable\">Омогући предлоге</string>\n    <string name=\"clear_feed\">Очисти новости</string>\n    <string name=\"welcome\">Добродошли</string>\n    <string name=\"chapters_empty\">Нема поглавља у овој манги</string>\n    <string name=\"content_type_hentai\">Хентаи</string>\n    <string name=\"clear_source_cookies_summary\">Избриши колачиће само за одређени домен. У већини случајева поништава овлашћење</string>\n    <string name=\"no_update_available\">Нема доступних ажурирања</string>\n    <string name=\"clear_all_history\">Избриши сву историју</string>\n    <string name=\"data_deletion\">Брисање података</string>\n    <string name=\"history_shortcuts\">Прикажи недавне пречице за мангу</string>\n    <string name=\"downloads_wifi_only_summary\">Зауставља преузимање када пређеш на мобилну мрежу</string>\n    <string name=\"suggest_new_sources\">Предложи нове изворе након ажурирања апликације</string>\n    <string name=\"import_completed\">Увоз је завршен</string>\n    <string name=\"show_reading_indicators\">Прикажи показивач током читања</string>\n    <string name=\"read_later\">За касније</string>\n    <string name=\"backup_saved\">Резервна копија је сачувана</string>\n    <string name=\"local_manga_processing\">Сачувана обрада манге</string>\n    <string name=\"user_agent\">Заглавље КорисничкогАгент-а</string>\n    <string name=\"cannot_find_available_storage\">Нема слободног простора</string>\n    <string name=\"create_backup\">Направи резервну копију података</string>\n    <string name=\"tap_to_try_again\">Додирни за поновни покушај</string>\n    <string name=\"ignore_ssl_errors\">Занемари SSL грешке</string>\n    <string name=\"auth_required\">Пријави се да би видео овај садржај</string>\n    <string name=\"next\">Следеће</string>\n    <string name=\"restore_backup\">Обнови из резервне копије</string>\n    <string name=\"reader_info_bar\">Прикажи траку са информацијама у читачу</string>\n    <string name=\"password_length_hint\">Лозинка мора имати 4 знака или више</string>\n    <string name=\"periodic_backups_enable\">Омогући повремене резервне копије</string>\n    <string name=\"server_address\">Адреса послуживача</string>\n    <string name=\"content_type_comics\">Стрипови</string>\n    <string name=\"moved_to_top\">Премештено је на врх</string>\n    <string name=\"text_feed_holder\">Нова поглавља онога што читаш су приказана овде</string>\n    <string name=\"text_suggestion_holder\">Почните да читате мангу и добићете персонализоване предлоге</string>\n    <string name=\"find_similar\">Пронађи сличне</string>\n    <string name=\"show_notification_new_chapters_off\">Нећеш добијати обавештења, али ће нова поглавља бити истакнута на листама</string>\n    <string name=\"storage_usage\">Коришћење складиштења</string>\n    <string name=\"data_not_restored_text\">Увери се да си изабрао исправну датотеку резервне копије</string>\n    <string name=\"data_restored\">Обновљено</string>\n    <string name=\"favourites_category_empty\">Празна категорија</string>\n    <string name=\"catalog\">Каталог</string>\n    <string name=\"protect_application_subtitle\">Унеси лозинку да би покренуо апликацију</string>\n    <string name=\"theme_name_sakura\">Сакура</string>\n    <string name=\"unknown\">Непознато</string>\n    <string name=\"in_progress\">У току</string>\n    <string name=\"suggestions\">Предлози</string>\n    <string name=\"download_option_manual_selection\">Изабери поглавља ручно</string>\n    <string name=\"enhanced_colors_summary\">Смањује траке, али може утицати на учинак</string>\n    <string name=\"importing_manga\">Увези манге</string>\n    <string name=\"enabled\">Омогућено</string>\n    <string name=\"pause\">Заустави</string>\n    <string name=\"clear_new_chapters_counters\">Такође очисти информације о новим поглављима</string>\n    <string name=\"text_clear_search_history_prompt\">Да ли желиш да трајно избришеш све недавне упите за претрагу?</string>\n    <string name=\"nothing_here\">Овде нема ничега</string>\n    <string name=\"remove_completed\">Уклањање је завршено</string>\n    <string name=\"items_limit_exceeded\">Немогуће је додати више ставки</string>\n    <string name=\"frequency_every_2_days\">Свака 2 дана</string>\n    <string name=\"manga_save_location\">Фасцикла за преузимања</string>\n    <string name=\"status_reading\">Читам</string>\n    <string name=\"auth_complete\">Овлашћени</string>\n    <string name=\"suggestions_notifications_summary\">Понекад ми прикажи обавештења са предложеном Мангом</string>\n    <string name=\"updates_feed_cleared\">Избрисано</string>\n    <string name=\"invalid_value_message\">Погрешна вредност</string>\n    <string name=\"downloads_cancelled\">Преузимања су отказана</string>\n    <string name=\"webtoon_zoom\">Webtoon увећање</string>\n    <string name=\"various_languages\">Разни језици</string>\n    <string name=\"removal_completed\">Уклањање је завршено</string>\n    <string name=\"theme_name_miku\">Мику</string>\n    <string name=\"edit\">Уреди</string>\n    <string name=\"reader_optimize\">Смањи потрошњу меморије (бета)</string>\n    <string name=\"captcha_required\">Потребна је CAPTCHA</string>\n    <string name=\"data_not_restored\">Подаци нису враћени</string>\n    <string name=\"manage_sources\">Управљај изворима</string>\n    <string name=\"directories\">Директоријуми</string>\n    <string name=\"local_manga_directories\">Локални директоријуми Манги</string>\n    <string name=\"manage_categories\">Управљај категоријама</string>\n    <string name=\"update\">Ажурирај</string>\n    <string name=\"scrobbling_empty_hint\">Да бисте пратили напредак читања, изаберите Изборник → Прати на екрану са детаљима манге.</string>\n    <string name=\"removed_from_history\">Уклоњено из историје</string>\n    <string name=\"no_manga_sources_found\">Вашим упитом није пронађен ниједан доступан извор манге</string>\n    <string name=\"crash_text\">Нешто није у реду. Пошаљите извештај о грешци програмерима да бисте нам помогли да то поправимо.</string>\n    <string name=\"color_light\">Светла</string>\n    <string name=\"detect_reader_mode_summary\">Аутоматски откриј да ли је манга webtoon</string>\n    <string name=\"web_view_unavailable\">WebView није доступан: провери да ли је WebView добављач инсталиран</string>\n    <string name=\"port\">Прикључак</string>\n    <string name=\"not_found_404\">Садржај није пронађен или је уклоњен</string>\n    <string name=\"feed_will_update_soon\">Ажурирање новости ће ускоро почети</string>\n    <string name=\"appwidget_recent_description\">Ваша недавно прочитана манга</string>\n    <string name=\"got_it\">Разумем</string>\n    <string name=\"type\">Тип</string>\n    <string name=\"bookmark_remove\">Уклони обележивач</string>\n    <string name=\"search_hint\">Унеси наслов манге, жанр или назив извора</string>\n    <string name=\"frequency_once_per_week\">Једном седмично</string>\n    <string name=\"description\">Опис</string>\n    <string name=\"disable_battery_optimization_summary\">Помаже код провера ажурирања у позадини</string>\n    <string name=\"auth_not_supported_by\">Пријављивање на %s није подржано</string>\n    <string name=\"status_on_hold\">На чекању</string>\n    <string name=\"last_2_hours\">Од последња 2 сата</string>\n    <string name=\"periodic_backups\">Повремене резервне копије</string>\n    <string name=\"reader_zoom_buttons\">Прикажи дугмад за увећање и смањивање</string>\n    <string name=\"name\">Име</string>\n    <string name=\"sources_reorder_tip\">Додирни и задржи ставку да би је преместио</string>\n    <string name=\"edit_category\">Уреди категорију</string>\n    <string name=\"resume\">Настави</string>\n    <string name=\"server_error\">Грешка на послуживачу (%1$d). Покушај поново касније</string>\n    <string name=\"bookmark_removed\">Обележивач је уклоњен</string>\n    <string name=\"select_range\">Изабери опсег</string>\n    <string name=\"tracker_warning\">Неки уређаји имају другачије понашање система, што може да прекине позадинске задатке.</string>\n    <string name=\"no_manga_sources\">Нема извора манге</string>\n    <string name=\"suggestions_excluded_genres_summary\">Наведите жанрове које не желите да видите у предлозима</string>\n    <string name=\"frequency_twice_per_month\">Двапут месечно</string>\n    <string name=\"prefetch_content\">Преучитавање садржаја</string>\n    <string name=\"main_screen_sections\">Одељци главног екрана</string>\n    <string name=\"confirm_exit\">Притисни Назад поново да изађеш</string>\n    <string name=\"scale_mode\">Режим скалирања</string>\n    <string name=\"advanced\">Напредна</string>\n    <string name=\"only_using_wifi\">Само на ВиФи мрежи</string>\n    <string name=\"sync_settings\">Подешавања синхронизације</string>\n    <string name=\"back\">Назад</string>\n    <string name=\"online_variant\">Варијанта са мреже</string>\n    <string name=\"create_category\">Нова категорија</string>\n    <string name=\"error_multiple_genres_not_supported\">Овај извор манге не подржава филтрирање према више жанрова</string>\n    <string name=\"download_option_all_unread_b\">Сва непрочитана поглавља (%s)</string>\n    <string name=\"color_dark\">Тамна</string>\n    <string name=\"screenshots_allow\">Дозволи</string>\n    <string name=\"backup_restore\">Прављење резервних копија и обнављање</string>\n    <string name=\"other_cache\">Други кеш</string>\n    <string name=\"dns_over_https\">DNS преко HTTPS-а</string>\n    <string name=\"show_suspicious_content\">Прикажи сумњив садржај</string>\n    <string name=\"sync_title\">Синхронизуј своје податке</string>\n    <string name=\"appwidget_shelf_description\">Манга из ваших Омиљених</string>\n    <string name=\"comics_archive_import_description\">Можеш да изабереш једну или више .cbz или .zip датотека, свака датотека ће бити препозната као засебна манга.</string>\n    <string name=\"downloads_paused\">Преузимања су заустављена</string>\n    <string name=\"too_many_requests_message\">Превише захтева. Покушај поново касније</string>\n    <string name=\"downloads_wifi_only\">Преузми само преко Ви-Фи мреже</string>\n    <string name=\"lock_screen_rotation\">Закључавање ротације екрана</string>\n    <string name=\"cancel_all_downloads_confirm\">Сва активна преузимања ће бити отказана, а делимично преузети подаци ће бити изгубљени</string>\n    <string name=\"send\">Пошаљи</string>\n    <string name=\"by_relevance\">Релевантност</string>\n    <string name=\"related_manga\">Повезана манга</string>\n    <string name=\"bookmark_add\">Додај обележивач</string>\n    <string name=\"discard\">Одбаци</string>\n    <string name=\"saved_manga\">Сачувана манга</string>\n    <string name=\"screenshots_block_all\">Увек блокирај</string>\n    <string name=\"new_sources_text\">Доступни су нови извори манге</string>\n    <string name=\"manga_error_description_pattern\">Детаљи о грешци:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Покушај да &lt;a href=%2$s&gt;отвориш мангу у веб прегледачу&lt;/a&gt; да би био сигуран да је доступна на извору&lt;br&gt;2. Увери се да користите &lt;a href=kotatsu://about&gt;latest version of Kotatsu&gt;најновију верзију Котатсу-а&lt;/a&gt;&lt;br&gt;3. Ако је доступно, пошаљи извештај о грешци програмерима.</string>\n    <string name=\"state_abandoned\">Напуштен</string>\n    <string name=\"zoom_mode_fit_height\">Уклопи по висини</string>\n    <string name=\"not_available\">Није доступно</string>\n    <string name=\"history_shortcuts_summary\">Учини недавну мангу доступном дугим притиском на икону апликације</string>\n    <string name=\"automatic_scroll\">Самостално померање</string>\n    <string name=\"download_option_first_n_chapters\">Првих %s</string>\n    <string name=\"keep_screen_on\">Задржи екран укљученим</string>\n    <string name=\"reverse\">Уназад</string>\n    <string name=\"track_sources\">Потражи ажурирања</string>\n    <string name=\"paused\">Заустављено</string>\n    <string name=\"wrong_password\">Погрешна лозинка</string>\n    <string name=\"group\">Група</string>\n    <string name=\"text_downloads_list_holder\">Немате ниједно преузимање</string>\n    <string name=\"color_correction\">Исправка боје</string>\n    <string name=\"just_now\">Управо сада</string>\n    <string name=\"chapter_is_missing\">Недостаје поглавље</string>\n    <string name=\"error_search_not_supported\">Овај извор манге не подржава претрагу</string>\n    <string name=\"suggestions_wifi_only_summary\">Немој ажурирати предлоге користећи ограничене мрежне везе</string>\n    <string name=\"webtoon_zoom_summary\">Дозволи покрете увећања у webtoon режиму</string>\n    <string name=\"categories_delete_confirm\">Да ли сте сигуран да желиш да избришеш изабране омиљене категорије? \\nСва манга у њој ће бити изгубљена и ово се не може поништити.</string>\n    <string name=\"frequency_once_per_month\">Једном месечно</string>\n    <string name=\"logged_in_as\">Пријављен као %s</string>\n    <string name=\"suggestions_info\">Сви подаци се анализирају само локално на овом уређају и никада се нигде не шаљу.</string>\n    <string name=\"reader_info_pattern\">Поглавље %1$d/%2$d Страна %3$d/%4$d</string>\n    <string name=\"contrast\">Контраст</string>\n    <string name=\"history_cleared\">Историја је очишћена</string>\n    <string name=\"size_s\">Величина: %s</string>\n    <string name=\"reader_slider\">Прикажи клизач за пребацивање страница</string>\n    <string name=\"no_manga_sources_text\">Омогући изворе манге за читање манге на мрежи</string>\n    <string name=\"downloaded\">Преузето</string>\n    <string name=\"undo\">Врати</string>\n    <string name=\"zoom_mode_fit_center\">Уклопи по средини</string>\n    <string name=\"exclude_nsfw_from_history\">Искључи манге са садржајем за одрасле из историје</string>\n    <string name=\"suggestions_enable_prompt\">Да ли желиш да примаш персонализоване предлоге за мангу?</string>\n    <string name=\"download_slowdown_summary\">Помаже у избегавању блокирања ваше ИП адресе</string>\n    <string name=\"check_for_new_chapters\">Провери да ли постоје нова поглавља</string>\n    <string name=\"text_delete_local_manga_batch\">Да ли желите да трајно избришете изабране ставке са уређаја?</string>\n    <string name=\"queued\">У реду чекања</string>\n    <string name=\"report\">Пријави</string>\n    <string name=\"captcha_solve\">Реши</string>\n    <string name=\"download_slowdown\">Успоравање преузимања</string>\n    <string name=\"bookmark_added\">Обележивач је додат</string>\n    <string name=\"data_restored_with_errors\">Подаци су обновљени, али има грешака</string>\n    <string name=\"sync\">Синхронизација</string>\n    <string name=\"search_chapters\">Пронађи поглавље</string>\n    <string name=\"exit_confirmation\">Потврда изласка</string>\n    <string name=\"manual\">Прилагођеном избору</string>\n    <string name=\"comics_archive\">Архива стрипова</string>\n    <string name=\"more\">Више</string>\n    <string name=\"theme_name_asuka\">Асука</string>\n    <string name=\"reader_optimize_summary\">Смањи квалитет страница ван екрана да би користио мање меморије</string>\n    <string name=\"always\">Увек</string>\n    <string name=\"address\">Адреса</string>\n    <string name=\"import_will_start_soon\">Увоз ће ускоро почети</string>\n    <string name=\"compact\">Сажето</string>\n    <string name=\"protect_application\">Закључај апликацију</string>\n    <string name=\"source_enabled\">Извор је омогућен</string>\n    <string name=\"enhanced_colors\">32-битни режим боја</string>\n    <string name=\"folder_with_images_import_description\">Можеш изабрати директоријум са архивама или сликама. Свака архива (или поддиректоријум) биће препозната као поглавље.</string>\n    <string name=\"reorder\">Распореди</string>\n    <string name=\"background\">Позадина</string>\n    <string name=\"suggestions_excluded_genres\">Изостави жанрове</string>\n    <string name=\"canceled\">Отказано</string>\n    <string name=\"disable_nsfw_summary\">Онемогућите изворе садржаја за одрасле и сакријте мангу за одрасле са листе ако је могуће</string>\n    <string name=\"account_already_exists\">Налог већ постоји</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"hide\">Сакриј</string>\n    <string name=\"passwords_mismatch\">Лозинке се не подударају</string>\n    <string name=\"downloads_removed\">Преузимања су уклоњена</string>\n    <string name=\"yesterday\">Јуче</string>\n    <string name=\"no_access_to_file\">Немаш приступ овој датотеци или директоријуму</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Манга означена за одрасле никада неће бити додата у историју и ваш напредак неће бити сачуван</string>\n    <string name=\"mark_as_current\">Означи као тренутно</string>\n    <string name=\"protect_application_summary\">Затражи лозинку при покретању Котатсу-а</string>\n    <string name=\"right_to_left\">Са десна на лево</string>\n    <string name=\"show_reading_indicators_summary\">Прикажи проценат читања у Историји и Омиљеним</string>\n    <string name=\"random\">Насумично</string>\n    <string name=\"mirror_switching\">Аутоматски изабери послуживач</string>\n    <string name=\"use_fingerprint\">Користи биометријско откључавање ако је доступно</string>\n    <string name=\"restore_summary\">Врати претходно направљену резервну копију</string>\n    <string name=\"onboard_text\">Изаберите језике на којима желите да читате мангу. Можете их променити касније у подешавањима.</string>\n    <string name=\"zoom_out\">Умањи</string>\n    <string name=\"reader_mode_hint\">Изабрана конфигурација ће бити запамћена за ову мангу</string>\n    <string name=\"keep_screen_on_summary\">Не искључуј екран док читам мангу</string>\n    <string name=\"default_s\">Подразумевано: %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Следеће непрочитано %s</string>\n    <string name=\"confirm\">Потврди</string>\n    <string name=\"suggestions_updating\">Предлози се ажурирају</string>\n    <string name=\"clear_updates_feed\">Избриши фид ажурирања</string>\n    <string name=\"voice_search\">Гласовна претрага</string>\n    <string name=\"enable\">Омогући</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"chapters_will_removed_background\">Поглавља ће бити уклоњена у позадини</string>\n    <string name=\"import_completed_hint\">Можете да избришете оригиналну датотеку из складишта за уштеду простора</string>\n    <string name=\"theme_name_rikka\">Рикка</string>\n    <string name=\"disabled\">Онемогући</string>\n    <string name=\"long_ago\">Давно</string>\n    <string name=\"reader_control_ltr_summary\">Не прилагођавај смер пребацивања страница на режим читача, нпр. притиском на десни тастер увек се прелази на следећу страницу. Ова опција утиче само на хардверске улазне уређаје</string>\n    <string name=\"incognito_mode\">Режим без чувања</string>\n    <string name=\"no_bookmarks_summary\">Можеш направити обележивач док читаш мангу</string>\n    <string name=\"theme_name_mamimi\">Мамими</string>\n    <string name=\"no_manga_sources_catalog_text\">У овом одељку нема доступних извора или су сви можда већ додати.\n\\nБудите у току</string>\n    <string name=\"available_d\">Доступно: %1$d</string>\n    <string name=\"manage\">Управљaj</string>\n    <string name=\"state\">Стање</string>\n    <string name=\"logout\">Одјави се</string>\n    <string name=\"status_completed\">Завршен</string>\n    <string name=\"manga_list\">Листа манги</string>\n    <string name=\"recent_manga\">Недавно</string>\n    <string name=\"reader_control_ltr\">Ергономска контрола читача</string>\n    <string name=\"dont_check\">Не проверавај</string>\n    <string name=\"zoom_mode_fit_width\">Уклопи по ширини</string>\n    <string name=\"read_more\">Опширније</string>\n    <string name=\"reset_filter\">Ресетујте филтер</string>\n    <string name=\"status_dropped\">Отказан пројекат</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"disable_nsfw\">Онемогући садржај за одрасле</string>\n    <string name=\"last_successful_backup\">Последња успешна резервна копија: %s</string>\n    <string name=\"available\">Доступно</string>\n    <string name=\"notifications_enable\">Омогући обавештења</string>\n    <string name=\"color_white\">Бела</string>\n    <string name=\"search_results\">Резултати претраге</string>\n    <string name=\"empty\">Празно</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Не предлажи ми мангу за одрасле</string>\n    <string name=\"file_not_found\">Датотека није пронађена</string>\n    <string name=\"downloads_resumed\">Преузимања су настављена</string>\n    <string name=\"never\">Никада</string>\n    <string name=\"text_unsaved_changes_prompt\">Сачувај или одбаци несачуване промене?</string>\n    <string name=\"state_paused\">Заустављано</string>\n    <string name=\"folder_with_images\">Фасцикла са сликама</string>\n    <string name=\"cookies_cleared\">Сви колачићи су уклоњени</string>\n    <string name=\"disable_battery_optimization\">Искључи оптимизацију батерије</string>\n    <string name=\"status_planned\">Планирано</string>\n    <string name=\"state_finished\">Завршено</string>\n    <string name=\"to_top\">На врх</string>\n    <string name=\"state_ongoing\">Траје</string>\n    <string name=\"suggestions_summary\">Предложи мангу на основу ваших жеља</string>\n    <string name=\"show\">Прикажи</string>\n    <string name=\"genres\">Жанрови</string>\n    <string name=\"sync_auth_hint\">Можеш се пријавити на постојећи налог или направити нови</string>\n    <string name=\"theme_name_kanade\">Канаде</string>\n    <string name=\"other_storage\">Друго складиште</string>\n    <string name=\"backups_output_directory\">Резервне копије излазног директоријума</string>\n    <string name=\"invert_colors\">Обрни боје</string>\n    <string name=\"color_theme\">Палета боја</string>\n    <string name=\"brightness\">Осветљеност</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"preparing_\">Припрема се…</string>\n    <string name=\"exit_confirmation_summary\">Притисни Назад двапут да би изашао из апликације</string>\n    <string name=\"bookmarks_removed\">Обележивачи су уклоњени</string>\n    <string name=\"screenshots_block_nsfw\">Блокирај садржај за одрасле</string>\n    <string name=\"enter_password\">Унеси лозинку</string>\n    <string name=\"repeat_password\">Поновите лозинку</string>\n    <string name=\"data_restored_success\">Сви подаци су обновљени</string>\n    <string name=\"theme_name_mion\">Мион</string>\n    <string name=\"mirror_switching_summary\">Самостално пребаци домене за изворе манге у случају грешака ако су послуживачи доступни</string>\n    <string name=\"no_bookmarks_yet\">Нема обележивача за сада</string>\n    <string name=\"no_thanks\">Не хвала</string>\n    <string name=\"suggest_new_sources_summary\">Затражи да омогућиш новододате изворе након ажурирања апликације</string>\n    <string name=\"backup_information\">Можеш да направиш резервну копију своје историје и омиљених и обновити је</string>\n    <string name=\"share_logs\">Дели дневнике</string>\n    <string name=\"zoom_mode_keep_start\">Задржи на почетку</string>\n    <string name=\"text_local_holder_secondary\">Сачувај нешто из каталога или увези из датотеке.</string>\n    <string name=\"speed\">Брзина</string>\n    <string name=\"silent\">Тихо</string>\n    <string name=\"download_option_all_chapters\">Сва поглавља са преводом %s</string>\n    <string name=\"content_type_other\">Остало</string>\n    <string name=\"suggestion_manga\">Предлог: %s</string>\n    <string name=\"color_black\">Црна</string>\n    <string name=\"removed_from_favourites\">Уклоњено из Омиљених</string>\n    <string name=\"bookmarks\">Обележивачи</string>\n    <string name=\"show_all\">Покажи све</string>\n    <string name=\"this_month\">Овог месеца</string>\n    <string name=\"today\">Од данас</string>\n    <string name=\"empty_favourite_categories\">Нема омиљених категорија</string>\n    <string name=\"invalid_domain_message\">Неважећи домен</string>\n    <string name=\"system_default\">Подразумевано</string>\n    <string name=\"proxy\">Заступник</string>\n    <string name=\"done\">Готово</string>\n    <string name=\"error_no_space_left\">Недовољно меморије на уређају</string>\n    <string name=\"sign_in\">Пријави се</string>\n    <string name=\"error_filter_states_genre_not_supported\">Овај извор не подржава филтрирање по жанровима и стањима у исто време</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Овај извор не подржава филтрирање по жанровима и локалним стандардима у исто време</string>\n    <string name=\"apply\">Примени</string>\n    <string name=\"genres_search_hint\">Унеси назив жанра</string>\n    <string name=\"globally\">Глобално</string>\n    <string name=\"downloads_settings_info\">Можеш да омогућиш успоравање преузимања за сваки извор манге појединачно у поставкама извора ако имаш проблеме са блокирањем од стране послуживача</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Можда ће помоћи око започињања преузимања ако имаш проблем са преузимањем</string>\n    <string name=\"this_manga\">Ова манга</string>\n    <string name=\"skip\">Прескочи</string>\n    <string name=\"color_correction_apply_text\">Ова подешавања се могу применити глобално или само на тренутну мангу. Ако се примењују глобално, појединачна подешавања неће бити замењена.</string>\n    <string name=\"grayscale\">Сиви тон</string>\n    <string name=\"welcome_text\">Изабери које изворе садржаја желиш да омогућиш. Ово можеш касније да промениш у подешавањима</string>\n    <string name=\"restore\">Поврати</string>\n    <string name=\"backup_date_\">Датум резервне копије: %s</string>\n    <string name=\"sync_auth\">Пријавите се за синхронизацију налога</string>\n    <string name=\"by_name_reverse\">Обрнуто име</string>\n    <string name=\"state_upcoming\">Излази</string>\n    <string name=\"genres_exclude\">Искључи жанрове</string>\n    <string name=\"rating_safe\">Безбедно</string>\n    <string name=\"rating_suggestive\">Препоручљиво</string>\n    <string name=\"default_tab\">Подразумевана картица</string>\n    <string name=\"content_rating\">Оцена садржаја</string>\n    <string name=\"rating_adult\">За одрасле</string>\n    <string name=\"mark_as_completed\">Означи као завршено</string>\n    <string name=\"mark_as_completed_prompt\">Означи изабрану мангу као потпуно прочитану?\n\\n\n\\nУпозорење: тренутни напредак читања ће бити изгубљен.</string>\n    <string name=\"incognito_mode_hint\">Ваш напредак читања неће бити сачуван</string>\n    <string name=\"email_password_enter_hint\">Унеси своју адресу е-поште и лозинку да бисте наставили</string>\n    <string name=\"category_hidden_done\">Ова категорија је сакривена са главног екрана и доступна јој је преко Изборника → Управљај категоријама</string>\n    <string name=\"toggle_ui\">Прикажи/сакриј интерфејс</string>\n    <string name=\"next_chapter\">Ново поглавље</string>\n    <string name=\"long_tap_action\">Радња дуготрајног додира</string>\n    <string name=\"none\">Ниједан</string>\n    <string name=\"use_two_pages_landscape\">Користи распоред две странице у пејзажној оријентацији (бета)</string>\n    <string name=\"vertical\">Вертикално</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"show_menu\">Прикажи изборник</string>\n    <string name=\"prev_chapter\">Претходна поглавља</string>\n    <string name=\"tap_action\">Радња при додиру</string>\n    <string name=\"config_reset_confirm\">Вратити подешавања на подразумеване вредности? Ова радња се не може опозвати.</string>\n    <string name=\"reader_actions_summary\">Конфигуриши радње за области екрана које се могу додирнути</string>\n    <string name=\"switch_pages_volume_buttons\">Омогући дугмад за јачину звука</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Користи дугмад за јачину звука за пребацивање страница</string>\n    <string name=\"prev_page\">Претходна страница</string>\n    <string name=\"next_page\">Следећа страница</string>\n    <string name=\"reader_actions\">Радње читаоца</string>\n    <string name=\"volume_unknown\">Непозната запремина</string>\n    <string name=\"last_read\">Последње прочитано</string>\n    <string name=\"volume_\">Количина %d</string>\n    <string name=\"default_webtoon_zoom_out\">Подразумевано умањивање вебтоон-а</string>\n    <string name=\"reader_fullscreen_summary\">Сакриј статус система и траке за навигацију</string>\n    <string name=\"suggestions_unavailable_text\">Предлози су искључени</string>\n    <string name=\"reading_time_estimation\">Прикажи процењено време читања</string>\n    <string name=\"reading_time_estimation_summary\">Вредност процене времена може бити нетачна</string>\n    <string name=\"check_for_new_chapters_disabled\">Провера нових поглавља је искључена</string>\n    <string name=\"fullscreen_mode\">Режим целог екрана</string>\n    <string name=\"remove_from_history\">Уклони из историје</string>\n    <string name=\"location\">Локација</string>\n    <string name=\"ask_for_dest_dir_every_time\">Затражи одредишни диркторијум сваки пут</string>\n    <string name=\"default_page_save_dir\">Подразумевани директоријум за чување странице</string>\n    <string name=\"show_labels_in_navbar\">Прикажи ознаке на навигационој траци</string>\n    <string name=\"pages_saving\">Чување страница</string>\n    <string name=\"automatic\">Самостално</string>\n    <string name=\"preferred_download_format\">Жељени формат за преузимање</string>\n    <string name=\"single_cbz_file\">Једна CBZ датотека</string>\n    <string name=\"multiple_cbz_files\">Више CBZ датотека</string>\n    <string name=\"reading_stats\">Статистика читања</string>\n    <string name=\"other_manga\">Остала манга</string>\n    <string name=\"less_than_minute\">Мање од минут</string>\n    <string name=\"statistics\">Статистика</string>\n    <string name=\"clear_stats\">Избриши статистику</string>\n    <string name=\"stats_cleared\">Статистика је избрисана</string>\n    <string name=\"day\">Дан</string>\n    <string name=\"three_months\">3 месеца</string>\n    <string name=\"clear_stats_confirm\">Да ли заиста желиш да избришеш сву статистику читања? Ова радња се не може опозвати.</string>\n    <string name=\"week\">Седмица</string>\n    <string name=\"month\">Месец</string>\n    <string name=\"all_time\">Све време</string>\n    <string name=\"empty_stats_text\">Нема статистике за изабрани период</string>\n    <string name=\"pages_read_s\">Читане странице: %s</string>\n    <string name=\"manga_migration\">Премештање Манге</string>\n    <string name=\"migration_completed\">Премештање је завршено</string>\n    <string name=\"alternatives\">Замене</string>\n    <string name=\"migrate\">Премести</string>\n    <string name=\"migrate_confirmation\">Манга %1$s из %2$s ће бити замењена са %3$s из %4$s у вашој Историји и Омиљеним (ако постоји)</string>\n    <string name=\"delete_read_chapters_summary\">Избриши поглавља која си већ прочитао из локалне меморије да би ослободио простор</string>\n    <string name=\"delete_read_chapters_prompt\">Ово ће трајно избрисати сва поглавља означена као прочитана из твог локалног складишта. Можеш их поново преузети касније, али увезена поглавља могу бити изгубљена заувек</string>\n    <string name=\"long_ago_read\">Давно прочитано</string>\n    <string name=\"unread\">Непрочитано</string>\n    <string name=\"chapters_grid_view\">Мрежни приказ</string>\n    <string name=\"delete_read_chapters\">Избриши прочитана поглавља</string>\n    <string name=\"no_chapters_deleted\">Ниједно поглавље није избрисано</string>\n    <string name=\"chapters_deleted_pattern\">Уклоњено %1$s, избрисано %2$s</string>\n    <string name=\"delete_read_chapters_auto\">Аутоматски избриши прочитана поглавља</string>\n    <string name=\"runs_on_app_start\">Ради када се апликација покрене</string>\n    <string name=\"split_by_translations\">Подели по преводима</string>\n    <string name=\"split_by_translations_summary\">Прикажи поглавља са различитим преводима одвојено, а не на једној листи</string>\n    <string name=\"order_oldest\">Најстарије</string>\n    <string name=\"enable_source\">Омогући извор</string>\n    <string name=\"unsupported_source\">Овај извор манге није подржан</string>\n    <string name=\"show_pages_thumbs\">Прикажи сличице страница</string>\n    <string name=\"error_no_data_received\">Никакви подаци нису примљени са послуживача</string>\n    <string name=\"show_pages_thumbs_summary\">Омогући картицу узраста на екрану са детаљима</string>\n    <string name=\"unsupported_backup_message\">Изабери одговарајућу Kotatsu датотеку резервне копије</string>\n    <string name=\"hours_short\">%d ч</string>\n    <string name=\"hours_minutes_short\">%1$d ч %2$d м</string>\n    <string name=\"minutes_short\">%d м</string>\n    <string name=\"missing_storage_permission\">Не постоји дозвола за приступање Манги на спољној меморији</string>\n    <string name=\"fix\">Поправи</string>\n    <string name=\"show_updated\">Прикажи ажурирано</string>\n    <string name=\"webtoon_gaps_summary\">Прикажи усправне празнине између страница у Вебтун режиму</string>\n    <string name=\"last_used\">Последњој кориштеној</string>\n    <string name=\"webtoon_gaps\">Празнине у режиму Вебтун-а</string>\n    <string name=\"search_suggestions\">Предлози за претрагу</string>\n    <string name=\"recent_queries\">Недавни упити</string>\n    <string name=\"suggested_queries\">Предложени упити</string>\n    <string name=\"authors\">Аутори</string>\n    <string name=\"less_frequently\">Ређе</string>\n    <string name=\"more_frequently\">Чешће</string>\n    <string name=\"frequency_of_check\">Учесталост провера</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Закачи навигациону траку</string>\n    <string name=\"pin_navigation_ui_summary\">Не сакривај навигациону траку и приказ претраге при листању</string>\n    <string name=\"blocked_by_server_message\">Блокирао вас је послуживач. Покушај да користиш другу мрежну везу (ВПН, прокси, итд.)</string>\n    <string name=\"disable\">Онемогући</string>\n    <string name=\"sources_disabled\">Извори су онемогућени</string>\n    <string name=\"disable_connectivity_check\">Онемогућите проверу везе</string>\n    <string name=\"disable_nsfw_notifications\">Онемогућите НСФВ обавештења</string>\n    <string name=\"disable_nsfw_notifications_summary\">Не приказуј обавештења за ажурирања НСФВ манге</string>\n    <string name=\"ignore_ssl_errors_summary\">Можеш да онемогућиш верификацију ССЛ сертификата у случају да се суочиш са проблемима везаним за ССЛ када приступаш мрежним ресурсима. Ово може утицати на твоју безбедност. Након промене овог подешавања потребно је поновно покретање апликације.</string>\n    <string name=\"disable_connectivity_check_summary\">Прескочи проверу повезивања у случају да имаш проблема са њом (нпр. прелазак у режим ван мреже иако је мрежа повезана)</string>\n    <string name=\"tracker_debug_info\">Провера дневника нових поглавља</string>\n    <string name=\"tracker_debug_info_summary\">Информације о отклањању грешака о позадинским проверама за нова поглавља</string>\n    <string name=\"_new\">Нови</string>\n    <string name=\"all_languages\">Сви језици</string>\n    <string name=\"screenshots_block_incognito\">Блокирај у режиму без архивирања</string>\n    <string name=\"percent_left\">Преостали постотак</string>\n    <string name=\"percent_read\">Постотак читања</string>\n    <string name=\"chapters_read\">Прочитана поглавља</string>\n    <string name=\"chapters_left\">Остала поглавља</string>\n    <string name=\"pin\">Закачи</string>\n    <string name=\"unpin\">Откачи</string>\n    <string name=\"source_pinned\">Извор је закачен</string>\n    <string name=\"source_unpinned\">Извор је откачен</string>\n    <string name=\"sources_unpinned\">Извори су откачени</string>\n    <string name=\"sources_pinned\">Извори су закачени</string>\n    <string name=\"recent_sources\">Недавни извори</string>\n    <string name=\"image_server\">Жељени послуживач слика</string>\n    <string name=\"crop_pages\">Изрежи странице</string>\n    <string name=\"external_source\">Спољни/додатак</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Нема Манге која одговара филтерима које си изабрао</string>\n    <string name=\"connection_ok\">Веза је У РЕДУ</string>\n    <string name=\"plugin_incompatible\">Некомпатибилан додатак или интерна грешка. Увери се да користиш најновију верзију додатка и Котатсу-а</string>\n    <string name=\"invalid_proxy_configuration\">Неважећа конфигурација проксија</string>\n    <string name=\"sfw\">БЗП</string>\n    <string name=\"seconds_short\">%d с</string>\n    <string name=\"skip_all\">Прескочи све</string>\n    <string name=\"stuck\">Застој</string>\n    <string name=\"not_in_favorites\">Није у Омиљенима</string>\n    <string name=\"too_many_requests_message_retry\">Превише захтева. Покушај поново после %s</string>\n    <string name=\"retry\">Покушај опет</string>\n    <string name=\"invalid_server_address_message\">Неважећа адреса послуживача</string>\n    <string name=\"minutes_seconds_short\">%1$d мин %2$d с</string>\n    <string name=\"show_quick_filters_summary\">Пружа могућност филтрирања манга листа према одређеним параметрима</string>\n    <string name=\"show_quick_filters\">Прикажи брзе филтере</string>\n    <string name=\"updated_long_ago\">Ажурирано давно</string>\n    <string name=\"unpopular\">Непопуларно</string>\n    <string name=\"by_date\">Датум</string>\n    <string name=\"popularity\">Популарност</string>\n    <string name=\"unstable_feature\">Нестабилна карактеристика</string>\n    <string name=\"unstable_feature_summary\">Ова функција је експериментална. Увери се да имаш резервну копију да не би дошло до губитка података</string>\n    <string name=\"scrobbler_auth_required\">Пријави се на %s да би наставио</string>\n    <string name=\"scrobbler_auth_intro\">Пријави се да би подесио интеграцију са %s. Ово ће ти омогућити да пратиш напредак и статус читања манге</string>\n    <string name=\"low_rating\">Ниска оцена</string>\n    <string name=\"sort_order_asc\">Узлазно</string>\n    <string name=\"sort_order_desc\">Силазно</string>\n    <string name=\"downloads_background\">Позадинска преузимања</string>\n    <string name=\"download_new_chapters\">Преузми нова поглавља</string>\n    <string name=\"manga_with_downloaded_chapters\">Манга са преузетим поглављима</string>\n    <string name=\"fixing_manga\">Поправљање манге</string>\n    <string name=\"fixed\">Успешно поправљено</string>\n    <string name=\"no_fix_required\">Није потребна исправка за \\\"%s\\\"</string>\n    <string name=\"manga_fix_prompt\">Ова функција ће пронаћи алтернативне изворе за изабрану мангу. Задатак ће потрајати и наставиће се у позадини</string>\n    <string name=\"manga_replaced\">Манга \\\"%1$s\\\" (%2$s) је замењена са \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"no_alternatives_found\">Нису пронађене алтернативе за \\\"%s\\\"</string>\n    <string name=\"content_type_novel\">Роман</string>\n    <string name=\"content_type_manhua\">Манхуа</string>\n    <string name=\"content_type_manhwa\">Манхва</string>\n    <string name=\"recently_added\">Недавно додано</string>\n    <string name=\"added_long_ago\">Додато давно</string>\n    <string name=\"popular_today\">Популарно данас</string>\n    <string name=\"popular_in_month\">Популарно овог месеца</string>\n    <string name=\"popular_in_year\">Популарно ове године</string>\n    <string name=\"original_language\">Оригинални језик</string>\n    <string name=\"year\">Година</string>\n    <string name=\"demographics\">Демографија</string>\n    <string name=\"demographic_shounen\">Схоунен</string>\n    <string name=\"demographic_shoujo\">Схоујо</string>\n    <string name=\"demographic_seinen\">Сеинен</string>\n    <string name=\"demographic_josei\">Јосеи</string>\n    <string name=\"years\">Године</string>\n    <string name=\"any\">Било који</string>\n    <string name=\"demographic_kodomo\">Кодомо</string>\n    <string name=\"content_type_doujinshi\">Доујинсхи</string>\n    <string name=\"content_type_image_set\">Сет слика</string>\n    <string name=\"debug\">Отклањање грешака</string>\n    <string name=\"source_code\">Изворни код</string>\n    <string name=\"user_manual\">Упутство за употребу</string>\n    <string name=\"telegram_group\">Телеграм група</string>\n    <string name=\"popular_in_hour\">Популарно овог часа</string>\n    <string name=\"popular_in_week\">Популарно ове седмице</string>\n    <string name=\"content_type_artist_cg\">Уметник (Рачунарско Генерисани)</string>\n    <string name=\"error_image_format\">Неподржан формат слике: %s</string>\n    <string name=\"filter_search_warning\">Овај извор не подржава претрагу са филтерима. Ваши филтери су обрисани</string>\n    <string name=\"content_type_one_shot\">Један ударац</string>\n    <string name=\"content_type_game_cg\">Игра (Рачунарско Генерисана)</string>\n    <string name=\"start_download\">Започни преузимање</string>\n    <string name=\"save_manga_confirm\">Сачувати изабрану мангу? Ово може да потроши мрежни саобраћај и простор на диску</string>\n    <string name=\"save_manga\">Сачувај мангу</string>\n    <string name=\"genre\">Жанр</string>\n    <string name=\"download_added\">Преузимање додато</string>\n    <string name=\"more_options\">Више опција</string>\n    <string name=\"destination_directory\">Одредишни именик</string>\n    <string name=\"chapter_selection_hint\">Можеш одабрати поглавља за преузимање дугим притиском на ставку на листи поглавља.</string>\n    <string name=\"chapters_all\">Сва</string>\n    <string name=\"screen_orientation\">Окретање екрана</string>\n    <string name=\"portrait\">Усправно</string>\n    <string name=\"landscape\">Положено</string>\n    <string name=\"download_over_cellular\">Преузимање преко мобилне мреже</string>\n    <string name=\"download_cellular_confirm\">Дозволити преузимања преко мобилне мреже?</string>\n    <string name=\"dont_allow\">Не дозволи</string>\n    <string name=\"allow_always\">Дозволи увек</string>\n    <string name=\"allow_once\">Дозволи једном</string>\n    <string name=\"ask_every_time\">Питај ме сваки пут</string>\n    <string name=\"pages_saved\">Странице су сачуване</string>\n    <string name=\"plugin_incompatible_with_cause\">Грешка додатка: %s\\n. Провери да ли користиш најновију верзију додатка и Котатсу-а</string>\n    <string name=\"error_not_image\">Неважећи формат: очекивана слика, али је добила %s</string>\n    <string name=\"max_backups_count\">Највећи дозвољен број резервних копија</string>\n    <string name=\"access_denied_403\">Приступ одбијен (403)</string>\n    <string name=\"delete_old_backups\">Избриши старе резервне копије</string>\n    <string name=\"delete_old_backups_summary\">Аутоматски избриши старе датотеке резервних копија да бис уштедео простор за складиштење</string>\n    <string name=\"handle_links_summary\">Отварање манга веза из спољних апликација (нпр. из прегледача). Можда ћеш морати да ручно омогућиш у системским поставкама апликације</string>\n    <string name=\"handle_links\">Руковање везама</string>\n    <string name=\"email\">Е-пошта</string>\n    <string name=\"captcha_required_message\">Овај извор захтева решавање CAPTCHA за наставак</string>\n    <string name=\"author\">Аутор</string>\n    <string name=\"rating\">Оцена</string>\n    <string name=\"source\">Извор</string>\n    <string name=\"translation\">Превод</string>\n    <string name=\"incognito\">Тајно</string>\n    <string name=\"open_telegram_bot\">Отвори Телеграм бот-а</string>\n    <string name=\"telegram_chat_id\">Телеграм ИД за ћаскање</string>\n    <string name=\"backup_tg_id_not_set\">ИД ћаскања није подешен</string>\n    <string name=\"error_connection_reset\">Веза је ресетована од стране удаљеног домаћина</string>\n    <string name=\"backup_tg_check\">Провери да ли API ради</string>\n    <string name=\"backup_tg_echo\">Пробна порука</string>\n    <string name=\"show_slider\">Прикажи клизач</string>\n    <string name=\"telegram_chat_id_summary\">Унеси ИД ћаскања на који треба послати резервне копије</string>\n    <string name=\"send_backups_telegram\">Пошаљи резервне копије у Телеграм</string>\n    <string name=\"test_connection\">Провери везу</string>\n    <string name=\"open_telegram_bot_summary\">Додирни да отвориш ћаскање са Котатсу Ботом за резервну копију</string>\n    <string name=\"clear_database\">Избриши базу података</string>\n    <string name=\"clear_database_summary\">Избриши информације о Манги које се не користе</string>\n    <string name=\"enable_all_sources_summary\">Сви доступни Манга извори биће трајно омогућени</string>\n    <string name=\"enable_all_sources\">Омогући све Манга изворе</string>\n    <string name=\"all_sources_enabled\">Сви извори су укључени</string>\n    <string name=\"backup_restored_background\">Резервна копија ће бити враћена у позадини</string>\n    <string name=\"reader_info_bar_transparent\">Провидна трака са информацијама о читаоцу</string>\n    <string name=\"reader_controls_in_bottom_bar\">Контроле читача у доњој траци</string>\n    <string name=\"pages_slider\">Клизач за пребацивање страница</string>\n    <string name=\"screen_rotation_locked\">Окретање екрана је закључано</string>\n    <string name=\"screen_rotation_unlocked\">Окретање екрана је откључано</string>\n    <string name=\"restoring_backup\">Враћање резервне копије</string>\n    <string name=\"chapters_and_pages\">Поглавља и странице</string>\n    <string name=\"simple\">Једноставно</string>\n    <string name=\"global_search\">Глобална претрага</string>\n    <string name=\"search_everywhere\">Тражи свуда</string>\n    <string name=\"badges_in_lists\">Значке у листама</string>\n    <string name=\"disable_captcha_notifications\">Онемогући Цаптцха обавештења</string>\n    <string name=\"disable_captcha_notifications_summary\">Нећете добијати обавештења о решавању ЦАПТЦХА за овај извор, али то може довести до прекида позадинских операција (проверавање нових поглавља, добијање препорука итд.)</string>\n    <string name=\"error_details\">Детаљи грешке</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"error_disclaimer_report\">Можеш послати извештај о грешкама нашим програмерима. Ово ће нам помоћи да истражимо и исправимо грешку.</string>\n    <string name=\"clear_browser_data\">Избриши податке из прегледача</string>\n    <string name=\"search_disabled_sources\">Претражи из онемогућених извора</string>\n    <string name=\"unnamed_chapter\">Неименовано поглавље</string>\n    <string name=\"chapter_number\">Поглавље %s</string>\n    <string name=\"error_disclaimer_manga\">Пробај да отвориш Мангу у мрежном прегледачу да би се уверио да је доступна из извора.</string>\n    <string name=\"error_disclaimer_app_outdated\">Изгледа да је твоја верзија Котатсу-а застарела. Молим инсталирај најновију верзију да би добио све доступне исправке.</string>\n    <string name=\"link_to_manga_on_s\">Веза до Манге на %s</string>\n    <string name=\"link_to_manga_in_app\">Повезница до Манге у Котатсу</string>\n    <string name=\"tags_warnings\">Означи опасне жанрове</string>\n    <string name=\"tags_warnings_summary\">Означи жанрове који могу бити неприкладни за већину корисника</string>\n    <string name=\"no_write_permission_to_file\">Нема дозволу за писање датотеке</string>\n    <string name=\"clear_browser_data_summary\">Избриши податке претраживача као што су кеш меморија и колачићи. Упозорење: Овлашћења у манга изворима могу постати неважећа</string>\n    <string name=\"include_disabled_sources\">Обухвати онемогућене изворе</string>\n    <string name=\"suggestions_disabled_sources_summary\">Прикажи предлоге из свих извора манге, обухватајући и оне онемогућене</string>\n    <string name=\"chapter_volume_number\">Том %1$s Поглавље %2$s</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Манга за одрасле неће бити приказана у предлозима. Ова опција може радити нетачно са неким изворима</string>\n    <string name=\"error_non_file_uri\">Изабрана путања не може да се користи јер не представља датотеку или директоријум</string>\n    <string name=\"pick_manga_page\">Изабери страницу манге</string>\n    <string name=\"dont_ask_again\">Не питај поново</string>\n    <string name=\"incognito_for_nsfw\">Режим нечувања за NSFW манге</string>\n    <string name=\"additional_action_required\">Потребна је додатна радња</string>\n    <string name=\"use_default_cover\">Користи подразумевану корицу</string>\n    <string name=\"theme_name_expressive\">Експресивно (Тест)</string>\n    <string name=\"incognito_mode_hint_nsfw\">Ова манга може да садржи садржај за одрасле. Да ли желите да користите режим „нечувања“?</string>\n    <string name=\"manga_override_hint\">Ове промене ће утицати на то како се манга приказује у апликацији</string>\n    <string name=\"pick_custom_file\">Изабери прилагођену датотеку</string>\n    <string name=\"change_cover\">Промени корице</string>\n    <string name=\"page_switch_timer\">Страница ће се мењати сваких ~%d секунди</string>\n    <string name=\"reader_navigation_inverted_summary\">Замените смер дугмета за јачину звука и смерне навигације помоћу хардверских типки (лево/горе/доле/десно)</string>\n    <string name=\"hide_from_main_screen\">Сакриј са главног екрана</string>\n    <string name=\"changelog\">Дневник промена</string>\n    <string name=\"changelog_summary\">Историја промена за недавно објављене верзије</string>\n    <string name=\"collapse\">Склопи</string>\n    <string name=\"expand\">Прошири</string>\n    <string name=\"adblock\">Блокирај огласе у прегледачу</string>\n    <string name=\"adblock_summary\">Блокирај рекламе у уграђеном прегледачу (бета)</string>\n    <string name=\"collapse_long_description\">Склопи дугачак опис</string>\n    <string name=\"creating_backup\">Прављење резервне копије</string>\n    <string name=\"share_backup\">Дели резервну копију</string>\n    <string name=\"reader_multitask\">Отвори читач у посебном задатку</string>\n    <string name=\"reader_multitask_summary\">Омогућава вам да истовремено држите више отворених читача са различитим мангама</string>\n    <string name=\"theme_name_itsuka\">Итсука</string>\n    <string name=\"theme_name_totoro\">Тоторо</string>\n    <string name=\"book_effect\">Жућкаста позадина (плави филтер)</string>\n    <string name=\"local_storage_cleanup\">Чишћење локалне меморије</string>\n    <string name=\"packup_creation_failed\">Није успело креирање резервне копије</string>\n    <string name=\"reader_navigation_inverted\">Обрни контроле навигације</string>\n    <string name=\"pull_to_prev_chapter\">Отпусти да би отворио претходно поглавље</string>\n    <string name=\"pull_to_next_chapter\">Отпусти да би отворио следеће поглавље</string>\n    <string name=\"pull_top_no_prev\">Нема претходног поглавља</string>\n    <string name=\"pull_bottom_no_next\">Нема следећег поглавља</string>\n    <string name=\"enable_pull_gesture_title\">Омогући покрет повлачења</string>\n    <string name=\"enable_pull_gesture_summary\">Користи покрет повлачења да би пребацивао поглавља у webtoon-у</string>\n    <string name=\"main_screen\">Главни екран</string>\n    <string name=\"main_screen_fab\">Прикажи плутајуће дугме Настави</string>\n    <string name=\"main_screen_fab_summary\">Омогућава наставак читања једним додиром. Ово дугме се неће појавити у режиму „Без чувања“ или када је историја празна</string>\n    <string name=\"error_corrupted_zip\">Оштећена ZIP архива (%s)</string>\n    <string name=\"discord_rpc\">Присуство богато Discord-ом</string>\n    <string name=\"discord_token\">Discord Токен</string>\n    <string name=\"discord_token_summary\">Унеси свој Discord Токен да би омогућио Rich Presence</string>\n    <string name=\"discord_token_description\">Унеси свој Discord Токен или додирни на %s да би га добио помоћу прегледача</string>\n    <string name=\"discord_token_hint\">Налепи свој Discord Токен овде</string>\n    <string name=\"discord_rpc_summary\">Прикажи свој статус читања на Дискорду</string>\n    <string name=\"obtain\">Добиј</string>\n    <string name=\"discord_rpc_description\">Читање манге на Котатсу - апликацији за читање манги</string>\n    <string name=\"reading_s\">Читам %s</string>\n    <string name=\"read_on_s\">Читај на %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Не користи RPC за садржај за одрасле</string>\n    <string name=\"invalid_token\">Неважећи токен: %s</string>\n    <string name=\"show_floating_control_button\">Прикажи плутајуће дугме за контролу</string>\n    <string name=\"unavailable\">Недоступно</string>\n    <string name=\"manga_restricted_description\">Ова манга није доступна за читање у овом извору. Покушај да је потражиш у другим изворима или је отвори у прегледачу за више информација</string>\n    <string name=\"no_chapters_in_manga\">Ова манга не садржи ниједно поглавље</string>\n    <string name=\"chapters_load_failed\">Учитавање листе поглавља није успело</string>\n    <string name=\"telegram_integration\">Интеграција Телеграма</string>\n    <string name=\"test_parser\">Извор тест манге</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-sv/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d artikel</item>\n        <item quantity=\"other\">%1$d artiklar</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d nytt kapitel</item>\n        <item quantity=\"other\">%1$d nya kapitel</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d kapitel</item>\n        <item quantity=\"other\">%1$d kapitel</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d minut sedan</item>\n        <item quantity=\"other\">%1$d minuter sedan</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d timme sedan</item>\n        <item quantity=\"other\">%1$d timmar sedan</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d dag sedan</item>\n        <item quantity=\"other\">%1$d dagar sedan</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d månad sedan</item>\n        <item quantity=\"other\">%1$d månader sedan</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d timme</item>\n        <item quantity=\"other\">%1$d timmar</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d minut</item>\n        <item quantity=\"other\">%1$d minuter</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-sv/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"light\">Ljust</string>\n    <string name=\"pages\">Sidor</string>\n    <string name=\"text_file_not_supported\">Välj en ZIP- eller CBZ-fil.</string>\n    <string name=\"local_storage\">Lokal lagring</string>\n    <string name=\"favourites\">Favoriter</string>\n    <string name=\"history\">Historik</string>\n    <string name=\"error_occurred\">Ett fel har inträffat</string>\n    <string name=\"network_error\">Nätverksfel</string>\n    <string name=\"details\">Detaljer</string>\n    <string name=\"chapters\">Kapitel</string>\n    <string name=\"list\">Lista</string>\n    <string name=\"detailed_list\">Detaljerad lista</string>\n    <string name=\"grid\">Rutnät</string>\n    <string name=\"list_mode\">Listläge</string>\n    <string name=\"settings\">Inställningar</string>\n    <string name=\"remote_sources\">Mangakällor</string>\n    <string name=\"loading_\">Laddar…</string>\n    <string name=\"computing_\">Beräknar…</string>\n    <string name=\"chapter_d_of_d\">Kapitel %1$d av %2$d</string>\n    <string name=\"close\">Stäng</string>\n    <string name=\"try_again\">Försök igen</string>\n    <string name=\"clear_history\">Rensa historik</string>\n    <string name=\"nothing_found\">Ingenting hittades</string>\n    <string name=\"history_is_empty\">Ingen historik än</string>\n    <string name=\"download_complete\">Nedladdat</string>\n    <string name=\"downloads\">Nedladdningar</string>\n    <string name=\"by_name\">Namn</string>\n    <string name=\"popular\">Populärt</string>\n    <string name=\"updated\">Uppdaterat</string>\n    <string name=\"add_to_favourites\">Lägg till som favorit</string>\n    <string name=\"filter\">Filter</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"read\">Läs</string>\n    <string name=\"you_have_not_favourites_yet\">Inga favoriter än</string>\n    <string name=\"save\">Spara</string>\n    <string name=\"create_shortcut\">Skapa genväg…</string>\n    <string name=\"manga_downloading_\">Laddar ned…</string>\n    <string name=\"processing_\">Behandlar…</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"follow_system\">Systemtema</string>\n    <string name=\"remove\">Ta bort</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"add_new_category\">Ny kategori</string>\n    <string name=\"search\">Sök</string>\n    <string name=\"by_rating\">Betyg</string>\n    <string name=\"add\">Lägg till</string>\n    <string name=\"share\">Dela</string>\n    <string name=\"share_s\">Dela %s</string>\n    <string name=\"search_manga\">Sök manga</string>\n    <string name=\"newest\">Nyaste</string>\n    <string name=\"sort_order\">Sorteringsordning</string>\n    <string name=\"dark\">Mörkt</string>\n    <string name=\"operation_not_supported\">Denna åtgärden stöds inte</string>\n    <string name=\"no_description\">Ingen beskrivning</string>\n    <string name=\"clear\">Rensa</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" borttaget från lokal lagring</string>\n    <string name=\"save_page\">Spara sida</string>\n    <string name=\"page_saved\">Sida sparad</string>\n    <string name=\"share_image\">Dela bild</string>\n    <string name=\"_import\">Importera</string>\n    <string name=\"delete\">Ta bort</string>\n    <string name=\"clear_pages_cache\">Rensa sidcache</string>\n    <string name=\"text_delete_local_manga\">Ta permanent bort \\\"%s\\\" från den här enheten\\?</string>\n    <string name=\"error\">Error</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Läsläge</string>\n    <string name=\"grid_size\">Rutnätsstorlek</string>\n    <string name=\"search_on_s\">Sök på %s</string>\n    <string name=\"delete_manga\">Ta bort manga</string>\n    <string name=\"reader_settings\">Läsarinställningar</string>\n    <string name=\"switch_pages\">Byt sida</string>\n    <string name=\"_continue\">Fortsätt</string>\n    <string name=\"search_history_cleared\">Rensat</string>\n    <string name=\"clear_thumbs_cache\">Rensa cache för miniatyrer</string>\n    <string name=\"open_in_browser\">Öppna i webbläsare</string>\n    <string name=\"notifications\">Aviseringar</string>\n    <string name=\"clear_search_history\">Rensa sökhistorik</string>\n    <string name=\"domain\">Domän</string>\n    <string name=\"internal_storage\">Internlagring</string>\n    <string name=\"external_storage\">Externlagring</string>\n    <string name=\"app_update_available\">En ny version av appen finns tillgänglig</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d av %2$d aktiva</string>\n    <string name=\"new_chapters\">Nya kapitel</string>\n    <string name=\"download\">Ladda ned</string>\n    <string name=\"notifications_settings\">Aviseringsinställningar</string>\n    <string name=\"light_indicator\">LED-indikator</string>\n    <string name=\"notification_sound\">Aviseringsljud</string>\n    <string name=\"vibration\">Vibration</string>\n    <string name=\"favourites_categories\">Favoritkategorier</string>\n    <string name=\"no_update_available\">Inga tillgängliga uppdateringar</string>\n    <string name=\"backup_information\">Du kan säkerhetskopiera din historik och dina favoriter och återställa senare</string>\n    <string name=\"just_now\">Just nu</string>\n    <string name=\"yesterday\">Igår</string>\n    <string name=\"long_ago\">Länge sedan</string>\n    <string name=\"group\">Grupp</string>\n    <string name=\"today\">Idag</string>\n    <string name=\"tap_to_try_again\">Tryck för att försöka igen</string>\n    <string name=\"reader_mode_hint\">Den valda konfigurationen kommer att bli ihågkommen för denna manga</string>\n    <string name=\"silent\">Tyst</string>\n    <string name=\"cookies_cleared\">Alla kakor rensades</string>\n    <string name=\"clear_feed\">Rensa flöde</string>\n    <string name=\"text_clear_updates_feed_prompt\">Permanent rensa all uppdateringshistorik\\?</string>\n    <string name=\"check_for_new_chapters\">Leta efter nya kapitel</string>\n    <string name=\"reverse\">Omvänt</string>\n    <string name=\"sign_in\">Logga in</string>\n    <string name=\"auth_required\">Logga in för att visa innehåll</string>\n    <string name=\"default_s\">Standard: %s</string>\n    <string name=\"next\">Nästa</string>\n    <string name=\"password_length_hint\">Lösenordet måste vara minst 4 tecken</string>\n    <string name=\"text_clear_search_history_prompt\">Permanent rensa alla senaste sökfrågor\\?</string>\n    <string name=\"welcome\">Välkommen</string>\n    <string name=\"tracker_warning\">Vissa enheter har olika systembeteende vilket kan ha sönder bakgrundsjobb.</string>\n    <string name=\"read_more\">Läs mer</string>\n    <string name=\"queued\">Köad</string>\n    <string name=\"chapter_is_missing\">Kapitlet saknas</string>\n    <string name=\"about_app_translation_summary\">Översätt denna app</string>\n    <string name=\"about_app_translation\">Översättning</string>\n    <string name=\"auth_complete\">Auktoriserad</string>\n    <string name=\"auth_not_supported_by\">Inloggning på %s stöds inte</string>\n    <string name=\"state_finished\">Färdig</string>\n    <string name=\"exclude_nsfw_from_history\">Exkludera vuxeninnehåll från historiken</string>\n    <string name=\"show_pages_numbers\">Numrerade sidor</string>\n    <string name=\"screenshots_policy\">Policy för skärmdumpar</string>\n    <string name=\"screenshots_allow\">Tillåt</string>\n    <string name=\"screenshots_block_nsfw\">Blockera på vuxeninnehåll</string>\n    <string name=\"screenshots_block_all\">Blockera alltid</string>\n    <string name=\"suggestions\">Förslag</string>\n    <string name=\"suggestions_enable\">Aktivera förslag</string>\n    <string name=\"suggestions_summary\">Föreslå manga baserat på dina preferenser</string>\n    <string name=\"suggestions_info\">Alla data analyseras lokalt på denna enhet och skickas inte någon annanstans.</string>\n    <string name=\"text_suggestion_holder\">Börja läsa manga för att få personliga förslag</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Föreslå inte manga med vuxeninnehåll</string>\n    <string name=\"enabled\">Aktiverad</string>\n    <string name=\"disabled\">Inaktiverad</string>\n    <string name=\"reset_filter\">Återställ filter</string>\n    <string name=\"onboard_text\">Välj det språk som du vill läsa manga på. Du kan ändra detta i inställningarna senare.</string>\n    <string name=\"never\">Aldrig</string>\n    <string name=\"only_using_wifi\">Endast på Wi-Fi</string>\n    <string name=\"always\">Alltid</string>\n    <string name=\"preload_pages\">Förladdning av sidor</string>\n    <string name=\"logged_in_as\">Inloggad som %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Olika språk</string>\n    <string name=\"search_chapters\">Hitta kapitel</string>\n    <string name=\"chapters_empty\">Inga kapitel i denna manga</string>\n    <string name=\"appearance\">Utseende</string>\n    <string name=\"suggestions_updating\">Uppdaterar förslag</string>\n    <string name=\"suggestions_excluded_genres\">Uteslut genrer</string>\n    <string name=\"suggestions_excluded_genres_summary\">Ange genrer som du inte vill få förslag på</string>\n    <string name=\"removal_completed\">Borttagningen slutförd</string>\n    <string name=\"text_empty_holder_primary\">Här var det tomt…</string>\n    <string name=\"feed_will_update_soon\">Flödesuppdatering påbörjas snart</string>\n    <string name=\"restore_backup\">Återställ från säkerhetskopia</string>\n    <string name=\"text_search_holder_secondary\">Försök att omformulera frågan.</string>\n    <string name=\"text_history_holder_primary\">Det du läser kommer att visas här</string>\n    <string name=\"text_local_holder_primary\">Spara något först</string>\n    <string name=\"manga_shelf\">Hylla</string>\n    <string name=\"remove_category\">Ta bort</string>\n    <string name=\"create_category\">Ny kategori</string>\n    <string name=\"text_history_holder_secondary\">Hitta något att läsa i sektionen «Utforska»</string>\n    <string name=\"pages_animation\">Sidanimation</string>\n    <string name=\"favourites_category_empty\">Tom kategori</string>\n    <string name=\"read_later\">Läs senare</string>\n    <string name=\"updates\">Uppdateringar</string>\n    <string name=\"new_version_s\">Ny version: %s</string>\n    <string name=\"text_local_holder_secondary\">Spara något från en onlinekatalog eller importera från en fil.</string>\n    <string name=\"recent_manga\">Nytt</string>\n    <string name=\"manga_save_location\">Nedladdningsmapp</string>\n    <string name=\"not_available\">Ej tillgänglig</string>\n    <string name=\"other_storage\">Annan lagring</string>\n    <string name=\"text_feed_holder\">Nya kapitel av det du läser visas här</string>\n    <string name=\"rotate_screen\">Rotera skärm</string>\n    <string name=\"cannot_find_available_storage\">Inget tillgängligt lagringsutrymme</string>\n    <string name=\"done\">Färdig</string>\n    <string name=\"size_s\">Storlek: %s</string>\n    <string name=\"update\">Uppdatera</string>\n    <string name=\"enter_password\">Ange lösenord</string>\n    <string name=\"all_favourites\">Alla favoriter</string>\n    <string name=\"protect_application_summary\">Fråga efter lösenord när appen startas</string>\n    <string name=\"search_results\">Sökresultat</string>\n    <string name=\"clear_updates_feed\">Rensa uppdateringsflödet</string>\n    <string name=\"track_sources\">Leta efter uppdateringar</string>\n    <string name=\"updates_feed_cleared\">Rensat</string>\n    <string name=\"dont_check\">Kontrollera inte</string>\n    <string name=\"protect_application\">Skydda appen</string>\n    <string name=\"wrong_password\">Fel lösenord</string>\n    <string name=\"repeat_password\">Repetera lösenord</string>\n    <string name=\"passwords_mismatch\">Lösenord stämmer inte överens</string>\n    <string name=\"about\">Om</string>\n    <string name=\"app_version\">Version %s</string>\n    <string name=\"check_for_updates\">Leta efter uppdateringar</string>\n    <string name=\"right_to_left\">Höger-till-vänster</string>\n    <string name=\"scale_mode\">Skalningsläge</string>\n    <string name=\"zoom_mode_fit_center\">Centrera</string>\n    <string name=\"zoom_mode_fit_height\">Anpassa mot höjd</string>\n    <string name=\"zoom_mode_keep_start\">Anpassa till start</string>\n    <string name=\"preparing_\">Förbereder…</string>\n    <string name=\"zoom_mode_fit_width\">Anpassa mot bredd</string>\n    <string name=\"black_dark_theme\">Svart</string>\n    <string name=\"black_dark_theme_summary\">Använder mindre ström på AMOLED-skärmar</string>\n    <string name=\"backup_restore\">Säkerhetskopiering och återställning</string>\n    <string name=\"create_backup\">Skapa säkerhetskopia</string>\n    <string name=\"data_restored\">Återställd</string>\n    <string name=\"data_restored_with_errors\">Datan återställdes men det förekommer fel</string>\n    <string name=\"data_restored_success\">All data återställdes</string>\n    <string name=\"file_not_found\">Filen hittades inte</string>\n    <string name=\"captcha_required\">CAPTCHA krävs</string>\n    <string name=\"captcha_solve\">Lös</string>\n    <string name=\"clear_cookies\">Rensa kakor</string>\n    <string name=\"confirm\">Bekräfta</string>\n    <string name=\"protect_application_subtitle\">Ange ett lösenord för att starta appen med</string>\n    <string name=\"backup_saved\">Säkerhetskopia sparad</string>\n    <string name=\"genres\">Genrer</string>\n    <string name=\"state_ongoing\">Pågående</string>\n    <string name=\"system_default\">Standard</string>\n    <string name=\"text_clear_cookies_prompt\">Du kommer loggas ut från alla källor</string>\n    <string name=\"text_delete_local_manga_batch\">Vill du ta bort markerade objekt från enheten permanent\\?</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"hide\">Dölj</string>\n    <string name=\"disable_all\">Inaktivera alla</string>\n    <string name=\"invalid_domain_message\">Ogiltig domän</string>\n    <string name=\"dns_over_https\">DNS över HTTPS</string>\n    <string name=\"detect_reader_mode\">Autodetektera läsarläge</string>\n    <string name=\"disable_battery_optimization\">Inaktivera batterioptimering</string>\n    <string name=\"local_manga_processing\">Bearbetar nedladdad manga</string>\n    <string name=\"detect_reader_mode_summary\">Automatiskt detektera om manga är en webtoon</string>\n    <string name=\"appwidget_recent_description\">Dina nyligen lästa manga</string>\n    <string name=\"disable_battery_optimization_summary\">Hjälper till med att leta efter uppdateringar i bakgrunden</string>\n    <string name=\"new_sources_text\">Nya mangakällor finns tillgängliga</string>\n    <string name=\"report\">Anmäl</string>\n    <string name=\"chapters_will_removed_background\">Kapitel kommer att tas bort i bakgrunden</string>\n    <string name=\"tracking\">Spårning</string>\n    <string name=\"logout\">Logga ut</string>\n    <string name=\"default_mode\">Standardläge</string>\n    <string name=\"status_planned\">Planerad</string>\n    <string name=\"status_reading\">Läser</string>\n    <string name=\"status_re_reading\">Läser om</string>\n    <string name=\"status_completed\">Läst</string>\n    <string name=\"status_on_hold\">Pausad</string>\n    <string name=\"status_dropped\">Övergiven</string>\n    <string name=\"use_fingerprint\">Använd biometrik om tillgängligt</string>\n    <string name=\"appwidget_shelf_description\">Manga från dina favoriter</string>\n    <string name=\"show_reading_indicators\">Visa indikatorer om läsförlopp</string>\n    <string name=\"data_deletion\">Radering av data</string>\n    <string name=\"show_reading_indicators_summary\">Visa hur långt du har läst i procent på listor för historik och favoriter</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Manga markerad för vuxna kommer aldrig läggas till i historiken och ditt läsförlopp kommer inte att sparas</string>\n    <string name=\"clear_cookies_summary\">Kan vara till hjälp för att lösa vissa problem. Alla auktoriseringar kommer att ogiltigförklaras</string>\n    <string name=\"download_slowdown\">Begränsa nedladdningshastighet</string>\n    <string name=\"check_new_chapters_title\">Leta efter och avisera om nya kapitel</string>\n    <string name=\"show_notification_new_chapters_on\">Du kommer att få aviseringar om uppdateringar på manga du läser</string>\n    <string name=\"show_notification_new_chapters_off\">Do kommer inte att få aviseringar men nya kapitel kommer att markeras i listan</string>\n    <string name=\"notifications_enable\">Aktivera aviseringar</string>\n    <string name=\"empty_favourite_categories\">Inga favoritkategorier</string>\n    <string name=\"name\">Namn</string>\n    <string name=\"edit\">Redigera</string>\n    <string name=\"edit_category\">Redigera kategori</string>\n    <string name=\"show_all\">Visa alla</string>\n    <string name=\"removed_from_history\">Borttaget från historiken</string>\n    <string name=\"bookmark_add\">Lägg till bokmärke</string>\n    <string name=\"bookmark_remove\">Ta bort bokmärke</string>\n    <string name=\"bookmarks\">Bokmärken</string>\n    <string name=\"bookmark_removed\">Bokmärke borttaget</string>\n    <string name=\"bookmark_added\">Bokmärke tillagt</string>\n    <string name=\"undo\">Ångra</string>\n    <string name=\"download_slowdown_summary\">Hjälper för att undvika att din IP-adress blir blockerad</string>\n    <string name=\"select_range\">Välj intervall</string>\n    <string name=\"not_found_404\">Innehållet kunde inte hittas eller har tagits bort</string>\n    <string name=\"crash_text\">Något gick fel. Skicka en felrapport till utvecklarna för att hjälpa oss att åtgärda problemet.</string>\n    <string name=\"send\">Skicka</string>\n    <string name=\"email_enter_hint\">Skriv in din email för att fortsätta</string>\n    <string name=\"clear_all_history\">Rensa all historik</string>\n    <string name=\"no_bookmarks_yet\">Inga bokmärken ännu</string>\n    <string name=\"bookmarks_removed\">Bokmärken borttagna</string>\n    <string name=\"no_bookmarks_summary\">Du kan skapa bokmärken medan du läser manga</string>\n    <string name=\"confirm_exit\">Tryck Tillbaka igen för att avsluta</string>\n    <string name=\"exit_confirmation\">Avslutningsbekräftelse</string>\n    <string name=\"reader_info_bar\">Visa informationsfältet i läsaren</string>\n    <string name=\"color_correction\">Färgkorrigering</string>\n    <string name=\"discard\">Kasta</string>\n    <string name=\"error_no_space_left\">Inget mer lagringsutrymme finns kvar på enheten</string>\n    <string name=\"network_unavailable_hint\">Slå på Wi-Fi eller mobildata för att läsa manga online</string>\n    <string name=\"share_logs\">Dela loggar</string>\n    <string name=\"enable_logging\">Aktivera loggning</string>\n    <string name=\"pages_saved\">Sidor sparade</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Inga manga matchade dina valda filter</string>\n    <string name=\"canceled\">Avbruten</string>\n    <string name=\"back\">Tillbaka</string>\n    <string name=\"sync\">Synkronisering</string>\n    <string name=\"account_already_exists\">Kontot finns redan</string>\n    <string name=\"last_2_hours\">Senaste 2 timmarna</string>\n    <string name=\"manage\">Hantera</string>\n    <string name=\"categories_delete_confirm\">Är du säker på att du vill radera de valda favoritkategorierna?\\nAll manga i dem går förlorade och du kan inte ångra åtgärden.</string>\n    <string name=\"reorder\">Ändra ordning</string>\n    <string name=\"exit_confirmation_summary\">Tryck Tillbaka två gånger för att avsluta appen</string>\n    <string name=\"history_shortcuts\">Visa genvägar för nyligen läst manga</string>\n    <string name=\"network_unavailable\">Ingen nätverksanslutning tillgänglig</string>\n    <string name=\"source_disabled\">Källa inaktiverad</string>\n    <string name=\"mark_as_current\">Markera som aktuell</string>\n    <string name=\"language\">Språk</string>\n    <string name=\"enable_logging_summary\">Spela in några handlingar i felsökningssyfte. Slå inte på om du är osäker på vad du gör</string>\n    <string name=\"chapters_grid_view\">Rutnätsvy</string>\n    <string name=\"sync_title\">Synkronisera din data</string>\n    <string name=\"no_manga_sources\">Inga mangakällor</string>\n    <string name=\"empty\">Tom</string>\n    <string name=\"history_cleared\">Historiken rensad</string>\n    <string name=\"no_chapters\">Inga kapitel</string>\n    <string name=\"incognito_mode\">Inkognitoläge</string>\n    <string name=\"automatic_scroll\">Automatisk rullning</string>\n    <string name=\"reader_info_pattern\">Kap. %1$d/%2$d Sida %3$d/%4$d</string>\n    <string name=\"reset\">Återställ</string>\n    <string name=\"text_unsaved_changes_prompt\">Spara eller kasta osparade ändringar?</string>\n    <string name=\"compact\">Kompakt</string>\n    <string name=\"random\">Slumpmässigt</string>\n    <string name=\"no_manga_sources_text\">Slå på mangakällor för att läsa manga online</string>\n    <string name=\"options\">Alternativ</string>\n    <string name=\"removed_from_favourites\">Borttagen från favoriter</string>\n    <string name=\"explore\">Utforska</string>\n    <string name=\"comics_archive\">Seriearkiv</string>\n    <string name=\"folder_with_images\">Mapp med bilder</string>\n    <string name=\"importing_manga\">Mangaimport</string>\n    <string name=\"import_completed\">Import slutförd</string>\n    <string name=\"import_completed_hint\">Du kan radera originalfilen från lagringen för att spara utrymme</string>\n    <string name=\"import_will_start_soon\">Importeringen börjar snart</string>\n    <string name=\"manga_error_description_pattern\">Felinformation:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Försök att &lt;a href=%2$s&gt;öppna manga i en webbläsare&lt;/a&gt; för att säkerställa att den finns tillgänglig hos källan&lt;br&gt;2. Kolla att du använder &lt;a href=kotatsu://about&gt;den senaste versionen av Kotatsu&lt;br&gt;3. Om den finns tillgänglig, skicka en felrapport till utvecklarna.</string>\n    <string name=\"invalid_server_address_message\">Felaktig serveradress</string>\n    <string name=\"saved_manga\">Sparade manga</string>\n    <string name=\"pages_cache\">Sidocache</string>\n    <string name=\"other_cache\">Annan cache</string>\n    <string name=\"feed\">Flöde</string>\n    <string name=\"storage_usage\">Lagringsanvändning</string>\n    <string name=\"available\">Tillgänglig</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"server_error\">Servern gav felet %1$d. Var god försök igen senare</string>\n    <string name=\"history_shortcuts_summary\">Visa nyligen läst manga när du trycker länge på app-ikonen</string>\n    <string name=\"brightness\">Ljusstyrka</string>\n    <string name=\"clear_new_chapters_counters\">Rensa även information om nya kapitel</string>\n    <string name=\"retry\">Försök igen</string>\n    <string name=\"state_upcoming\">Kommande</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Serier</string>\n    <string name=\"allow_unstable_updates_summary\">Ta emot aviseringar om instabila uppdateringar</string>\n    <string name=\"got_it\">Jag förstår</string>\n    <string name=\"pause\">Pausa</string>\n    <string name=\"restore\">Återställ</string>\n    <string name=\"genres_exclude\">Exkludera genrer</string>\n    <string name=\"content_rating\">Innehållsklassificering</string>\n    <string name=\"reader_control_ltr_summary\">Justera inte hållet sidorna vänds åt i läsläget, t.ex. trycker du på höger piltangent bläddrar du alltid framåt. Denna inställning påverkar endast inmatningsenheter</string>\n    <string name=\"theme_name_dynamic\">Dynamisk</string>\n    <string name=\"show_in_grid_view\">Visa i rutnätsvy</string>\n    <string name=\"nothing_here\">Det finns inget här</string>\n    <string name=\"services\">Tjänster</string>\n    <string name=\"allow_unstable_updates\">Tillåt instabila uppdateringar</string>\n    <string name=\"comics_archive_import_description\">Du kan välja en eller flera .cbz- eller .zip-filer. Varje fil kommer läsas in som separata manga.</string>\n    <string name=\"prefetch_content\">Innehållsnedladdning på förhand</string>\n    <string name=\"scrobbling_empty_hint\">För att spåra ditt läsförlopp, tryck på Meny → Spårning på mangans informationsskärm.</string>\n    <string name=\"ignore_ssl_errors\">Ignorera SSL-fel</string>\n    <string name=\"mirror_switching\">Välj spegelserver automatiskt</string>\n    <string name=\"resume\">Återuppta</string>\n    <string name=\"paused\">Pausad</string>\n    <string name=\"remove_completed\">Ta bort färdiglästa</string>\n    <string name=\"downloads_wifi_only\">Ladda endast ned via Wi-Fi</string>\n    <string name=\"suggestions_notifications_summary\">Visa ibland aviseringar med föreslagna manga</string>\n    <string name=\"more\">Mer</string>\n    <string name=\"folder_with_images_import_description\">Du kan välja en mapp med arkiv eller bilder. Varje arkiv (eller undermapp) kommer läsas in som ett kapitel.</string>\n    <string name=\"enable\">Aktivera</string>\n    <string name=\"no_thanks\">Nej tack</string>\n    <string name=\"cancel_all_downloads_confirm\">Alla aktiva nedladdningar kommer avbrytas och delvis nedladdat data går förlorat</string>\n    <string name=\"text_downloads_list_holder\">Du har inga nedladdningar</string>\n    <string name=\"downloads_resumed\">Nedladdningarna har återupptagits</string>\n    <string name=\"downloads_paused\">Nedladdningarna har pausats</string>\n    <string name=\"address\">Adress</string>\n    <string name=\"downloaded\">Nedladdad</string>\n    <string name=\"username\">Användarnamn</string>\n    <string name=\"invalid_port_number\">Ogiltigt portnummer</string>\n    <string name=\"network\">Nätverk</string>\n    <string name=\"download_option_first_n_chapters\">%s första</string>\n    <string name=\"download_option_next_unread_n_chapters\">Kommande %s olästa</string>\n    <string name=\"color_light\">Ljus</string>\n    <string name=\"search_hint\">Skriv in en mangatitel, genre eller namn på källa</string>\n    <string name=\"unknown\">Okänd</string>\n    <string name=\"error_corrupted_file\">Ogiltigt data har returnerats eller så är filen korrupt</string>\n    <string name=\"reader_zoom_buttons_summary\">Huruvida zoomknapparna ska visas i det nedre högra hörnet</string>\n    <string name=\"enhanced_colors_summary\">Minskar på färgartefakter men kan på påverka prestanda</string>\n    <string name=\"enhanced_colors\">32-bitars färgläge</string>\n    <string name=\"frequency_every_day\">Varje dag</string>\n    <string name=\"last_successful_backup\">Senast lyckad säkerhetskopia: %s</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"manual\">Manuell</string>\n    <string name=\"available_d\">Tillgängliga: %1$d</string>\n    <string name=\"disable_nsfw_summary\">Inaktivera vuxenkällor och visa om möjligt inte manga för vuxna i listan</string>\n    <string name=\"state_paused\">Pausad</string>\n    <string name=\"reader_optimize\">Minska minnesanvändning (beta)</string>\n    <string name=\"reader_optimize_summary\">Minska kvalitén på sidor som inte visas för att använda mindre minne</string>\n    <string name=\"error_multiple_genres_not_supported\">Filtrering efter flera genrer stöds inte av denna mangakälla</string>\n    <string name=\"downloads_settings_info\">Du kan aktivera långsam nedladdning för varje mangakälla individuellt i källans inställningar om du har problem med att servern blockerar dig</string>\n    <string name=\"skip\">Hoppa över</string>\n    <string name=\"globally\">Globalt</string>\n    <string name=\"grayscale\">Gråskala</string>\n    <string name=\"this_manga\">Denna manga</string>\n    <string name=\"webtoon_zoom\">Zoomning i webtoons</string>\n    <string name=\"reader_slider\">Visa sidväxlarreglaget</string>\n    <string name=\"sync_auth_hint\">Du kan logga in i ett existerande konto eller skapa ett nytt</string>\n    <string name=\"speed\">Hastighet</string>\n    <string name=\"show_on_shelf\">Visa på Bokhyllan</string>\n    <string name=\"find_similar\">Hitta liknande</string>\n    <string name=\"sync_settings\">Synkroniseringsinställningar</string>\n    <string name=\"mirror_switching_summary\">Byt automatiskt mangakällors domän om spegelservrar finns tillgängliga</string>\n    <string name=\"cancel_all\">Avbryt alla</string>\n    <string name=\"web_view_unavailable\">WebView är inte tillgängligt; kontrollera om en WebView-leverantör är installerad</string>\n    <string name=\"clear_network_cache\">Rensa nätverkscachen</string>\n    <string name=\"invalid_value_message\">Ogiltigt värde</string>\n    <string name=\"invert_colors\">Invertera färger</string>\n    <string name=\"restore_summary\">Återställ från en tidigare säkerhetskopia</string>\n    <string name=\"data_and_privacy\">Data och integritet</string>\n    <string name=\"suggestions_wifi_only_summary\">Uppdatera inte förslag vid användning av anslutningar med datapriser</string>\n    <string name=\"tracker_wifi_only_summary\">Kolla inte efter nya kapitel vid användning av anslutningar med datapriser</string>\n    <string name=\"advanced\">Avancerat</string>\n    <string name=\"lock_screen_rotation\">Lås skärmens rotation</string>\n    <string name=\"order_added\">Tillagd</string>\n    <string name=\"no_manga_sources_catalog_text\">Det finns inga källor tillgängliga i denna sektion eller så har alla redan lagts till.\\nHåll utkik</string>\n    <string name=\"source_enabled\">Källa aktiverad</string>\n    <string name=\"manage_sources\">Hantera källor</string>\n    <string name=\"download_started\">Nedladdning påbörjades</string>\n    <string name=\"manga_list\">Mangalista</string>\n    <string name=\"email_password_enter_hint\">Skriv in din email och lösenord för att fortsätta</string>\n    <string name=\"images_procy_description\">Använd tjänsten wsrv.nl för att minska på nätverkstrafiken och påskynda bildnedladdning om möjligt</string>\n    <string name=\"show_pages_numbers_summary\">Visa sidnummer i skärmens nedre hörn</string>\n    <string name=\"download_option_all_chapters\">Alla kapitel med översättning %s</string>\n    <string name=\"voice_search\">Röstsökning</string>\n    <string name=\"related_manga\">Relaterad manga</string>\n    <string name=\"color_black\">Svart</string>\n    <string name=\"related_manga_summary\">Visa en lista över relaterade manga. I vissa fall kan den vara felaktig eller saknas</string>\n    <string name=\"disable_nsfw\">Inaktivera vuxeninnehåll</string>\n    <string name=\"on_device\">På enheten</string>\n    <string name=\"directories\">Mappar</string>\n    <string name=\"zoom_in\">Zooma in</string>\n    <string name=\"moved_to_top\">Flyttad till toppen</string>\n    <string name=\"zoom_out\">Zooma ut</string>\n    <string name=\"keep_screen_on\">Håll skärm påslagen</string>\n    <string name=\"keep_screen_on_summary\">Stäng inte av skärmen medan du läser manga</string>\n    <string name=\"list_options\">Listalternativ</string>\n    <string name=\"periodic_backups\">Regelbunden säkerhetskopiering</string>\n    <string name=\"frequency_every_2_days\">Varannan dag</string>\n    <string name=\"frequency_once_per_week\">En gång i veckan</string>\n    <string name=\"frequency_twice_per_month\">Två gånger i månaden</string>\n    <string name=\"backups_output_directory\">Säkerhetskopiornas utmatningsmapp</string>\n    <string name=\"error_search_not_supported\">Sökfunktionen stöds inte av denna mangakälla</string>\n    <string name=\"by_name_reverse\">Namn omvänd</string>\n    <string name=\"show\">Visa</string>\n    <string name=\"images_proxy_title\">Proxy för bildoptimisering</string>\n    <string name=\"too_many_requests_message\">För många förfrågningar. Försök igen senare</string>\n    <string name=\"type\">Typ</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"show_suspicious_content\">Visa misstänksamt innehåll</string>\n    <string name=\"color_theme\">Färgtema</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"server_address\">Serveradress</string>\n    <string name=\"sync_host_description\">Du kan använda din egna synkroniseringsserver eller den som är standard. Ändra inte detta om du inte är säker på vad du gör.</string>\n    <string name=\"reader_control_ltr\">Ergonomiska kontroller i läsläge</string>\n    <string name=\"sources_reorder_tip\">Tryck och håll ned på ett objekt för att ändra deras ordning</string>\n    <string name=\"settings_apply_restart_required\">Var god starta om appen för att verkställa ändringarna</string>\n    <string name=\"clear_source_cookies_summary\">Rensa kakorna för en specifik domän. I flesta fall ogiltiggörs tidigare auktorisering</string>\n    <string name=\"download_option_whole_manga\">Hela mangan</string>\n    <string name=\"webtoon_zoom_summary\">Tillåt inzoomningsgest i webtoon-läge</string>\n    <string name=\"download_option_all_unread_b\">Alla olästa kapitel (%s)</string>\n    <string name=\"pick_custom_directory\">Välj en egen mapp</string>\n    <string name=\"download_option_all_unread\">Alla olästa kapitel</string>\n    <string name=\"local_manga_directories\">Lokala mangamappar</string>\n    <string name=\"this_month\">Denna månad</string>\n    <string name=\"description\">Beskrivning</string>\n    <string name=\"background\">Bakgrund</string>\n    <string name=\"data_not_restored\">Data återställdes inte</string>\n    <string name=\"color_dark\">Mörk</string>\n    <string name=\"manage_categories\">Hantera kategorier</string>\n    <string name=\"color_white\">Vit</string>\n    <string name=\"languages\">Språk</string>\n    <string name=\"captcha_required_summary\">%s kräver att en captcha löses för att fungera korrekt</string>\n    <string name=\"to_top\">Till toppen</string>\n    <string name=\"items_limit_exceeded\">Inga fler objekt kan läggas till</string>\n    <string name=\"reader_zoom_buttons\">Visa zoomknappar</string>\n    <string name=\"categories\">Kategorier</string>\n    <string name=\"content_type_other\">Annat</string>\n    <string name=\"sources_catalog\">Källkatalog</string>\n    <string name=\"no_manga_sources_found\">Inga tillgängliga mangakällor hittades med din sökning</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Filtrering efter både genre och språk stöds inte av denna källa</string>\n    <string name=\"genres_search_hint\">Börja skriva in namnet på genren</string>\n    <string name=\"color_correction_apply_text\">De här inställningarna kan verkställas globalt eller endast för den aktuella mangan. Om du väljer globalt kommer individuella inställningar inte att överskuggas.</string>\n    <string name=\"apply\">Verkställ</string>\n    <string name=\"welcome_text\">Var god välj vilka innehållskällor du vill aktivera. Detta kan även konfigureras i inställningarna vid ett senare tillfälle</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Kan hjälpa till att få nedladdningen att sätta igång om du har problem med det</string>\n    <string name=\"suggestions_enable_prompt\">Vill du få personliga mangarekommendationer?</string>\n    <string name=\"suggestion_manga\">Förslag: %s</string>\n    <string name=\"periodic_backups_enable\">Aktivera regelbunden säkerhetskopiering</string>\n    <string name=\"downloads_removed\">Nedladdningarna har tagits bort</string>\n    <string name=\"downloads_cancelled\">Nedladdningarna har avbrutits</string>\n    <string name=\"too_many_requests_message_retry\">För många förfrågningar. Försök igen efter %s</string>\n    <string name=\"no_access_to_file\">Du har inte tillgång till denna fil eller mapp</string>\n    <string name=\"download_option_manual_selection\">Välj kapitel manuellt</string>\n    <string name=\"reader_info_bar_summary\">Visa aktuell tid och läsförlopp längst upp på skärmen</string>\n    <string name=\"password\">Lösenord</string>\n    <string name=\"authorization_optional\">Auktorisering (valfritt)</string>\n    <string name=\"by_relevance\">Relevans</string>\n    <string name=\"data_not_restored_text\">Kontrollera att du har valt den rätta säkerhetskopian</string>\n    <string name=\"suggest_new_sources\">Föreslå nya källor efter appen uppdaterats</string>\n    <string name=\"frequency_once_per_month\">En gång i månaden</string>\n    <string name=\"remove_completed_downloads_confirm\">Din nedladdningshistorik kommer tas bort permanent. Inga nedladdade filer påverkas</string>\n    <string name=\"downloads_wifi_only_summary\">Stoppa nedladdningar vid anslutning till ett mobilt nätverk</string>\n    <string name=\"port\">Port</string>\n    <string name=\"progress\">Förlopp</string>\n    <string name=\"main_screen_sections\">Huvudskärmssektioner</string>\n    <string name=\"suggest_new_sources_summary\">Tillfråga om att aktivera nyligen tillagda källor efter en applikationsuppdatering</string>\n    <string name=\"sync_auth\">Logga in på synkroniseringskonto</string>\n    <string name=\"in_progress\">Pågår</string>\n    <string name=\"state\">Publiceringsstatus</string>\n    <string name=\"volume_\">Volym %d</string>\n    <string name=\"default_tab\">Standardflik</string>\n    <string name=\"mark_as_completed\">Markera som färdigläst</string>\n    <string name=\"mark_as_completed_prompt\">Markera den valda mangan som färidgläst?\\n\\nVarning: aktuellt läsförlopp kommer att gå förlorat.</string>\n    <string name=\"volume_unknown\">Okänd volym</string>\n    <string name=\"incognito_mode_hint\">Ditt läsförlopp kommer ej att sparas</string>\n    <string name=\"show_menu\">Visa meny</string>\n    <string name=\"toggle_ui\">Visa/göm gränssnitt</string>\n    <string name=\"prev_chapter\">Tidigare kapitel</string>\n    <string name=\"next_chapter\">Nästa kapitel</string>\n    <string name=\"prev_page\">Tidigare sida</string>\n    <string name=\"next_page\">Nästa sida</string>\n    <string name=\"reader_actions_summary\">Konfigurera handlingarna för de tryckbara skärmområdena</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Använd volymknapparna för att bläddra</string>\n    <string name=\"error_multiple_states_not_supported\">Filtrering efter flera publiceringsstatusar stöds inte av denna mangakälla</string>\n    <string name=\"error_filter_states_genre_not_supported\">Filtrering efter både genrer och publiceringsstatusar stöds inte av denna källa</string>\n    <string name=\"unsupported_backup_message\">Var god välj en vederbörlig Kotatsu-säkerhetskopiering</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_novel\">Roman</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"error_connection_reset\">Anslutning återställd av fjärrvärden</string>\n    <string name=\"restoring_backup\">Återställer säkerhetskopia</string>\n    <string name=\"incognito\">Inkognito</string>\n    <string name=\"backup_tg_check\">Kontrollera om API:n fungerar</string>\n    <string name=\"backup_tg_echo\">Testmeddelande</string>\n    <string name=\"backup_tg_id_not_set\">Chatt-ID är inte inställt</string>\n    <string name=\"telegram_chat_id\">Telegram chatt-ID</string>\n    <string name=\"open_telegram_bot\">Öppna Telegram-botten</string>\n    <string name=\"hours_short\">%d t</string>\n    <string name=\"reading_time_estimation_summary\">Den beräknade lästiden kan vara inexakt</string>\n    <string name=\"pages_saving\">Sparar sidor</string>\n    <string name=\"preferred_download_format\">Föredraget nedladdningsformat</string>\n    <string name=\"suggestions_unavailable_text\">Förslagsfunktionen är inaktiverad</string>\n    <string name=\"statistics\">Statistik</string>\n    <string name=\"stats_cleared\">Statistik rensad</string>\n    <string name=\"clear_stats_confirm\">Är du säker på att du vill rensa all läsarstatistik? Det går inte att ångra denna åtgärd.</string>\n    <string name=\"empty_stats_text\">Det finns ingen statistik för den valda perioden</string>\n    <string name=\"migrate\">Migrera</string>\n    <string name=\"migration_completed\">Migrering slutförd</string>\n    <string name=\"chapters_deleted_pattern\">Raderade %1$s, rensade %2$s</string>\n    <string name=\"delete_read_chapters_summary\">Rader kapitel du redan har läst från lokal lagring för att spara utrymme</string>\n    <string name=\"split_by_translations\">Dela upp efter översättningar</string>\n    <string name=\"enable_source\">Aktivera källa</string>\n    <string name=\"unsupported_source\">Denna mangakälla stöds ej</string>\n    <string name=\"show_pages_thumbs\">Visa sidors tumnaglar</string>\n    <string name=\"show_pages_thumbs_summary\">Aktivera \\\"Sidor\\\"-fliken på informationsskärmen</string>\n    <string name=\"error_no_data_received\">Ingen data tags emot från servern</string>\n    <string name=\"minutes_short\">%d m</string>\n    <string name=\"seconds_short\">%d s</string>\n    <string name=\"less_frequently\">Mindre frekvent</string>\n    <string name=\"more_frequently\">Mer frekvent</string>\n    <string name=\"frequency_of_check\">Intervall mellan kontroller</string>\n    <string name=\"search_suggestions\">Sökförslag</string>\n    <string name=\"recent_queries\">Senaste sökningar</string>\n    <string name=\"all_languages\">Alla språk</string>\n    <string name=\"source_pinned\">Källa fastnålad</string>\n    <string name=\"percent_read\">Procent läst</string>\n    <string name=\"plugin_incompatible\">Inkompatibelt tillägg eller internt fel. Kontrollera att du använder den senaste versionen av tillägget och Kotatsu</string>\n    <string name=\"connection_ok\">Anslutningen är OK</string>\n    <string name=\"invalid_proxy_configuration\">Ogiltig proxykonfiguration</string>\n    <string name=\"sort_order_desc\">Fallande</string>\n    <string name=\"scrobbler_auth_required\">Logga in på %s för att fortsätta</string>\n    <string name=\"recently_added\">Nyligen tillagda</string>\n    <string name=\"added_long_ago\">Tillagda för länge sedan</string>\n    <string name=\"popular_in_hour\">Populära denna timme</string>\n    <string name=\"popular_today\">Populära idag</string>\n    <string name=\"popular_in_week\">Populära denna vecka</string>\n    <string name=\"filter_search_warning\">Denna källa stöder inte sök med filter. Dina filter har blivit borttagna</string>\n    <string name=\"portrait\">Stående</string>\n    <string name=\"content_type_image_set\">Bildsamling</string>\n    <string name=\"show_labels_in_navbar\">Visa etiketter i navigeringsfältet</string>\n    <string name=\"captcha_required_message\">Denna källa kräver att du löser en captcha för att fortsätta</string>\n    <string name=\"reader_info_bar_transparent\">Transparent informationsfält i läsaren</string>\n    <string name=\"category_hidden_done\">Denna kategori har gömts från huvudskärmen och är tillgänglig via Meny → Hantera kategorier</string>\n    <string name=\"ask_for_dest_dir_every_time\">Efterfråga destinationsmappen varje gång</string>\n    <string name=\"clear_stats\">Rensa statistik</string>\n    <string name=\"delete_read_chapters\">Radera färdiglästa kapitel</string>\n    <string name=\"pages_read_s\">Antal lästa sidor: %s</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" från \\\"%2$s\\\" kommer att bli ersatt med \\\"%3$s\\\" från \\\"%4$s\\\" i din historik och favoriter (om närvarande)</string>\n    <string name=\"enable_all_sources\">Aktivera alla mangakällor</string>\n    <string name=\"enable_all_sources_summary\">Alla tillgängliga mangakällor kommer aktiveras permanent</string>\n    <string name=\"all_sources_enabled\">Alla källor har aktiverats</string>\n    <string name=\"crop_pages\">Beskär sidor</string>\n    <string name=\"use_two_pages_landscape\">Använd dubbelsidig layout vid liggande skärmorientering (beta)</string>\n    <string name=\"handle_links\">Hantera länkar</string>\n    <string name=\"handle_links_summary\">Hantera mangalänkar från externa applikationer (t.ex. webbläsare). Du kan även behöva tillåta det manuellt i systemets inställningar för applikationen</string>\n    <string name=\"email\">E-mail</string>\n    <string name=\"sources_unpinned\">Källor lösgjorda</string>\n    <string name=\"plugin_incompatible_with_cause\">Tilläggsfel: %s\\nKontrollera att du använder den senaste versionen av tillägget och Kotatsu</string>\n    <string name=\"show_quick_filters\">Visa snabbfilter</string>\n    <string name=\"skip_all\">Hoppa över alla</string>\n    <string name=\"updated_long_ago\">Uppdaterad för länge sedan</string>\n    <string name=\"unpopular\">Opopulär</string>\n    <string name=\"low_rating\">Dåligt betyg</string>\n    <string name=\"sort_order_asc\">Stigande</string>\n    <string name=\"show_quick_filters_summary\">Låter dig filtrera mangalistor efter särskilda parametrar</string>\n    <string name=\"popularity\">Popularitet</string>\n    <string name=\"unstable_feature\">Instabil funktion</string>\n    <string name=\"unstable_feature_summary\">Denna funktion är experimentell. Var god säkerställ att du har en säkerhetskopia för att undvika dataförlust</string>\n    <string name=\"by_date\">Datum</string>\n    <string name=\"popular_in_month\">Populära denna månad</string>\n    <string name=\"original_language\">Originalspråk</string>\n    <string name=\"demographics\">Demografi</string>\n    <string name=\"user_manual\">Användarhandbok</string>\n    <string name=\"source_code\">Källkod</string>\n    <string name=\"telegram_group\">Telegram-grupp</string>\n    <string name=\"genre\">Genre</string>\n    <string name=\"download_added\">Nedladdning tillagd</string>\n    <string name=\"more_options\">Fler val</string>\n    <string name=\"download_over_cellular\">Nedladdning sker över mobilnätet</string>\n    <string name=\"download_cellular_confirm\">Tillåta nedladdningar över mobilnätet?</string>\n    <string name=\"chapters_all\">Alla</string>\n    <string name=\"dont_allow\">Tillåt inte</string>\n    <string name=\"allow_always\">Tillåt alltid</string>\n    <string name=\"landscape\">Liggande</string>\n    <string name=\"reading_stats\">Läsarstatistik</string>\n    <string name=\"save_manga_confirm\">Spara den valda mangan? Detta kan komma att använda nätverket och ta upp lagringsutrymme</string>\n    <string name=\"save_manga\">Spara manga</string>\n    <string name=\"allow_once\">Tillåt en gång</string>\n    <string name=\"ask_every_time\">Fråga varje gång</string>\n    <string name=\"access_denied_403\">Åtkomst nekad (403)</string>\n    <string name=\"max_backups_count\">Max antal säkerhetskopior</string>\n    <string name=\"no_fix_required\">Ingen reparation krävdes för \\\"%s\\\"</string>\n    <string name=\"no_alternatives_found\">Inga alternativ funnits för \\\"%s\\\"</string>\n    <string name=\"clear_database\">Rensa databas</string>\n    <string name=\"clear_database_summary\">Radera information om manga som inte används</string>\n    <string name=\"send_backups_telegram\">Skicka säkerhetskopior via Telegram</string>\n    <string name=\"telegram_chat_id_summary\">Ange chatt-ID:t som säkerhetskopior ska skickas till</string>\n    <string name=\"delete_old_backups\">Radera gamla säkerhetskopior</string>\n    <string name=\"delete_old_backups_summary\">Radera gamla säkerhetskopior automatiskt för att spara lagringsutrymme</string>\n    <string name=\"error_image_format\">Bildformat saknar stöd: %s</string>\n    <string name=\"translation\">Översättning</string>\n    <string name=\"manga_migration\">Mangamigrering</string>\n    <string name=\"delete_read_chapters_auto\">Radera automatiskt färdiglästa kapitel</string>\n    <string name=\"webtoon_gaps_summary\">Visa vertikala springor mellan sidorna i webtoon-läge</string>\n    <string name=\"hours_minutes_short\">%1$d t %2$d m</string>\n    <string name=\"pin_navigation_ui\">Fastnåla navigeringsgränssnittet</string>\n    <string name=\"pin_navigation_ui_summary\">Göm inte navigeringsfältet och sökvyn vid rullning</string>\n    <string name=\"suggested_queries\">Föreslagna sökningar</string>\n    <string name=\"authors\">Författare</string>\n    <string name=\"disable\">Inaktivera</string>\n    <string name=\"sources_disabled\">Källor inaktiverade</string>\n    <string name=\"disable_connectivity_check_summary\">Hoppar över anslutningskontrollen ifall du har problem med den (t.ex. offline-läge aktiveras trots att du har en internetanslutning)</string>\n    <string name=\"disable_connectivity_check\">Inaktivera anslutningskontroll</string>\n    <string name=\"pin\">Fastnåla</string>\n    <string name=\"screenshots_block_incognito\">Blockera i inkognitoläge</string>\n    <string name=\"image_server\">Föredragen bildserver</string>\n    <string name=\"percent_left\">Procent kvar</string>\n    <string name=\"source_unpinned\">Källa lösgjord</string>\n    <string name=\"reader_controls_in_bottom_bar\">Läsarkontroller i nedre fältet</string>\n    <string name=\"chapters_and_pages\">Kapitel och sidor</string>\n    <string name=\"pages_slider\">Sidväxlarreglage</string>\n    <string name=\"screen_rotation_unlocked\">Skärmrotationen har låsts upp</string>\n    <string name=\"screen_rotation_locked\">Skärmrotationen har låsts fast</string>\n    <string name=\"fullscreen_mode\">Helskärmsläge</string>\n    <string name=\"reader_fullscreen_summary\">Göm statusfältet och navigeringsfältet</string>\n    <string name=\"other_manga\">Annan manga</string>\n    <string name=\"default_page_save_dir\">Standardmapp för sparade sidor</string>\n    <string name=\"remove_from_history\">Ta bort från historiken</string>\n    <string name=\"less_than_minute\">Mindre än en minut</string>\n    <string name=\"chapters_left\">Kapitel kvar</string>\n    <string name=\"delete_read_chapters_prompt\">Detta kommer permanent radera alla kapitel märkta som färdiglästa från lokal lagring. Du kan ladda ned dom på nytt senare men de importerade kapitlena kan gå förlorade för evigt</string>\n    <string name=\"ignore_ssl_errors_summary\">Du kan inaktivera verifiering av SSL-certifikat om du stöter på SSL-relaterade problem när du försöker nå nätverksresurser. Detta kan påverka anslutningens säkerhet. Omstart av applikationen krävs för att ändringen ska verkställas.</string>\n    <string name=\"chapters_read\">Kapitel lästa</string>\n    <string name=\"sources_pinned\">Källor fastnålade</string>\n    <string name=\"download_new_chapters\">Ladda ned nya kapitel</string>\n    <string name=\"backup_restored_background\">Säkerhetskopian kommer att återställas i bakgrunden</string>\n    <string name=\"error_not_image\">Ogiltigt format: förväntade en bild men fick %s</string>\n    <string name=\"downloads_background\">Bakgrundsnedladdningar</string>\n    <string name=\"scrobbler_auth_intro\">Logga in för att ställa in integreringen med %s. Detta tillåter dig att spåra ditt mangaläsförlopp och status</string>\n    <string name=\"manga_with_downloaded_chapters\">Manga med nedladdade kapitel</string>\n    <string name=\"popular_in_year\">Populära detta år</string>\n    <string name=\"chapter_selection_hint\">Du kan välja kapitlena du vill ladda ned genom att långtrycka på ett objekt i kapitellistan.</string>\n    <string name=\"year\">År</string>\n    <string name=\"manga_fix_prompt\">Denna funktion letar efter alternativa källor för den valda mangan. Processen kommer ta lite tid och fortgår i bakgrunden</string>\n    <string name=\"destination_directory\">Destinationsmapp</string>\n    <string name=\"screen_orientation\">Skärmorientering</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) har ersatts av \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"fixing_manga\">Reparera manga</string>\n    <string name=\"fixed\">Reparation lyckades</string>\n    <string name=\"external_source\">Extern/tillägg</string>\n    <string name=\"reading_time_estimation\">Visa beräknad lästid</string>\n    <string name=\"start_download\">Påbörja nedladdning</string>\n    <string name=\"runs_on_app_start\">Körs när applikationen startar</string>\n    <string name=\"last_used\">Senast använd</string>\n    <string name=\"webtoon_gaps\">Springor i webtoon-läge</string>\n    <string name=\"split_by_translations_summary\">Visa kapitel med olika översättningar separat istället för i en sammanslagen lista</string>\n    <string name=\"order_oldest\">Äldsta</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d s</string>\n    <string name=\"show_updated\">Visa uppdaterade</string>\n    <string name=\"blocked_by_server_message\">Du är blockerad av servern. Försök med en annan nätverksanslutning (VPN, proxy, osv.)</string>\n    <string name=\"tracker_debug_info_summary\">Felsökningsinformation om bakgrundskontroller av nya kapitel</string>\n    <string name=\"disable_nsfw_notifications_summary\">Visa inte aviseringar gällande uppdateringar av manga med vuxeninnehåll</string>\n    <string name=\"disable_nsfw_notifications\">Inaktivera aviseringar för vuxeninnehåll</string>\n    <string name=\"not_in_favorites\">Ej i favoriterna</string>\n    <string name=\"missing_storage_permission\">Appen har inte tillåtelse att få tillgång till manga på extern lagring</string>\n    <string name=\"fix\">Reparera</string>\n    <string name=\"no_chapters_deleted\">Inga kapitel har raderats</string>\n    <string name=\"global_search\">Sök globalt</string>\n    <string name=\"author\">Författare</string>\n    <string name=\"rating\">Betyg</string>\n    <string name=\"source\">Källa</string>\n    <string name=\"search_everywhere\">Sök överallt</string>\n    <string name=\"badges_in_lists\">Märken i listor</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"rating_safe\">Alla åldrar</string>\n    <string name=\"rating_suggestive\">Suggestiv</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"backup_frequency\">Säkerhetskopieringsintervall</string>\n    <string name=\"backup_date_\">Säkerhetskopieringsdatum: %s</string>\n    <string name=\"last_read\">Senast läst</string>\n    <string name=\"reader_actions\">Läsarhandlingar</string>\n    <string name=\"switch_pages_volume_buttons\">Aktivera volymknapparna</string>\n    <string name=\"state_abandoned\">Övergiven</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-sw360dp/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\t<bool name=\"config_materialPreferenceIconSpaceReserved\" tools:ignore=\"MissingDefaultResource,UnusedAttribute\" tools:node=\"replace\">\n\t\tfalse\n\t</bool>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ta/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d மணித்துளி</item>\n        <item quantity=\"other\">%1$d நிமிடங்கள்</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d உருப்படி</item>\n        <item quantity=\"other\">%1$d உருப்படிகள்</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d புதிய அத்தியாயம்</item>\n        <item quantity=\"other\">%1$d புதிய அத்தியாயங்கள்</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d அத்தியாயம்</item>\n        <item quantity=\"other\">%1$d அத்தியாயங்கள்</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d மணித்துளி முன்பு</item>\n        <item quantity=\"other\">%1$d நிமிடங்களுக்கு முன்பு</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d மணி நேரத்திற்கு முன்பு</item>\n        <item quantity=\"other\">%1$d மணி நேரத்திற்கு முன்பு</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d நாள் முன்பு</item>\n        <item quantity=\"other\">%1$d சில நாட்களுக்கு முன்பு</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d மாதத்திற்கு முன்பு</item>\n        <item quantity=\"other\">%1$d மாதங்களுக்கு முன்பு</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d மணி</item>\n        <item quantity=\"other\">%1$d மணிநேரம்</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ta/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" உள்ளக சேமிப்பகத்திலிருந்து நீக்கப்பட்டது</string>\n    <string name=\"save_page\">பக்கத்தை சேமிக்கவும்</string>\n    <string name=\"page_saved\">பக்கம் சேமிக்கப்பட்டது</string>\n    <string name=\"pages_saved\">பக்கங்கள் சேமிக்கப்பட்டன</string>\n    <string name=\"share_image\">படத்தைப் பகிரவும்</string>\n    <string name=\"_import\">இறக்குமதி</string>\n    <string name=\"delete\">நீக்கு</string>\n    <string name=\"operation_not_supported\">இந்த செயல்பாடு ஆதரிக்கப்படவில்லை</string>\n    <string name=\"text_file_not_supported\">ஒரு சிப் அல்லது சிபிஇசட் கோப்பைத் தேர்ந்தெடுக்கவும்.</string>\n    <string name=\"no_description\">விளக்கம் இல்லை</string>\n    <string name=\"clear_pages_cache\">பக்க கேச் அழிக்கவும்</string>\n    <string name=\"text_file_sizes\">b | kb | Mb | g | tb</string>\n    <string name=\"grid_size\">கட்டம் அளவு</string>\n    <string name=\"standard\">தரநிலை</string>\n    <string name=\"webtoon\">வெப்டூன்</string>\n    <string name=\"read_mode\">பயன்முறையைப் படியுங்கள்</string>\n    <string name=\"search_on_s\">%s தேடுங்கள்</string>\n    <string name=\"delete_manga\">கிளைகளை நீக்கு</string>\n    <string name=\"text_delete_local_manga\">சாதனத்திலிருந்து \\\"%s\\\" ஐ நிரந்தரமாக நீக்கவா?</string>\n    <string name=\"_continue\">தொடரவும்</string>\n    <string name=\"reader_settings\">வாசகர் அமைப்புகள்</string>\n    <string name=\"switch_pages\">பக்கங்களை மாற்றவும்</string>\n    <string name=\"error\">பிழை</string>\n    <string name=\"clear_thumbs_cache\">சிறுபடங்களை அழிக்கவும்</string>\n    <string name=\"clear_search_history\">தேடல் வரலாற்றை அழிக்கவும்</string>\n    <string name=\"search_history_cleared\">அழிக்கப்பட்டது</string>\n    <string name=\"internal_storage\">உள் சேமிப்பு</string>\n    <string name=\"external_storage\">வெளிப்புற சேமிப்பு</string>\n    <string name=\"domain\">டொமைன்</string>\n    <string name=\"app_update_available\">பயன்பாட்டின் புதிய பதிப்பு கிடைக்கிறது</string>\n    <string name=\"local_storage\">உள்ளக சேமிப்பு</string>\n    <string name=\"favourites\">பிடித்தவை</string>\n    <string name=\"history\">வரலாறு</string>\n    <string name=\"grid\">வலைவாய்</string>\n    <string name=\"list_mode\">பட்டியல் பயன்முறை</string>\n    <string name=\"settings\">அமைப்புகள்</string>\n    <string name=\"remote_sources\">மங்கா ஆதாரங்கள்</string>\n    <string name=\"loading_\">ஏற்றுகிறது…</string>\n    <string name=\"computing_\">கம்ப்யூட்டிங்…</string>\n    <string name=\"chapter_d_of_d\">அத்தியாயம் %1$d %2$d</string>\n    <string name=\"close\">மூடு</string>\n    <string name=\"try_again\">மீண்டும் முயற்சிக்கவும்</string>\n    <string name=\"retry\">மீண்டும் முயற்சிக்கவும்</string>\n    <string name=\"clear_history\">வரலாற்றை அழிக்கவும்</string>\n    <string name=\"nothing_found\">எதுவும் கிடைக்கவில்லை</string>\n    <string name=\"share_s\">பங்கு %s</string>\n    <string name=\"history_is_empty\">இன்னும் வரலாறு இல்லை</string>\n    <string name=\"read\">படிக்க</string>\n    <string name=\"you_have_not_favourites_yet\">இன்னும் பிடித்தவை இல்லை</string>\n    <string name=\"add_to_favourites\">இது பிடித்தது</string>\n    <string name=\"add_new_category\">புதிய வகை</string>\n    <string name=\"add\">கூட்டு</string>\n    <string name=\"save\">சேமி</string>\n    <string name=\"share\">பங்கு</string>\n    <string name=\"create_shortcut\">குறுக்குவழியை உருவாக்கவும்</string>\n    <string name=\"search\">தேடல்</string>\n    <string name=\"search_manga\">மங்காவைத் தேடுங்கள்</string>\n    <string name=\"manga_downloading_\">பதிவிறக்கம்…</string>\n    <string name=\"processing_\">செயலாக்கம்…</string>\n    <string name=\"download_complete\">பதிவிறக்கம்</string>\n    <string name=\"downloads\">பதிவிறக்கங்கள்</string>\n    <string name=\"by_name\">பெயர்</string>\n    <string name=\"popular\">மக்கள்</string>\n    <string name=\"updated\">புதுப்பிக்கப்பட்டது</string>\n    <string name=\"newest\">புதியது</string>\n    <string name=\"by_rating\">செயல்வரம்பு</string>\n    <string name=\"sort_order\">வரிசைப்படுத்துதல் ஒழுங்கு</string>\n    <string name=\"filter\">வடிப்பி</string>\n    <string name=\"theme\">கருப்பொருள்</string>\n    <string name=\"light\">ஒளி</string>\n    <string name=\"dark\">இருண்ட</string>\n    <string name=\"follow_system\">அமைப்பைப் பின்தொடரவும்</string>\n    <string name=\"pages\">பக்கங்கள்</string>\n    <string name=\"clear\">தெளிவான</string>\n    <string name=\"remove\">அகற்று</string>\n    <string name=\"notifications\">அறிவிப்புகள்</string>\n    <string name=\"open_in_browser\">வலை உலாவியில் திறக்கவும்</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d %2$d</string>\n    <string name=\"new_chapters\">புதிய அத்தியாயங்கள்</string>\n    <string name=\"download\">பதிவிறக்கம்</string>\n    <string name=\"notifications_settings\">அறிவிப்புகள் அமைப்புகள்</string>\n    <string name=\"notification_sound\">அறிவிப்பு ஒலி</string>\n    <string name=\"light_indicator\">எல்.ஈ.டி காட்டி</string>\n    <string name=\"vibration\">அதிர்வு</string>\n    <string name=\"favourites_categories\">பிடித்த பிரிவுகள்</string>\n    <string name=\"remove_category\">அகற்று</string>\n    <string name=\"text_empty_holder_primary\">இது இங்கே காலியாக உள்ளது…</string>\n    <string name=\"text_search_holder_secondary\">வினவலை மறுசீரமைக்க முயற்சிக்கவும்.</string>\n    <string name=\"backup_saved\">காப்புப்பிரதி சேமிக்கப்பட்டது</string>\n    <string name=\"chapter_is_missing\">அத்தியாயம் இல்லை</string>\n    <string name=\"about_app_translation_summary\">இந்த பயன்பாட்டை மொழிபெயர்க்கவும்</string>\n    <string name=\"auth_complete\">அங்கீகரிக்கப்பட்ட</string>\n    <string name=\"auth_not_supported_by\">%s இல் உள்நுழைவது ஆதரிக்கப்படவில்லை</string>\n    <string name=\"about_app_translation\">மொழிபெயர்ப்பு</string>\n    <string name=\"text_clear_cookies_prompt\">நீங்கள் அனைத்து மூலங்களிலிருந்தும் வெளியேறுவீர்கள்</string>\n    <string name=\"genres\">வகைகள்</string>\n    <string name=\"state_finished\">முடிந்தது</string>\n    <string name=\"state_ongoing\">நடந்து கொண்டிருக்கிறது</string>\n    <string name=\"system_default\">இயல்புநிலை</string>\n    <string name=\"screenshots_policy\">திரைக்காட்சி கொள்கை</string>\n    <string name=\"screenshots_block_nsfw\">NSFW இல் தொகுதி</string>\n    <string name=\"screenshots_allow\">இசைவு</string>\n    <string name=\"screenshots_block_all\">எப்போதும் தடுக்கும்</string>\n    <string name=\"suggestions\">பரிந்துரைகள்</string>\n    <string name=\"suggestions_enable\">பரிந்துரைகளை இயக்கவும்</string>\n    <string name=\"suggestions_summary\">உங்கள் விருப்பங்களின் அடிப்படையில் மங்காவை பரிந்துரைக்கவும்</string>\n    <string name=\"invalid_proxy_configuration\">தவறான பதிலாள் உள்ளமைவு</string>\n    <string name=\"mark_as_completed\">முடிக்கப்பட்டபடி குறி</string>\n    <string name=\"content_rating\">உள்ளடக்க மதிப்பீடு</string>\n    <string name=\"genres_exclude\">வகைகளை விலக்கு</string>\n    <string name=\"rating_safe\">பாதுகாப்பானது</string>\n    <string name=\"rating_suggestive\">பரிந்துரைக்கும்</string>\n    <string name=\"rating_adult\">அகவை வந்தோர்</string>\n    <string name=\"mark_as_completed_prompt\">தேர்ந்தெடுக்கப்பட்ட மங்காவை முழுமையாகப் படித்தபடி மார்க்?\\n\\n எச்சரிக்கை: தற்போதைய வாசிப்பு முன்னேற்றம் இழக்கப்படும்.</string>\n    <string name=\"authors\">ஆசிரியர்கள்</string>\n    <string name=\"show_quick_filters_summary\">சில அளவுருக்களால் மங்கா பட்டியல்களை வடிகட்டும் திறனை வழங்குகிறது</string>\n    <string name=\"sfw\">எச்.எஃப்.டபிள்யூ</string>\n    <string name=\"show_quick_filters\">விரைவான வடிப்பான்களைக் காட்டு</string>\n    <string name=\"skip_all\">அனைத்தையும் தவிர்க்கவும்</string>\n    <string name=\"stuck\">சிக்கிக்கொண்டது</string>\n    <string name=\"not_in_favorites\">பிடித்தவைகளில் இல்லை</string>\n    <string name=\"content_type_image_set\">பட தொகுப்பு</string>\n    <string name=\"content_type_artist_cg\">கலைஞர் சி.ஜி</string>\n    <string name=\"content_type_game_cg\">விளையாட்டு சி.ஜி</string>\n    <string name=\"debug\">பிழைத்திருத்தம்</string>\n    <string name=\"source_code\">மூலக் குறியீடு</string>\n    <string name=\"user_manual\">பயனர் கையேடு</string>\n    <string name=\"telegram_group\">தந்தி குழு</string>\n    <string name=\"chapters_all\">அனைத்தும்</string>\n    <string name=\"error_image_format\">ஆதரிக்கப்படாத பட வடிவம்: %s</string>\n    <string name=\"error_not_image\">தவறான வடிவம்: எதிர்பார்க்கப்படும் படம் ஆனால் %s கிடைத்தன</string>\n    <string name=\"start_download\">பதிவிறக்கத் தொடங்குங்கள்</string>\n    <string name=\"save_manga_confirm\">தேர்ந்தெடுக்கப்பட்ட மங்காவைச் சேமிக்கவா? இது போக்குவரத்து மற்றும் வட்டு இடத்தை உட்கொள்ளலாம்</string>\n    <string name=\"save_manga\">மங்காவைக் காப்பாற்றுங்கள்</string>\n    <string name=\"genre\">வகை</string>\n    <string name=\"download_added\">பதிவிறக்கம் சேர்க்கப்பட்டது</string>\n    <string name=\"more_options\">மேலும் விருப்பங்கள்</string>\n    <string name=\"destination_directory\">இலக்கு அடைவு</string>\n    <string name=\"chapter_selection_hint\">அத்தியாய பட்டியலில் உள்ள உருப்படியை நீண்ட சொடுக்கு செய்வதன் மூலம் பதிவிறக்கம் செய்ய அத்தியாயங்களைத் தேர்ந்தெடுக்கலாம்.</string>\n    <string name=\"download_over_cellular\">செல்லுலார் நெட்வொர்க்கில் பதிவிறக்குகிறது</string>\n    <string name=\"color_correction\">வண்ண திருத்தம்</string>\n    <string name=\"brightness\">பிரகாசம்</string>\n    <string name=\"reset\">மீட்டமை</string>\n    <string name=\"network_unavailable\">பிணையம் கிடைக்கவில்லை</string>\n    <string name=\"compact\">கச்சிதமான</string>\n    <string name=\"source_disabled\">மூல முடக்கப்பட்டது</string>\n    <string name=\"show_suspicious_content\">சந்தேகத்திற்கிடமான உள்ளடக்கத்தைக் காட்டு</string>\n    <string name=\"theme_name_dynamic\">மாறும்</string>\n    <string name=\"theme_name_miku\">மிகு</string>\n    <string name=\"theme_name_asuka\">அசுகா</string>\n    <string name=\"theme_name_sakura\">சகுரா</string>\n    <string name=\"theme_name_mamimi\">புன்னகை</string>\n    <string name=\"theme_name_kanade\">கனடா</string>\n    <string name=\"services\">சேவைகள்</string>\n    <string name=\"user_agent\">USERAGENT தலைப்பு</string>\n    <string name=\"settings_apply_restart_required\">இந்த மாற்றங்களைப் பயன்படுத்த விண்ணப்பத்தை மறுதொடக்கம் செய்யுங்கள்</string>\n    <string name=\"show_on_shelf\">அலமாரியில் காட்டு</string>\n    <string name=\"sync_auth_hint\">நீங்கள் ஏற்கனவே இருக்கும் கணக்கில் உள்நுழையலாம் அல்லது புதிய ஒன்றை உருவாக்கலாம்</string>\n    <string name=\"find_similar\">ஒத்ததைக் கண்டறியவும்</string>\n    <string name=\"error_occurred\">பிழை ஏற்பட்டது</string>\n    <string name=\"network_error\">பிணைய பிழை</string>\n    <string name=\"details\">விவரங்கள்</string>\n    <string name=\"chapters\">அத்தியாயங்கள்</string>\n    <string name=\"list\">பட்டியல்</string>\n    <string name=\"detailed_list\">விரிவான பட்டியல்</string>\n    <string name=\"text_local_holder_primary\">முதலில் எதையாவது சேமிக்கவும்</string>\n    <string name=\"recent_manga\">அண்மைக் கால</string>\n    <string name=\"other_storage\">பிற சேமிப்பு</string>\n    <string name=\"create_backup\">தரவு காப்புப்பிரதியை உருவாக்கவும்</string>\n    <string name=\"data_restored\">மீட்டெடுக்கப்பட்டது</string>\n    <string name=\"data_restored_success\">எல்லா தரவுகளும் மீட்டெடுக்கப்பட்டன</string>\n    <string name=\"group\">குழு</string>\n    <string name=\"today\">இன்று</string>\n    <string name=\"tap_to_try_again\">மீண்டும் முயற்சிக்க தட்டவும்</string>\n    <string name=\"silent\">அமைதியாக</string>\n    <string name=\"captcha_required\">கேப்ட்சா தேவை</string>\n    <string name=\"password_length_hint\">கடவுச்சொல் 4 எழுத்துக்கள் அல்லது அதற்கு மேற்பட்டதாக இருக்க வேண்டும்</string>\n    <string name=\"welcome\">வரவேற்கிறோம்</string>\n    <string name=\"tracker_warning\">சில சாதனங்கள் வெவ்வேறு கணினி நடத்தைகளைக் கொண்டுள்ளன, அவை பின்னணி பணிகளை உடைக்கக்கூடும்.</string>\n    <string name=\"read_more\">மேலும் வாசிக்க</string>\n    <string name=\"queued\">வரிசையில்</string>\n    <string name=\"exclude_nsfw_from_history\">என்.எச்.எஃப்.டபிள்யூ மங்காவை வரலாற்றிலிருந்து விலக்குங்கள்</string>\n    <string name=\"show_pages_numbers\">எண்ணுள்ள பக்கங்கள்</string>\n    <string name=\"appearance\">தோற்றம்</string>\n    <string name=\"suggestions_excluded_genres\">வகைகளை விலக்கு</string>\n    <string name=\"suggestions_excluded_genres_summary\">பரிந்துரைகளில் நீங்கள் பார்க்க விரும்பாத வகைகளைக் குறிப்பிடவும்</string>\n    <string name=\"back\">பின்</string>\n    <string name=\"check_new_chapters_title\">புதிய அத்தியாயங்களை சரிபார்த்து, அதைப் பற்றி அறிவிக்கவும்</string>\n    <string name=\"edit_category\">வகையைத் திருத்து</string>\n    <string name=\"dns_over_https\">Https க்கு மேல் dns</string>\n    <string name=\"status_planned\">திட்டமிடப்பட்டது</string>\n    <string name=\"report\">அறிக்கை</string>\n    <string name=\"data_deletion\">தரவு நீக்குதல்</string>\n    <string name=\"show_all\">அனைத்தையும் காட்டு</string>\n    <string name=\"select_range\">வரம்பைத் தேர்ந்தெடுக்கவும்</string>\n    <string name=\"manage\">நிர்வகிக்கவும்</string>\n    <string name=\"reorder\">மறுவரிசை</string>\n    <string name=\"empty\">காலி</string>\n    <string name=\"explore\">ஆராயுங்கள்</string>\n    <string name=\"exit_confirmation\">வெளியேறும் உறுதிப்படுத்தல்</string>\n    <string name=\"saved_manga\">மங்காவைக் காப்பாற்றினார்</string>\n    <string name=\"pages_cache\">பக்கங்கள் கேச்</string>\n    <string name=\"other_cache\">பிற கேச்</string>\n    <string name=\"storage_usage\">சேமிப்பக பயன்பாடு</string>\n    <string name=\"feed\">தீவனம்</string>\n    <string name=\"history_shortcuts_summary\">பயன்பாட்டு ஐகானில் நீண்ட நேரம் அழுத்துவதன் மூலம் அண்மைக் கால மங்காவை கிடைக்கச் செய்யுங்கள்</string>\n    <string name=\"periodic_backups\">அவ்வப்போது காப்புப்பிரதிகள்</string>\n    <string name=\"frequency_every_day\">தினமும்</string>\n    <string name=\"lock_screen_rotation\">பூட்டு திரை சுழற்சி</string>\n    <string name=\"sources_catalog\">ஆதாரங்கள் பட்டியல்</string>\n    <string name=\"source_enabled\">மூல இயக்கப்பட்டது</string>\n    <string name=\"state\">மாநிலம்</string>\n    <string name=\"error_search_not_supported\">இந்த மங்கா மூலத்தால் தேடலை ஆதரிக்கவில்லை</string>\n    <string name=\"genres_search_hint\">வகை பெயரைத் தட்டச்சு செய்யத் தொடங்குங்கள்</string>\n    <string name=\"backup_date_\">காப்பு தேதி: %s</string>\n    <string name=\"default_tab\">இயல்புநிலை தாவல்</string>\n    <string name=\"tracker_debug_info_summary\">புதிய அத்தியாயங்களுக்கான பின்னணி காசோலைகள் பற்றிய தகவல்களை பிழைத்திருத்தவும்</string>\n    <string name=\"_new\">புதிய</string>\n    <string name=\"image_server\">விருப்பமான பட சேவையகம்</string>\n    <string name=\"sort_order_asc\">ஏறுதல்</string>\n    <string name=\"sort_order_desc\">இறங்கு</string>\n    <string name=\"by_date\">திகதி</string>\n    <string name=\"popularity\">புகழ்</string>\n    <string name=\"scrobbler_auth_required\">தொடர %s இல் உள்நுழைக</string>\n    <string name=\"unstable_feature\">நிலையற்ற நற்பொருத்தம்</string>\n    <string name=\"popular_today\">இன்று பிரபலமானது</string>\n    <string name=\"popular_in_week\">இந்த வாரம் பிரபலமானது</string>\n    <string name=\"year\">ஆண்டு</string>\n    <string name=\"years\">ஆண்டுகள்</string>\n    <string name=\"any\">ஏதேனும்</string>\n    <string name=\"access_denied_403\">அணுகல் மறுக்கப்பட்டது (403)</string>\n    <string name=\"email\">மின்னஞ்சல்</string>\n    <string name=\"author\">நூலாசிரியர்</string>\n    <string name=\"backup_tg_check\">பநிஇ வேலை செய்கிறதா என்று சரிபார்க்கவும்</string>\n    <string name=\"all_sources_enabled\">அனைத்து ஆதாரங்களும் இயக்கப்பட்டுள்ளன</string>\n    <string name=\"web_view_unavailable\">வெப்வியூ கிடைக்கவில்லை: வெப்வியூ வழங்குநர் நிறுவப்பட்டிருக்கிறாரா என்று சரிபார்க்கவும்</string>\n    <string name=\"clear_network_cache\">பிணையம் கேச் அழிக்கவும்</string>\n    <string name=\"proxy\">பதிலாள்</string>\n    <string name=\"invalid_value_message\">தவறான மதிப்பு</string>\n    <string name=\"email_password_enter_hint\">தொடர உங்கள் மின்னஞ்சல் மற்றும் கடவுச்சொல்லை உள்ளிடவும்</string>\n    <string name=\"authorization_optional\">ஏற்பு (விரும்பினால்)</string>\n    <string name=\"text_history_holder_primary\">நீங்கள் படித்தவை இங்கே காண்பிக்கப்படும்</string>\n    <string name=\"text_history_holder_secondary\">«எக்ச்ப்ளோர்» பிரிவில் என்ன படிக்க வேண்டும் என்பதைக் கண்டறியவும்</string>\n    <string name=\"text_empty_holder_secondary_filtered\">நீங்கள் தேர்ந்தெடுத்த வடிப்பான்களுடன் பொருந்தக்கூடிய மங்கா எதுவும் இல்லை</string>\n    <string name=\"text_local_holder_secondary\">நிகழ்நிலை பட்டியலிலிருந்து ஏதாவது சேமிக்கவும் அல்லது ஒரு கோப்பிலிருந்து இறக்குமதி செய்யவும்.</string>\n    <string name=\"manga_shelf\">அலமாரி</string>\n    <string name=\"pages_animation\">பக்க அனிமேசன்</string>\n    <string name=\"manga_save_location\">கோப்புறை பதிவிறக்குகிறது</string>\n    <string name=\"not_available\">கிடைக்கவில்லை</string>\n    <string name=\"cannot_find_available_storage\">கிடைக்கக்கூடிய சேமிப்பு இல்லை</string>\n    <string name=\"done\">முடிந்தது</string>\n    <string name=\"all_favourites\">அனைத்து பிடித்தவைகளும்</string>\n    <string name=\"favourites_category_empty\">வெற்று வகை</string>\n    <string name=\"read_later\">பின்னர் படியுங்கள்</string>\n    <string name=\"updates\">புதுப்பிப்புகள்</string>\n    <string name=\"text_feed_holder\">நீங்கள் படிக்கிறவற்றின் புதிய அத்தியாயங்கள் இங்கே காட்டப்பட்டுள்ளன</string>\n    <string name=\"search_results\">தேடல் முடிவுகள்</string>\n    <string name=\"new_version_s\">புதிய பதிப்பு: %s</string>\n    <string name=\"size_s\">அளவு: %s</string>\n    <string name=\"clear_updates_feed\">புதுப்பிப்புகளை அழிக்கவும்</string>\n    <string name=\"updates_feed_cleared\">அழிக்கப்பட்டது</string>\n    <string name=\"rotate_screen\">திரை சுழற்றுங்கள்</string>\n    <string name=\"update\">புதுப்பிப்பு</string>\n    <string name=\"feed_will_update_soon\">தீவன புதுப்பிப்பு விரைவில் தொடங்கும்</string>\n    <string name=\"track_sources\">புதுப்பிப்புகளைப் பாருங்கள்</string>\n    <string name=\"dont_check\">சரிபார்க்க வேண்டாம்</string>\n    <string name=\"enter_password\">கடவுச்சொல்லை உள்ளிடவும்</string>\n    <string name=\"wrong_password\">தவறான கடவுச்சொல்</string>\n    <string name=\"protect_application\">பயன்பாட்டைப் பாதுகாக்கவும்</string>\n    <string name=\"protect_application_summary\">கோட்டாட்சுவைத் தொடங்கும்போது கடவுச்சொல்லைக் கேளுங்கள்</string>\n    <string name=\"repeat_password\">கடவுச்சொல்லை மீண்டும் செய்யவும்</string>\n    <string name=\"passwords_mismatch\">பொருந்தாத கடவுச்சொற்கள்</string>\n    <string name=\"about\">பற்றி</string>\n    <string name=\"app_version\">பதிப்பு %s</string>\n    <string name=\"check_for_updates\">புதுப்பிப்புகளை சரிபார்க்கவும்</string>\n    <string name=\"state_upcoming\">வரவிருக்கும்</string>\n    <string name=\"no_update_available\">புதுப்பிப்புகள் எதுவும் கிடைக்கவில்லை</string>\n    <string name=\"right_to_left\">வலதுபுறம் இடது</string>\n    <string name=\"create_category\">புதிய வகை</string>\n    <string name=\"scale_mode\">அளவிலான பயன்முறை</string>\n    <string name=\"zoom_mode_fit_center\">பொருத்தம் நடுவண்</string>\n    <string name=\"zoom_mode_fit_height\">உயரத்திற்கு ஏற்றது</string>\n    <string name=\"zoom_mode_fit_width\">அகலத்திற்கு ஏற்றது</string>\n    <string name=\"zoom_mode_keep_start\">தொடக்கத்தில் வைத்திருங்கள்</string>\n    <string name=\"black_dark_theme\">கருப்பு</string>\n    <string name=\"black_dark_theme_summary\">AMOLED திரைகளில் குறைந்த சக்தியைப் பயன்படுத்துகிறது</string>\n    <string name=\"backup_restore\">காப்புப்பிரதி மற்றும் மீட்டமை</string>\n    <string name=\"restore_backup\">காப்புப்பிரதியிலிருந்து மீட்டமைக்கவும்</string>\n    <string name=\"preparing_\">தயாரித்தல்…</string>\n    <string name=\"file_not_found\">கோப்பு கிடைக்கவில்லை</string>\n    <string name=\"data_restored_with_errors\">தரவு மீட்டெடுக்கப்பட்டது, ஆனால் பிழைகள் உள்ளன</string>\n    <string name=\"backup_information\">உங்கள் வரலாறு மற்றும் பிடித்தவைகளின் காப்புப்பிரதியை உருவாக்கி அதை மீட்டெடுக்கலாம்</string>\n    <string name=\"just_now\">இப்போது</string>\n    <string name=\"yesterday\">நேற்று</string>\n    <string name=\"long_ago\">நீண்ட காலத்திற்கு முன்பு</string>\n    <string name=\"reader_mode_hint\">தேர்ந்தெடுக்கப்பட்ட உள்ளமைவு இந்த மங்காவுக்கு நினைவில் இருக்கும்</string>\n    <string name=\"captcha_solve\">தீர்க்க</string>\n    <string name=\"clear_cookies\">குக்கீகளை அழிக்கவும்</string>\n    <string name=\"cookies_cleared\">அனைத்து குக்கீகள் அகற்றப்பட்டன</string>\n    <string name=\"clear_feed\">தெளிவான ஊட்டம்</string>\n    <string name=\"text_clear_updates_feed_prompt\">அனைத்து புதுப்பிப்பு வரலாற்றையும் நிரந்தரமாக அழிக்கவா?</string>\n    <string name=\"check_for_new_chapters\">புதிய அத்தியாயங்களை சரிபார்க்கவும்</string>\n    <string name=\"reverse\">தலைகீழ்</string>\n    <string name=\"chapters_grid_view\">கட்டம் பார்வை</string>\n    <string name=\"sign_in\">விடுபதிகை</string>\n    <string name=\"auth_required\">இந்த உள்ளடக்கத்தைக் காண உள்நுழைக</string>\n    <string name=\"default_s\">இயல்புநிலை: %s</string>\n    <string name=\"next\">அடுத்தது</string>\n    <string name=\"protect_application_subtitle\">பயன்பாட்டைத் தொடங்க கடவுச்சொல்லை உள்ளிடவும்</string>\n    <string name=\"confirm\">உறுதிப்படுத்தவும்</string>\n    <string name=\"text_clear_search_history_prompt\">அனைத்து அண்மைக் கால தேடல் வினவல்களையும் நிரந்தரமாக அகற்றவா?</string>\n    <string name=\"suggestions_info\">எல்லா தரவுகளும் இந்த சாதனத்தில் உள்நாட்டில் மட்டுமே பகுப்பாய்வு செய்யப்படுகின்றன, மேலும் எங்கும் அனுப்பப்படவில்லை.</string>\n    <string name=\"text_suggestion_holder\">மங்காவைப் படிக்கத் தொடங்குங்கள், நீங்கள் தனிப்பயனாக்கப்பட்ட பரிந்துரைகளைப் பெறுவீர்கள்</string>\n    <string name=\"exclude_nsfw_from_suggestions\">NSFW மங்காவை பரிந்துரைக்க வேண்டாம்</string>\n    <string name=\"enabled\">இயக்கப்பட்டது</string>\n    <string name=\"disabled\">முடக்கப்பட்டது</string>\n    <string name=\"reset_filter\">வடிகட்டியை மீட்டமை</string>\n    <string name=\"onboard_text\">நீங்கள் மங்காவைப் படிக்க விரும்பும் மொழிகளைத் தேர்ந்தெடுக்கவும். நீங்கள் அதை பின்னர் அமைப்புகளில் மாற்றலாம்.</string>\n    <string name=\"never\">ஒருபோதும்</string>\n    <string name=\"only_using_wifi\">வைஃபை மட்டுமே</string>\n    <string name=\"always\">எப்போதும்</string>\n    <string name=\"preload_pages\">பக்கங்களை முன்னரே ஏற்றவும்</string>\n    <string name=\"logged_in_as\">%s ஆக உள்நுழைந்துள்ளது</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">பல்வேறு மொழிகள்</string>\n    <string name=\"search_chapters\">அத்தியாயத்தைக் கண்டறியவும்</string>\n    <string name=\"chapters_empty\">இந்த மங்காவில் அத்தியாயங்கள் இல்லை</string>\n    <string name=\"suggestions_updating\">பரிந்துரைகள் புதுப்பித்தல்</string>\n    <string name=\"text_delete_local_manga_batch\">சாதனத்திலிருந்து தேர்ந்தெடுக்கப்பட்ட உருப்படிகளை நிரந்தரமாக நீக்கவா?</string>\n    <string name=\"removal_completed\">அகற்றுதல் முடிந்தது</string>\n    <string name=\"download_slowdown\">மந்தநிலை பதிவிறக்கவும்</string>\n    <string name=\"download_slowdown_summary\">உங்கள் ஐபி முகவரியைத் தடுப்பதைத் தவிர்க்க உதவுகிறது</string>\n    <string name=\"local_manga_processing\">மங்கா செயலாக்கத்தை சேமித்தது</string>\n    <string name=\"chapters_will_removed_background\">அத்தியாயங்கள் பின்னணியில் அகற்றப்படும்</string>\n    <string name=\"canceled\">ரத்து செய்யப்பட்டது</string>\n    <string name=\"account_already_exists\">கணக்கு ஏற்கனவே உள்ளது</string>\n    <string name=\"sync\">ஒத்தியக்கம்</string>\n    <string name=\"sync_title\">உங்கள் தரவை ஒத்திசைக்கவும்</string>\n    <string name=\"email_enter_hint\">தொடர உங்கள் மின்னஞ்சலை உள்ளிடவும்</string>\n    <string name=\"hide\">மறை</string>\n    <string name=\"new_sources_text\">புதிய மங்கா ஆதாரங்கள் கிடைக்கின்றன</string>\n    <string name=\"show_notification_new_chapters_on\">நீங்கள் படிக்கும் மங்காவின் புதுப்பிப்புகள் பற்றிய அறிவிப்புகளைப் பெறுவீர்கள்</string>\n    <string name=\"by_name_reverse\">பெயர் தலைகீழானது</string>\n    <string name=\"show_notification_new_chapters_off\">நீங்கள் அறிவிப்புகளைப் பெற மாட்டீர்கள், ஆனால் புதிய அத்தியாயங்கள் பட்டியல்களில் முன்னிலைப்படுத்தப்படும்</string>\n    <string name=\"notifications_enable\">அறிவிப்புகளை இயக்கவும்</string>\n    <string name=\"name\">பெயர்</string>\n    <string name=\"edit\">தொகு</string>\n    <string name=\"tracking\">கண்காணிப்பு</string>\n    <string name=\"empty_favourite_categories\">பிடித்த பிரிவுகள் இல்லை</string>\n    <string name=\"logout\">விடுபதிகை</string>\n    <string name=\"bookmark_add\">புக்மார்க்கைச் சேர்க்கவும்</string>\n    <string name=\"bookmark_remove\">புத்தகக்குறியை அகற்று</string>\n    <string name=\"bookmarks\">புக்மார்க்குகள்</string>\n    <string name=\"bookmark_removed\">புக்மார்க்கு நீக்கப்பட்டது</string>\n    <string name=\"bookmark_added\">புக்மார்க்கு சேர்க்கப்பட்டது</string>\n    <string name=\"undo\">செயல்தவிர்</string>\n    <string name=\"removed_from_history\">வரலாற்றிலிருந்து அகற்றப்பட்டது</string>\n    <string name=\"default_mode\">இயல்புநிலை பயன்முறை</string>\n    <string name=\"detect_reader_mode\">தன்னியக்க வாசகர் பயன்முறை</string>\n    <string name=\"detect_reader_mode_summary\">மங்கா வெப்டூன் என்றால் தானாகவே கண்டறியவும்</string>\n    <string name=\"disable_battery_optimization\">பேட்டரி தேர்வுமுறை முடக்கு</string>\n    <string name=\"disable_battery_optimization_summary\">பின்னணி புதுப்பிப்புகள் சோதனைகளுக்கு உதவுகிறது</string>\n    <string name=\"crash_text\">ஏதோ தவறு நடந்தது. அதை சரிசெய்ய எங்களுக்கு உதவ ஒரு பிழை அறிக்கையை டெவலப்பர்களிடம் சமர்ப்பிக்கவும்.</string>\n    <string name=\"send\">அனுப்பு</string>\n    <string name=\"status_reading\">படித்தல்</string>\n    <string name=\"status_re_reading\">மறு வாசிப்பு</string>\n    <string name=\"status_completed\">முடிந்தது</string>\n    <string name=\"status_on_hold\">நிறுத்தி</string>\n    <string name=\"status_dropped\">கைவிடப்பட்டது</string>\n    <string name=\"disable_all\">அனைத்தையும் முடக்கு</string>\n    <string name=\"use_fingerprint\">கிடைத்தால் பயோமெட்ரிக் பயன்படுத்தவும்</string>\n    <string name=\"appwidget_shelf_description\">உங்களுக்கு பிடித்தவர்களிடமிருந்து மங்கா</string>\n    <string name=\"appwidget_recent_description\">உங்கள் அண்மைக் காலத்தில் படித்த மங்கா</string>\n    <string name=\"show_reading_indicators\">வாசிப்பு முன்னேற்ற குறிகாட்டிகளைக் காட்டு</string>\n    <string name=\"show_reading_indicators_summary\">வரலாறு மற்றும் பிடித்தவைகளில் படித்த சதவீதத்தைக் காட்டு</string>\n    <string name=\"import_will_start_soon\">இறக்குமதி விரைவில் தொடங்கும்</string>\n    <string name=\"download_option_all_unread\">படிக்காத அனைத்து அத்தியாயங்களும்</string>\n    <string name=\"exclude_nsfw_from_history_summary\">என்.எச்.எஃப்.டபிள்யூ எனக் குறிக்கப்பட்ட மங்கா ஒருபோதும் வரலாற்றில் சேர்க்கப்படாது, உங்கள் முன்னேற்றம் காப்பாற்றப்படாது</string>\n    <string name=\"clear_cookies_summary\">சில சிக்கல்கள் ஏற்பட்டால் உதவ முடியும். அனைத்து அங்கீகாரங்களும் செல்லாதவை</string>\n    <string name=\"invalid_domain_message\">தவறான டொமைன்</string>\n    <string name=\"invalid_server_address_message\">தவறான சேவையக முகவரி</string>\n    <string name=\"clear_all_history\">எல்லா வரலாற்றையும் அழிக்கவும்</string>\n    <string name=\"last_2_hours\">கடைசி 2 மணி நேரம்</string>\n    <string name=\"history_cleared\">வரலாறு அழிக்கப்பட்டது</string>\n    <string name=\"no_bookmarks_yet\">இன்னும் புக்மார்க்குகள் இல்லை</string>\n    <string name=\"no_bookmarks_summary\">மங்காவைப் படிக்கும்போது நீங்கள் புத்தகக்குறியை உருவாக்கலாம்</string>\n    <string name=\"bookmarks_removed\">புக்மார்க்குகள் அகற்றப்பட்டன</string>\n    <string name=\"no_manga_sources\">மங்கா ஆதாரங்கள் இல்லை</string>\n    <string name=\"no_manga_sources_text\">மங்கா ஆதாரங்களை மங்கா ஆன்லைனில் படிக்க இயக்கவும்</string>\n    <string name=\"random\">சீரற்ற</string>\n    <string name=\"categories_delete_confirm\">தேர்ந்தெடுக்கப்பட்ட பிடித்த வகைகளை நீக்க விரும்புகிறீர்களா?\\n அதில் உள்ள அனைத்து மங்காவும் இழக்கப்படும், இதை செயல்தவிர்க்க முடியாது.</string>\n    <string name=\"confirm_exit\">வெளியேற மீண்டும் அழுத்தவும்</string>\n    <string name=\"exit_confirmation_summary\">பயன்பாட்டிலிருந்து வெளியேற இரண்டு முறை மீண்டும் அழுத்தவும்</string>\n    <string name=\"available\">கிடைக்கிறது</string>\n    <string name=\"removed_from_favourites\">பிடித்தவைகளிலிருந்து அகற்றப்பட்டது</string>\n    <string name=\"options\">விருப்பங்கள்</string>\n    <string name=\"not_found_404\">உள்ளடக்கம் கண்டுபிடிக்கப்படவில்லை அல்லது அகற்றப்படவில்லை</string>\n    <string name=\"incognito_mode\">மறைநிலை பயன்முறை</string>\n    <string name=\"no_chapters\">அத்தியாயங்கள் இல்லை</string>\n    <string name=\"automatic_scroll\">தானியங்கி சுருள்</string>\n    <string name=\"reader_info_pattern\">ச %1$d/%2$d பஜி.%3$d/%4$d</string>\n    <string name=\"reader_info_bar\">வாசகரில் செய்தி பட்டியைக் காட்டு</string>\n    <string name=\"comics_archive\">காமிக்ச் காப்பகம்</string>\n    <string name=\"folder_with_images\">படங்களுடன் கோப்புறை</string>\n    <string name=\"importing_manga\">மங்காவை இறக்குமதி செய்கிறது</string>\n    <string name=\"import_completed\">இறக்குமதி முடிந்தது</string>\n    <string name=\"import_completed_hint\">இடத்தை சேமிக்க நீங்கள் அசல் கோப்பை சேமிப்பகத்திலிருந்து நீக்கலாம்</string>\n    <string name=\"manga_error_description_pattern\">பிழை விவரங்கள்:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. &lt;a href=%2$s&gt;மங்காவை இணைய உலாவியில் திறக்க முயற்சிக்கவும்&lt;/a&gt;, அது அதன் மூலத்தில் கிடைப்பதை உறுதிசெய்யவும்&lt;br&gt;2. நீங்கள் &lt;a href=kotatsu://about&gt;Kotatsu இன் சமீபத்திய பதிப்பைப் பயன்படுத்துகிறீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்&lt;/a&gt;&lt;br&gt;3. அது கிடைத்தால், டெவலப்பர்களுக்கு பிழை அறிக்கையை அனுப்பவும்.</string>\n    <string name=\"history_shortcuts\">அண்மைக் கால மங்கா குறுக்குவழிகளைக் காட்டு</string>\n    <string name=\"reader_control_ltr_summary\">பக்க மாறுதல் திசையை வாசகர் பயன்முறையில் சரிசெய்ய வேண்டாம், இ. g. சரியான விசையை அழுத்துவது எப்போதும் அடுத்த பக்கத்திற்கு மாறுகிறது. இந்த விருப்பம் வன்பொருள் உள்ளீட்டு சாதனங்களை மட்டுமே பாதிக்கிறது</string>\n    <string name=\"reader_control_ltr\">பணிச்சூழலியல் வாசகர் கட்டுப்பாடு</string>\n    <string name=\"contrast\">மாறுபாடு</string>\n    <string name=\"text_unsaved_changes_prompt\">சேமிக்கப்படாத மாற்றங்களைச் சேமிக்கவா அல்லது நிராகரிக்கவா?</string>\n    <string name=\"discard\">நிராகரிக்கவும்</string>\n    <string name=\"error_no_space_left\">சாதனத்தில் எந்த இடமும் இல்லை</string>\n    <string name=\"reader_slider\">பக்க மாறுதல் ச்லைடரைக் காட்டு</string>\n    <string name=\"webtoon_zoom\">வலை தொனி சூம்</string>\n    <string name=\"network_unavailable_hint\">ஆன்லைனில் மங்காவைப் படிக்க வைஃபை அல்லது மொபைல் நெட்வொர்க்கை இயக்கவும்</string>\n    <string name=\"server_error\">சேவையக பக்க பிழை (%1$d). தயவுசெய்து பின்னர் மீண்டும் முயற்சிக்கவும்</string>\n    <string name=\"clear_new_chapters_counters\">புதிய அத்தியாயங்களைப் பற்றிய தெளிவான தகவல்களும்</string>\n    <string name=\"prefetch_content\">உள்ளடக்க முன் ஏற்றுதல்</string>\n    <string name=\"mark_as_current\">மின்னோட்டமாக குறிக்கவும்</string>\n    <string name=\"language\">மொழி</string>\n    <string name=\"share_logs\">பதிவுகளைப் பகிரவும்</string>\n    <string name=\"enable_logging\">பதிவை இயக்கவும்</string>\n    <string name=\"enable_logging_summary\">பிழைத்திருத்த நோக்கங்களுக்காக சில செயல்களைப் பதிவுசெய்க. நீங்கள் என்ன செய்கிறீர்கள் என்று உங்களுக்குத் தெரியாவிட்டால் அதை இயக்க வேண்டாம்</string>\n    <string name=\"color_theme\">வண்ணத் திட்டம்</string>\n    <string name=\"show_in_grid_view\">கட்டம் பார்வையில் காண்பி</string>\n    <string name=\"theme_name_mion\">விவரம்</string>\n    <string name=\"theme_name_rikka\">பணக்காரர்</string>\n    <string name=\"nothing_here\">இங்கே எதுவும் இல்லை</string>\n    <string name=\"download_option_next_unread_n_chapters\">அடுத்து படிக்காத %s</string>\n    <string name=\"content_type_other\">மற்றொன்று</string>\n    <string name=\"scrobbling_empty_hint\">வாசிப்பு முன்னேற்றத்தைக் கண்காணிக்க, மங்கா விவரங்கள் திரையில் பட்டியல் → தடத்தைத் தேர்ந்தெடுக்கவும்.</string>\n    <string name=\"allow_unstable_updates\">நிலையற்ற புதுப்பிப்புகளை அனுமதிக்கவும்</string>\n    <string name=\"allow_unstable_updates_summary\">நிலையற்ற கட்டடங்களைப் பற்றிய அறிவிப்புகளைப் பெறுங்கள்</string>\n    <string name=\"download_started\">பதிவிறக்கம் தொடங்கியது</string>\n    <string name=\"got_it\">கிடைத்தது</string>\n    <string name=\"sources_reorder_tip\">அவற்றை மறுவரிசைப்படுத்த ஒரு பொருளைத் தட்டவும் பிடிக்கவும்</string>\n    <string name=\"comics_archive_import_description\">நீங்கள் ஒன்று அல்லது அதற்கு மேற்பட்ட .CBZ அல்லது .ZIP கோப்புகளைத் தேர்ந்தெடுக்கலாம், ஒவ்வொரு கோப்பும் ஒரு தனி மங்காவாக அங்கீகரிக்கப்படும்.</string>\n    <string name=\"folder_with_images_import_description\">காப்பகங்கள் அல்லது படங்களுடன் ஒரு கோப்பகத்தை நீங்கள் தேர்ந்தெடுக்கலாம். ஒவ்வொரு காப்பகமும் (அல்லது துணை அடைவு) ஒரு அத்தியாயமாக அங்கீகரிக்கப்படும்.</string>\n    <string name=\"speed\">வேகம்</string>\n    <string name=\"sync_settings\">ஒத்திசைவு அமைப்புகள்</string>\n    <string name=\"server_address\">சேவையக முகவரி</string>\n    <string name=\"sync_host_description\">நீங்கள் சுய-ஓச்ட் ஒத்திசைவு சேவையகம் அல்லது இயல்புநிலை ஒன்றைப் பயன்படுத்தலாம். நீங்கள் என்ன செய்கிறீர்கள் என்று உங்களுக்குத் தெரியாவிட்டால் இதை மாற்ற வேண்டாம்.</string>\n    <string name=\"ignore_ssl_errors\">SSL பிழைகளை புறக்கணிக்கவும்</string>\n    <string name=\"mirror_switching\">தானாகவே கண்ணாடியைத் தேர்வுசெய்க</string>\n    <string name=\"mirror_switching_summary\">கண்ணாடிகள் கிடைத்தால் பிழைகள் குறித்த மங்கா ஆதாரங்களுக்கான களங்களை தானாக மாற்றவும்</string>\n    <string name=\"pause\">இடைநிறுத்தம்</string>\n    <string name=\"resume\">மீண்டும் தொடங்குங்கள்</string>\n    <string name=\"paused\">இடைநிறுத்தப்பட்டது</string>\n    <string name=\"remove_completed\">முடிக்கப்பட்டதை அகற்று</string>\n    <string name=\"cancel_all\">அனைத்தையும் ரத்துசெய்</string>\n    <string name=\"downloads_wifi_only\">வைஃபை வழியாக மட்டுமே பதிவிறக்கவும்</string>\n    <string name=\"downloads_wifi_only_summary\">மொபைல் நெட்வொர்க்கிற்கு மாறும்போது பதிவிறக்குவதை நிறுத்துங்கள்</string>\n    <string name=\"suggestion_manga\">பரிந்துரை: %s</string>\n    <string name=\"suggestions_notifications_summary\">சில நேரங்களில் பரிந்துரைக்கப்பட்ட மங்காவுடன் அறிவிப்புகளைக் காட்டுங்கள்</string>\n    <string name=\"more\">மேலும்</string>\n    <string name=\"enable\">இயக்கு</string>\n    <string name=\"no_thanks\">நன்றி இல்லை</string>\n    <string name=\"cancel_all_downloads_confirm\">அனைத்து செயலில் உள்ள பதிவிறக்கங்களும் ரத்து செய்யப்படும், ஓரளவு பதிவிறக்கம் செய்யப்பட்ட தரவு இழக்கப்படும்</string>\n    <string name=\"remove_completed_downloads_confirm\">உங்கள் பதிவிறக்கங்களின் வரலாறு நிரந்தரமாக நீக்கப்படும். பதிவிறக்கம் செய்யப்பட்ட கோப்புகள் எதுவும் பாதிக்கப்படாது</string>\n    <string name=\"text_downloads_list_holder\">உங்களிடம் எந்த பதிவிறக்கங்களும் இல்லை</string>\n    <string name=\"downloads_resumed\">பதிவிறக்கங்கள் மீண்டும் தொடங்கப்பட்டுள்ளன</string>\n    <string name=\"downloads_paused\">பதிவிறக்கங்கள் இடைநிறுத்தப்பட்டுள்ளன</string>\n    <string name=\"downloads_removed\">பதிவிறக்கங்கள் அகற்றப்பட்டுள்ளன</string>\n    <string name=\"downloads_cancelled\">பதிவிறக்கங்கள் ரத்து செய்யப்பட்டுள்ளன</string>\n    <string name=\"suggestions_enable_prompt\">தனிப்பயனாக்கப்பட்ட மங்கா பரிந்துரைகளைப் பெற விரும்புகிறீர்களா?</string>\n    <string name=\"type\">வகை</string>\n    <string name=\"address\">முகவரி</string>\n    <string name=\"port\">துறைமுகம்</string>\n    <string name=\"downloaded\">பதிவிறக்கம்</string>\n    <string name=\"images_proxy_title\">படங்கள் உகப்பாக்கம் பதிலாள்</string>\n    <string name=\"images_procy_description\">போக்குவரத்து பயன்பாட்டைக் குறைக்க மற்றும் முடிந்தால் பட ஏற்றுதலை விரைவுபடுத்த WSRV.NL சேவையைப் பயன்படுத்தவும்</string>\n    <string name=\"invert_colors\">வண்ணங்களை தலைகீழ்</string>\n    <string name=\"username\">பயனர்பெயர்</string>\n    <string name=\"password\">கடவுச்சொல்</string>\n    <string name=\"invalid_port_number\">தவறான துறைமுகம் எண்</string>\n    <string name=\"network\">பிணையம்</string>\n    <string name=\"data_and_privacy\">தரவு மற்றும் தனியுரிமை</string>\n    <string name=\"restore_summary\">முன்பு உருவாக்கப்பட்ட காப்புப்பிரதியை மீட்டமை</string>\n    <string name=\"webtoon_zoom_summary\">வெப்டூன் பயன்முறையில் சைகையில் பெரிதாக்க அனுமதிக்கவும்</string>\n    <string name=\"reader_info_bar_summary\">திரையின் மேற்புறத்தில் தற்போதைய நேரத்தையும் வாசிப்பு முன்னேற்றத்தையும் காட்டுங்கள்</string>\n    <string name=\"show_pages_numbers_summary\">பக்க எண்களை கீழ் மூலையில் காண்பி</string>\n    <string name=\"clear_source_cookies_summary\">குறிப்பிட்ட டொமைனுக்கு மட்டுமே குக்கீகளை அழிக்கவும். பெரும்பாலான சந்தர்ப்பங்களில் அங்கீகாரத்தை செல்லாது</string>\n    <string name=\"download_option_all_chapters\">மொழிபெயர்ப்பு %s உடன் அனைத்து அத்தியாயங்களும்</string>\n    <string name=\"download_option_whole_manga\">முழு மங்கா</string>\n    <string name=\"download_option_first_n_chapters\">முதல் %s</string>\n    <string name=\"restore\">மீட்டெடுக்கவும்</string>\n    <string name=\"download_option_all_unread_b\">படிக்காத அனைத்து அத்தியாயங்களும் (%s)</string>\n    <string name=\"download_option_manual_selection\">அத்தியாயங்களை கைமுறையாகத் தேர்ந்தெடுக்கவும்</string>\n    <string name=\"pick_custom_directory\">தனிப்பயன் கோப்பகத்தைத் தேர்ந்தெடுங்கள்</string>\n    <string name=\"no_access_to_file\">இந்த கோப்பு அல்லது கோப்பகத்திற்கு உங்களுக்கு அணுகல் இல்லை</string>\n    <string name=\"local_manga_directories\">உள்ளக மங்கா கோப்பகங்கள்</string>\n    <string name=\"description\">விவரம்</string>\n    <string name=\"this_month\">இந்த மாதம்</string>\n    <string name=\"voice_search\">குரல் தேடல்</string>\n    <string name=\"related_manga\">தொடர்புடைய மங்கா</string>\n    <string name=\"color_light\">ஒளி</string>\n    <string name=\"color_dark\">இருண்ட</string>\n    <string name=\"color_white\">வெள்ளை</string>\n    <string name=\"color_black\">கருப்பு</string>\n    <string name=\"background\">பின்னணி</string>\n    <string name=\"data_not_restored\">தரவு மீட்டெடுக்கப்படவில்லை</string>\n    <string name=\"data_not_restored_text\">சரியான காப்புப்பிரதி கோப்பை நீங்கள் தேர்ந்தெடுத்துள்ளீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்</string>\n    <string name=\"manage_categories\">வகைகளை நிர்வகிக்கவும்</string>\n    <string name=\"suggestions_wifi_only_summary\">மீட்டர் பிணையம் இணைப்புகளைப் பயன்படுத்தி பரிந்துரைகளைப் புதுப்பிக்க வேண்டாம்</string>\n    <string name=\"tracker_wifi_only_summary\">மீட்டர் பிணையம் இணைப்புகளைப் பயன்படுத்தி புதிய அத்தியாயங்களை சரிபார்க்க வேண்டாம்</string>\n    <string name=\"search_hint\">மங்கா தலைப்பு, வகை அல்லது மூல பெயரை உள்ளிடவும்</string>\n    <string name=\"progress\">முன்னேற்றம்</string>\n    <string name=\"order_added\">சேர்க்கப்பட்டது</string>\n    <string name=\"show\">காட்டு</string>\n    <string name=\"captcha_required_summary\">%s சரியாக வேலை செய்ய ஒரு கேப்ட்சா தீர்க்கப்பட வேண்டும்</string>\n    <string name=\"languages\">மொழிகள்</string>\n    <string name=\"unknown\">தெரியவில்லை</string>\n    <string name=\"in_progress\">முன்னேற்றத்தில் உள்ளது</string>\n    <string name=\"disable_nsfw\">NSFW ஐ முடக்கு</string>\n    <string name=\"too_many_requests_message\">பல கோரிக்கைகள். பின்னர் மீண்டும் முயற்சிக்கவும்</string>\n    <string name=\"too_many_requests_message_retry\">பல கோரிக்கைகள். %s க்குப் பிறகு மீண்டும் முயற்சிக்கவும்</string>\n    <string name=\"related_manga_summary\">தொடர்புடைய மங்காவின் பட்டியலைக் காட்டு. சில சந்தர்ப்பங்களில் இது துல்லியமாக அல்லது காணாமல் போகலாம்</string>\n    <string name=\"advanced\">மேம்பட்ட</string>\n    <string name=\"manga_list\">மங்கா பட்டியல்</string>\n    <string name=\"error_corrupted_file\">தவறான தரவு திரும்பப் பெறப்படுகிறது அல்லது கோப்பு சிதைந்துள்ளது</string>\n    <string name=\"on_device\">சாதனத்தில்</string>\n    <string name=\"directories\">கோப்பகங்கள்</string>\n    <string name=\"main_screen_sections\">முதன்மையான திரை பிரிவுகள்</string>\n    <string name=\"items_limit_exceeded\">மேலும் உருப்படிகளைச் சேர்க்க முடியாது</string>\n    <string name=\"to_top\">மேலே</string>\n    <string name=\"moved_to_top\">மேலே நகர்த்தப்பட்டது</string>\n    <string name=\"zoom_out\">சிறிதாக்கு</string>\n    <string name=\"zoom_in\">பெரிதாக்கு</string>\n    <string name=\"reader_zoom_buttons\">சூம் பொத்தான்களைக் காட்டு</string>\n    <string name=\"reader_zoom_buttons_summary\">கீழ் வலது மூலையில் ஜூம் கட்டுப்பாட்டு பொத்தான்களைக் காட்ட வேண்டுமா</string>\n    <string name=\"keep_screen_on\">திரையை தொடர்ந்து வைத்திருங்கள்</string>\n    <string name=\"keep_screen_on_summary\">நீங்கள் மங்காவைப் படிக்கும்போது திரையை அணைக்க வேண்டாம்</string>\n    <string name=\"state_abandoned\">கைவிடப்பட்டது</string>\n    <string name=\"enhanced_colors_summary\">பேண்டலிங்கைக் குறைக்கிறது, ஆனால் செயல்திறனை பாதிக்கலாம்</string>\n    <string name=\"enhanced_colors\">32-பிட் வண்ண பயன்முறை</string>\n    <string name=\"suggest_new_sources\">பயன்பாட்டு புதுப்பிப்புக்குப் பிறகு புதிய ஆதாரங்களை பரிந்துரைக்கவும்</string>\n    <string name=\"suggest_new_sources_summary\">பயன்பாட்டைப் புதுப்பித்த பிறகு புதிதாக சேர்க்கப்பட்ட ஆதாரங்களை இயக்குமாறு தூண்டுதல்</string>\n    <string name=\"list_options\">பட்டியல் விருப்பங்கள்</string>\n    <string name=\"by_relevance\">பொருத்தமானது</string>\n    <string name=\"categories\">வகைகள்</string>\n    <string name=\"online_variant\">நிகழ்நிலை மாறுபாடு</string>\n    <string name=\"backup_frequency\">காப்பு உருவாக்கும் அதிர்வெண்</string>\n    <string name=\"frequency_every_2_days\">ஒவ்வொரு 2 நாட்களுக்கும்</string>\n    <string name=\"frequency_once_per_week\">வாரத்திற்கு ஒரு முறை</string>\n    <string name=\"frequency_twice_per_month\">மாதத்திற்கு இரண்டு முறை</string>\n    <string name=\"frequency_once_per_month\">மாதத்திற்கு ஒரு முறை</string>\n    <string name=\"periodic_backups_enable\">அவ்வப்போது காப்புப்பிரதிகளை இயக்கவும்</string>\n    <string name=\"backups_output_directory\">காப்புப்பிரதிகள் வெளியீட்டு அடைவு</string>\n    <string name=\"last_successful_backup\">கடைசி வெற்றிகரமான காப்புப்பிரதி: %s</string>\n    <string name=\"speed_value\">எக்ச்%.1f எஃப்</string>\n    <string name=\"content_type_manga\">மங்கா</string>\n    <string name=\"content_type_hentai\">என்டாய்</string>\n    <string name=\"content_type_comics\">காமிக்ச்</string>\n    <string name=\"no_manga_sources_catalog_text\">இந்த பிரிவில் எந்த ஆதாரங்களும் கிடைக்கவில்லை, அல்லது இவை அனைத்தும் ஏற்கனவே சேர்க்கப்பட்டிருக்கலாம்.\\n காத்திருங்கள்</string>\n    <string name=\"no_manga_sources_found\">உங்கள் வினவலால் கிடைக்கக்கூடிய மங்கா ஆதாரங்கள் எதுவும் இல்லை</string>\n    <string name=\"catalog\">பட்டியல்</string>\n    <string name=\"manage_sources\">ஆதாரங்களை நிர்வகிக்கவும்</string>\n    <string name=\"manual\">கையேடு</string>\n    <string name=\"available_d\">கிடைக்கிறது: %1$d</string>\n    <string name=\"disable_nsfw_summary\">NSFW ஆதாரங்களை முடக்கி, முடிந்தால் வயதுவந்த மங்காவை பட்டியலிலிருந்து மறைக்கவும்</string>\n    <string name=\"state_paused\">இடைநிறுத்தப்பட்டது</string>\n    <string name=\"reader_optimize\">நினைவக நுகர்வு குறைக்க (பீட்டா)</string>\n    <string name=\"reader_optimize_summary\">குறைந்த நினைவகத்தைப் பயன்படுத்த ஆஃப்ச்கிரீன் பக்கங்களின் தரத்தை குறைக்கவும்</string>\n    <string name=\"error_multiple_genres_not_supported\">பல வகைகளால் வடிகட்டுவது இந்த மங்கா மூலத்தால் ஆதரிக்கப்படவில்லை</string>\n    <string name=\"error_multiple_states_not_supported\">பல மாநிலங்களால் வடிகட்டுவது இந்த மங்கா மூலத்தால் ஆதரிக்கப்படவில்லை</string>\n    <string name=\"downloads_settings_info\">சேவையக பக்க தடுப்பதில் உங்களுக்கு சிக்கல்கள் இருந்தால், ஒவ்வொரு மங்கா மூலத்திற்கும் பதிவிறக்கம் மந்தநிலையை மூல அமைப்புகளில் தனித்தனியாக இயக்கலாம்</string>\n    <string name=\"skip\">தவிர்</string>\n    <string name=\"grayscale\">கிரேச்கேல்</string>\n    <string name=\"globally\">உலகளவில்</string>\n    <string name=\"this_manga\">இந்த மங்கா</string>\n    <string name=\"color_correction_apply_text\">இந்த அமைப்புகளை உலகளவில் அல்லது தற்போதைய மங்காவுக்கு மட்டுமே பயன்படுத்தலாம். உலகளவில் பயன்படுத்தப்பட்டால், தனிப்பட்ட அமைப்புகள் மீறப்படாது.</string>\n    <string name=\"apply\">இடு</string>\n    <string name=\"error_filter_locale_genre_not_supported\">வகைகள் மற்றும் இருப்பிடங்கள் இரண்டாலும் வடிகட்டுவது இந்த மூலத்தால் ஆதரிக்கப்படவில்லை</string>\n    <string name=\"error_filter_states_genre_not_supported\">வகைகள் மற்றும் மாநிலங்கள் இரண்டாலும் வடிகட்டுவது இந்த மூலத்தால் ஆதரிக்கப்படவில்லை</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">உங்களுக்கு ஏதேனும் சிக்கல்கள் இருந்தால் பதிவிறக்கத்தைத் தொடங்க உதவலாம்</string>\n    <string name=\"welcome_text\">நீங்கள் இயக்க விரும்பும் உள்ளடக்க ஆதாரங்களைத் தேர்ந்தெடுக்கவும். இதை பின்னர் அமைப்புகளிலும் கட்டமைக்க முடியும்</string>\n    <string name=\"sync_auth\">கணக்கை ஒத்திசைக்க உள்நுழைக</string>\n    <string name=\"category_hidden_done\">இந்த வகை முதன்மையான திரையில் இருந்து மறைக்கப்பட்டது மற்றும் பட்டியல் → வகைகளை நிர்வகிக்க முடியும்</string>\n    <string name=\"volume_\">தொகுதி %d</string>\n    <string name=\"volume_unknown\">அறியப்படாத தொகுதி</string>\n    <string name=\"incognito_mode_hint\">உங்கள் வாசிப்பு முன்னேற்றம் காப்பாற்றப்படாது</string>\n    <string name=\"vertical\">செங்குத்து</string>\n    <string name=\"last_read\">கடைசியாக படித்தார்</string>\n    <string name=\"show_menu\">மெனுவைக் காட்டு</string>\n    <string name=\"toggle_ui\">இடைமுகம் ஐக் காட்டு/மறைக்க</string>\n    <string name=\"prev_chapter\">முந்தைய அத்தியாயம்</string>\n    <string name=\"next_chapter\">அடுத்த அத்தியாயம்</string>\n    <string name=\"prev_page\">முந்தைய பக்கம்</string>\n    <string name=\"next_page\">அடுத்த பக்கம்</string>\n    <string name=\"reader_actions\">வாசகர் செயல்கள்</string>\n    <string name=\"reader_actions_summary\">தட்டக்கூடிய திரை பகுதிகளுக்கான செயல்களை உள்ளமைக்கவும்</string>\n    <string name=\"switch_pages_volume_buttons\">தொகுதி பொத்தான்களை இயக்கவும்</string>\n    <string name=\"switch_pages_volume_buttons_summary\">பக்கங்களை மாற்றுவதற்கு தொகுதி பொத்தான்களைப் பயன்படுத்தவும்</string>\n    <string name=\"tap_action\">செயலைத் தட்டவும்</string>\n    <string name=\"long_tap_action\">நீண்ட குழாய் நடவடிக்கை</string>\n    <string name=\"none\">எதுவுமில்லை</string>\n    <string name=\"config_reset_confirm\">இயல்புநிலை மதிப்புகளுக்கு அமைப்புகளை மீட்டமைக்கவா? இந்த செயலை செயல்தவிர்க்க முடியாது.</string>\n    <string name=\"use_two_pages_landscape\">இயற்கை நோக்குநிலையில் (பீட்டா) இரண்டு பக்கங்கள் தளவமைப்பைப் பயன்படுத்தவும்</string>\n    <string name=\"default_webtoon_zoom_out\">இயல்புநிலை வெப்டூன் பெரிதாக்கவும்</string>\n    <string name=\"fullscreen_mode\">முழுத்திரை பயன்முறை</string>\n    <string name=\"reader_fullscreen_summary\">கணினி நிலை மற்றும் வழிசெலுத்தல் பார்களை மறைக்கவும்</string>\n    <string name=\"reading_time_estimation\">மதிப்பிடப்பட்ட வாசிப்பு நேரத்தைக் காட்டு</string>\n    <string name=\"reading_time_estimation_summary\">நேர மதிப்பீட்டு மதிப்பு துல்லியமாக இருக்கலாம்</string>\n    <string name=\"suggestions_unavailable_text\">பரிந்துரைகள் நற்பொருத்தம் முடக்கப்பட்டுள்ளது</string>\n    <string name=\"check_for_new_chapters_disabled\">புதிய அத்தியாயங்களைச் சரிபார்ப்பது முடக்கப்பட்டுள்ளது</string>\n    <string name=\"show_labels_in_navbar\">வழிசெலுத்தல் பட்டியில் லேபிள்களைக் காட்டு</string>\n    <string name=\"pages_saving\">பக்கங்களைச் சேமித்தல்</string>\n    <string name=\"ask_for_dest_dir_every_time\">ஒவ்வொரு முறையும் இலக்கு டி.ஐ.ஆரைக் கேளுங்கள்</string>\n    <string name=\"default_page_save_dir\">இயல்புநிலை பக்கம் கோப்பகத்தை சேமிக்கவும்</string>\n    <string name=\"remove_from_history\">வரலாற்றிலிருந்து அகற்று</string>\n    <string name=\"location\">இடம்</string>\n    <string name=\"preferred_download_format\">விருப்பமான பதிவிறக்க வடிவம்</string>\n    <string name=\"automatic\">தானியங்கி</string>\n    <string name=\"single_cbz_file\">ஒற்றை சிபிஇசட் கோப்பு</string>\n    <string name=\"multiple_cbz_files\">பல சிபிஇசட் கோப்புகள்</string>\n    <string name=\"reading_stats\">புள்ளிவிவரங்களைப் படித்தல்</string>\n    <string name=\"other_manga\">மற்ற மங்கா</string>\n    <string name=\"less_than_minute\">ஒரு நிமிடத்திற்கும் குறைவாக</string>\n    <string name=\"statistics\">புள்ளிவிவரங்கள்</string>\n    <string name=\"clear_stats\">தெளிவான புள்ளிவிவரங்கள்</string>\n    <string name=\"stats_cleared\">புள்ளிவிவரங்கள் அழிக்கப்பட்டன</string>\n    <string name=\"clear_stats_confirm\">அனைத்து வாசிப்பு புள்ளிவிவரங்களையும் அழிக்க விரும்புகிறீர்களா? இந்த செயலை செயல்தவிர்க்க முடியாது.</string>\n    <string name=\"week\">வாரம்</string>\n    <string name=\"month\">மாதம்</string>\n    <string name=\"all_time\">எல்லா நேரமும்</string>\n    <string name=\"day\">நாள்</string>\n    <string name=\"three_months\">மூன்று மாதங்கள்</string>\n    <string name=\"empty_stats_text\">தேர்ந்தெடுக்கப்பட்ட காலத்திற்கு புள்ளிவிவரங்கள் எதுவும் இல்லை</string>\n    <string name=\"pages_read_s\">பக்கங்கள் படிக்க: %s</string>\n    <string name=\"alternatives\">மாற்று</string>\n    <string name=\"migrate\">இடம்பெயர்வு</string>\n    <string name=\"migrate_confirmation\">\\\"%2$s\\\" இலிருந்து மங்கா \\\"%1$s\\\" உங்கள் வரலாறு மற்றும் பிடித்தவைகளில் (இருந்தால்) \\\"%4$s\\\" இலிருந்து \\\"%3$s\\\" உடன் மாற்றப்படும்</string>\n    <string name=\"manga_migration\">மங்கா இடம்பெயர்வு</string>\n    <string name=\"migration_completed\">இடம்பெயர்வு முடிந்தது</string>\n    <string name=\"delete_read_chapters\">வாசிப்பு அத்தியாயங்களை நீக்கு</string>\n    <string name=\"no_chapters_deleted\">அத்தியாயங்கள் எதுவும் நீக்கப்படவில்லை</string>\n    <string name=\"chapters_deleted_pattern\">%1$s ஐ அகற்றி, %2$s ஐ அழித்தது</string>\n    <string name=\"delete_read_chapters_summary\">உள்ளக சேமிப்பகத்திலிருந்து நீங்கள் ஏற்கனவே படித்த அத்தியாயங்களை நீக்கவும்</string>\n    <string name=\"delete_read_chapters_prompt\">இது உங்கள் உள்ளக சேமிப்பகத்திலிருந்து வாசிக்கப்பட்டதாக குறிக்கப்பட்ட அனைத்து அத்தியாயங்களையும் நிரந்தரமாக நீக்கும். நீங்கள் அதை பின்னர் மீண்டும் ஏற்றலாம், ஆனால் இறக்குமதி செய்யப்பட்ட அத்தியாயங்கள் என்றென்றும் இழக்கப்படலாம்</string>\n    <string name=\"delete_read_chapters_auto\">வாசிப்பு அத்தியாயங்களை தானாக நீக்குங்கள்</string>\n    <string name=\"runs_on_app_start\">பயன்பாடு தொடங்கும் போது இயங்கும்</string>\n    <string name=\"split_by_translations\">மொழிபெயர்ப்புகளால் பிரிக்கப்படுகிறது</string>\n    <string name=\"split_by_translations_summary\">ஒரு பட்டியலில் இல்லாமல், வெவ்வேறு மொழிபெயர்ப்புகளுடன் தனித்தனியாக அத்தியாயங்களைக் காட்டு</string>\n    <string name=\"order_oldest\">பழமையானது</string>\n    <string name=\"long_ago_read\">நீண்ட காலத்திற்கு முன்பு படியுங்கள்</string>\n    <string name=\"unread\">படிக்காதது</string>\n    <string name=\"enable_source\">மூலத்தை இயக்கு</string>\n    <string name=\"unsupported_source\">இந்த மங்கா மூலத்தை ஆதரிக்கவில்லை</string>\n    <string name=\"show_pages_thumbs\">பக்கங்கள் சிறு உருவங்களைக் காட்டு</string>\n    <string name=\"show_pages_thumbs_summary\">விவரங்கள் திரையில் \\\"பக்கங்கள்\\\" தாவலை இயக்கவும்</string>\n    <string name=\"error_no_data_received\">சேவையகத்திலிருந்து தரவு எதுவும் பெறப்படவில்லை</string>\n    <string name=\"unsupported_backup_message\">சரியான கோட்டாட்சு காப்பு கோப்பைத் தேர்ந்தெடுக்கவும்</string>\n    <string name=\"hours_short\">%d h</string>\n    <string name=\"minutes_short\">%d மீ</string>\n    <string name=\"seconds_short\">%d கள்</string>\n    <string name=\"hours_minutes_short\">%1$d h %2$d மீ</string>\n    <string name=\"minutes_seconds_short\">%1$d m %2$d கள்</string>\n    <string name=\"fix\">சரிசெய்யவும்</string>\n    <string name=\"missing_storage_permission\">வெளிப்புற சேமிப்பகத்தில் மங்காவை அணுக இசைவு இல்லை</string>\n    <string name=\"last_used\">கடைசியாக பயன்படுத்தப்பட்டது</string>\n    <string name=\"show_updated\">புதுப்பிக்கப்பட்டதைக் காட்டு</string>\n    <string name=\"webtoon_gaps\">வெப்டூன் பயன்முறையில் இடைவெளிகள்</string>\n    <string name=\"webtoon_gaps_summary\">வெப்டூன் பயன்முறையில் பக்கங்களுக்கு இடையில் செங்குத்து இடைவெளிகளைக் காட்டு</string>\n    <string name=\"less_frequently\">குறைவாக அடிக்கடி</string>\n    <string name=\"more_frequently\">மேலும் அடிக்கடி</string>\n    <string name=\"frequency_of_check\">காசோலையின் அதிர்வெண்</string>\n    <string name=\"pin_navigation_ui\">முள் வழிசெலுத்தல் இடைமுகம்</string>\n    <string name=\"pin_navigation_ui_summary\">வழிசெலுத்தல் பட்டியை மறைக்க வேண்டாம் மற்றும் சுருளில் தேடல்</string>\n    <string name=\"search_suggestions\">பரிந்துரைகளைத் தேடுங்கள்</string>\n    <string name=\"recent_queries\">அண்மைக் கால வினவல்கள்</string>\n    <string name=\"suggested_queries\">பரிந்துரைக்கப்பட்ட வினவல்கள்</string>\n    <string name=\"blocked_by_server_message\">நீங்கள் சேவையகத்தால் தடுக்கப்பட்டுள்ளீர்கள். வேறு பிணைய இணைப்பைப் பயன்படுத்த முயற்சிக்கவும் (VPN, பதிலாள், முதலியன)</string>\n    <string name=\"disable\">முடக்கு</string>\n    <string name=\"sources_disabled\">ஆதாரங்கள் முடக்கப்பட்டன</string>\n    <string name=\"disable_connectivity_check\">இணைப்பு காசோலையை முடக்கு</string>\n    <string name=\"ignore_ssl_errors_summary\">பிணையம் வளங்களை அணுகும்போது SSL தொடர்பான சிக்கல்களை எதிர்கொண்டால் SSL சான்றிதழ்கள் சரிபார்ப்பை முடக்கலாம். இது உங்கள் பாதுகாப்பை பாதிக்கலாம். இந்த அமைப்பை மாற்றிய பின் பயன்பாட்டு மறுதொடக்கம் தேவை.</string>\n    <string name=\"disable_connectivity_check_summary\">உங்களிடம் சிக்கல்கள் இருந்தால் இணைப்பு காசோலையைத் தவிர்க்கவும் (எ.கா. பிணையம் இணைக்கப்பட்டிருந்தாலும் இணைப்பில்லாத பயன்முறையில் செல்வது)</string>\n    <string name=\"disable_nsfw_notifications\">NSFW அறிவிப்புகளை முடக்கு</string>\n    <string name=\"disable_nsfw_notifications_summary\">NSFW MANGA புதுப்பிப்புகள் பற்றிய அறிவிப்புகளைக் காட்ட வேண்டாம்</string>\n    <string name=\"tracker_debug_info\">புதிய அத்தியாயங்கள் பதிவை சரிபார்க்கிறது</string>\n    <string name=\"all_languages\">அனைத்து மொழிகளும்</string>\n    <string name=\"screenshots_block_incognito\">மறைநிலை பயன்முறையில் இருக்கும்போது தடுக்கவும்</string>\n    <string name=\"crop_pages\">பயிர் பக்கங்கள்</string>\n    <string name=\"pin\">முள்</string>\n    <string name=\"unpin\">அன்பின்</string>\n    <string name=\"source_pinned\">மூல பின்</string>\n    <string name=\"source_unpinned\">சான்று இணைக்கப்படாதது</string>\n    <string name=\"sources_unpinned\">ஆதாரங்கள் இணைக்கப்படாதவை</string>\n    <string name=\"sources_pinned\">ஆதாரங்கள் பொருத்தப்பட்டுள்ளன</string>\n    <string name=\"recent_sources\">அண்மைக் கால ஆதாரங்கள்</string>\n    <string name=\"percent_read\">விழுக்காடு படித்தது</string>\n    <string name=\"percent_left\">விழுக்காடு இடது</string>\n    <string name=\"chapters_read\">அத்தியாயங்கள் படித்தன</string>\n    <string name=\"chapters_left\">அத்தியாயங்கள் எஞ்சியுள்ளன</string>\n    <string name=\"external_source\">வெளிப்புற/சொருகி</string>\n    <string name=\"plugin_incompatible\">பொருந்தாத சொருகி அல்லது உள் பிழை. சொருகி மற்றும் கோட்டாட்சுவின் அண்மைக் கால பதிப்பைப் பயன்படுத்துகிறீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்</string>\n    <string name=\"plugin_incompatible_with_cause\">சொருகி பிழை: %s\\n சொருகி மற்றும் கோட்டாட்சுவின் அண்மைக் கால பதிப்பைப் பயன்படுத்துகிறீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்</string>\n    <string name=\"connection_ok\">இணைப்பு சரி</string>\n    <string name=\"updated_long_ago\">நீண்ட காலத்திற்கு முன்பே புதுப்பிக்கப்பட்டது</string>\n    <string name=\"unpopular\">பிரபலமற்ற</string>\n    <string name=\"low_rating\">குறைந்த மதிப்பீடு</string>\n    <string name=\"scrobbler_auth_intro\">%s உடன் ஒருங்கிணைப்பை அமைக்க உள்நுழைக. இது உங்கள் மங்கா வாசிப்பு முன்னேற்றம் மற்றும் நிலையை கண்காணிக்க உங்களை அனுமதிக்கும்</string>\n    <string name=\"unstable_feature_summary\">இந்த செயல்பாடு சோதனை. தரவு இழப்பைத் தவிர்க்க உங்களுக்கு காப்புப்பிரதி இருப்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்</string>\n    <string name=\"downloads_background\">பின்னணி பதிவிறக்கங்கள்</string>\n    <string name=\"download_new_chapters\">புதிய அத்தியாயங்களைப் பதிவிறக்கவும்</string>\n    <string name=\"manga_with_downloaded_chapters\">பதிவிறக்கம் செய்யப்பட்ட அத்தியாயங்களுடன் மங்கா</string>\n    <string name=\"manga_replaced\">மங்கா \\\"%1$s\\\" (%2$s) \\\"%3$s\\\" (%4$s) உடன் மாற்றப்பட்டது</string>\n    <string name=\"fixing_manga\">மங்காவை சரிசெய்தல்</string>\n    <string name=\"fixed\">வெற்றிகரமாக சரி செய்யப்பட்டது</string>\n    <string name=\"no_fix_required\">\\\"%s\\\" க்கு பிழைத்திருத்தம் தேவையில்லை</string>\n    <string name=\"no_alternatives_found\">\\\"%s\\\" க்கு மாற்று வழிகள் எதுவும் கிடைக்கவில்லை</string>\n    <string name=\"manga_fix_prompt\">இந்த செயல்பாடு தேர்ந்தெடுக்கப்பட்ட மங்காவிற்கான மாற்று ஆதாரங்களைக் கண்டறியும். பணி சிறிது நேரம் எடுக்கும் மற்றும் பின்னணியில் தொடரும்</string>\n    <string name=\"content_type_novel\">நாவல்</string>\n    <string name=\"content_type_manhua\">மன்உவா</string>\n    <string name=\"content_type_manhwa\">மன்அ்வா</string>\n    <string name=\"recently_added\">அண்மைக் காலத்தில் சேர்க்கப்பட்டது</string>\n    <string name=\"added_long_ago\">நீண்ட காலத்திற்கு முன்பு சேர்க்கப்பட்டது</string>\n    <string name=\"popular_in_hour\">இந்த மணிநேரம் பிரபலமானது</string>\n    <string name=\"popular_in_month\">இந்த மாதம் பிரபலமானது</string>\n    <string name=\"popular_in_year\">இந்த ஆண்டு பிரபலமானது</string>\n    <string name=\"original_language\">அசல் மொழி</string>\n    <string name=\"demographics\">மக்கள்தொகை</string>\n    <string name=\"demographic_shounen\">சவுன்</string>\n    <string name=\"demographic_shoujo\">சோசோ</string>\n    <string name=\"demographic_seinen\">அவரது</string>\n    <string name=\"demographic_josei\">சோசி</string>\n    <string name=\"filter_search_warning\">இந்த மூலமானது வடிப்பான்களுடன் தேடலை ஆதரிக்காது. உங்கள் வடிப்பான்கள் அழிக்கப்பட்டுவிட்டன</string>\n    <string name=\"content_type_one_shot\">ஒரு காட்சி</string>\n    <string name=\"content_type_doujinshi\">டூசின்சி</string>\n    <string name=\"download_cellular_confirm\">செல்லுலார் நெட்வொர்க்கில் பதிவிறக்கங்களை அனுமதிக்கவா?</string>\n    <string name=\"dont_allow\">அனுமதிக்க வேண்டாம்</string>\n    <string name=\"allow_always\">எப்போதும் அனுமதிக்கவும்</string>\n    <string name=\"allow_once\">ஒரு முறை அனுமதிக்கவும்</string>\n    <string name=\"ask_every_time\">ஒவ்வொரு முறையும் கேளுங்கள்</string>\n    <string name=\"screen_orientation\">திரை நோக்குநிலை</string>\n    <string name=\"portrait\">உருவப்படம்</string>\n    <string name=\"landscape\">நிலப்பரப்பு</string>\n    <string name=\"max_backups_count\">காப்புப்பிரதிகளின் அதிகபட்ச எண்ணிக்கை</string>\n    <string name=\"delete_old_backups\">பழைய காப்புப்பிரதிகளை நீக்கவும்</string>\n    <string name=\"delete_old_backups_summary\">சேமிப்பக இடத்தை சேமிக்க பழைய காப்பு கோப்புகளை தானாக நீக்கவும்</string>\n    <string name=\"handle_links\">இணைப்புகளைக் கையாளவும்</string>\n    <string name=\"handle_links_summary\">வெளிப்புற பயன்பாடுகளிலிருந்து மங்கா இணைப்புகளைக் கையாளவும் (எ.கா. வலை உலாவி). பயன்பாட்டின் கணினி அமைப்புகளில் நீங்கள் அதை கைமுறையாக இயக்க வேண்டியிருக்கலாம்</string>\n    <string name=\"captcha_required_message\">இந்த மூலத்தைத் தொடர ஒரு கேப்ட்சாவை தீர்க்க வேண்டும்</string>\n    <string name=\"rating\">செயல்வரம்பு</string>\n    <string name=\"source\">மூலம்</string>\n    <string name=\"translation\">மொழிபெயர்ப்பு</string>\n    <string name=\"show_slider\">ச்லைடரைக் காட்டு</string>\n    <string name=\"incognito\">மறைநிலை</string>\n    <string name=\"error_connection_reset\">தொலைநிலை புரவலன் மூலம் இணைப்பு மீட்டமைப்பு</string>\n    <string name=\"backup_tg_echo\">சோதனை செய்தி</string>\n    <string name=\"backup_tg_id_not_set\">அரட்டை ஐடி அமைக்கப்படவில்லை</string>\n    <string name=\"telegram_chat_id\">தந்தி அரட்டை ஐடி</string>\n    <string name=\"open_telegram_bot\">டெலிகிராம் போட் திறக்கவும்</string>\n    <string name=\"send_backups_telegram\">டெலிகிராமில் காப்புப்பிரதிகளை அனுப்பவும்</string>\n    <string name=\"test_connection\">சோதனை இணைப்பு</string>\n    <string name=\"telegram_chat_id_summary\">காப்புப்பிரதிகள் அனுப்பப்பட வேண்டிய அரட்டை ஐடியை உள்ளிடவும்</string>\n    <string name=\"open_telegram_bot_summary\">கோட்டாட்சு காப்பு போட் உடன் அரட்டையடிக்க அழுத்தவும்</string>\n    <string name=\"clear_database\">தரவுத்தளத்தை அழிக்கவும்</string>\n    <string name=\"clear_database_summary\">பயன்படுத்தப்படாத மங்கா பற்றிய தகவல்களை நீக்கு</string>\n    <string name=\"enable_all_sources\">அனைத்து மங்கா ஆதாரங்களையும் இயக்கவும்</string>\n    <string name=\"enable_all_sources_summary\">கிடைக்கக்கூடிய அனைத்து மங்கா ஆதாரங்களும் நிரந்தரமாக இயக்கப்படும்</string>\n    <string name=\"demographic_kodomo\">கோடோமோ</string>\n    <string name=\"clear_browser_data\">உலாவி தரவை அழிக்கவும்</string>\n    <string name=\"clear_browser_data_summary\">கேச் மற்றும் குக்கீகள் போன்ற உலாவி தரவை அழிக்கவும். எச்சரிக்கை: மாங்கா மூலங்களில் அங்கீகாரம் செல்லாததாக மாறக்கூடும்</string>\n    <string name=\"error_details\">பிழை விவரங்கள்</string>\n    <string name=\"no_write_permission_to_file\">கோப்பை எழுத அனுமதி இல்லை</string>\n    <string name=\"restoring_backup\">காப்புப்பிரதியை மீட்டமைக்கிறது</string>\n    <string name=\"search_disabled_sources\">முடக்கப்பட்ட ஆதாரங்கள் மூலம் தேடுங்கள்</string>\n    <string name=\"reader_info_bar_transparent\">வெளிப்படையான வாசகர் தகவல் பட்டி</string>\n    <string name=\"chapter_volume_number\">தொகுதி %1$s அத்தியாயம் %2$s</string>\n    <string name=\"chapter_number\">அத்தியாயம் %s</string>\n    <string name=\"unnamed_chapter\">பெயரிடப்படாத அத்தியாயம்</string>\n    <string name=\"simple\">எளிமையானது</string>\n    <string name=\"reader_controls_in_bottom_bar\">கீழ் பட்டியில் வாசகர் கட்டுப்பாடுகள்</string>\n    <string name=\"chapters_and_pages\">அத்தியாயங்கள் மற்றும் பக்கங்கள்</string>\n    <string name=\"pages_slider\">பக்க சுவிட்ச் ஸ்லைடர்</string>\n    <string name=\"screen_rotation_locked\">திரை சுழற்சி பூட்டப்பட்டுள்ளது</string>\n    <string name=\"screen_rotation_unlocked\">திரைச் சுழற்சி திறக்கப்பட்டது</string>\n    <string name=\"disable_captcha_notifications\">கேப்ட்சா அறிவிப்புகளை முடக்கு</string>\n    <string name=\"disable_captcha_notifications_summary\">இந்த மூலத்திற்கான CAPTCHA ஐத் தீர்ப்பது குறித்த அறிவிப்புகளை நீங்கள் பெறமாட்டீர்கள், ஆனால் இது பின்னணி செயல்பாடுகளை சீர்குலைக்க வழிவகுக்கும் (புதிய அத்தியாயங்களைச் சரிபார்த்தல், பரிந்துரைகளைப் பெறுதல் போன்றவை)</string>\n    <string name=\"global_search\">உலகளாவிய தேடல்</string>\n    <string name=\"badges_in_lists\">பட்டியல்களில் பேட்ஜ்கள்</string>\n    <string name=\"link_to_manga_in_app\">மை மற்றும் மாங்கா கொட்டாச்சு</string>\n    <string name=\"backup_restored_background\">காப்புப்பிரதி பின்னணியில் மீட்டமைக்கப்படும்</string>\n    <string name=\"error_disclaimer_manga\">மங்கா அதன் மூலத்தில் கிடைப்பதை உறுதிசெய்ய, இணைய உலாவியில் அதைத் திறக்க முயற்சிக்கவும்.</string>\n    <string name=\"error_disclaimer_app_outdated\">உங்கள் Kotatsu பதிப்பு காலாவதியானது போல் தெரிகிறது. கிடைக்கக்கூடிய அனைத்து திருத்தங்களையும் பெற சமீபத்திய பதிப்பை நிறுவவும்.</string>\n    <string name=\"link_to_manga_on_s\">மங்காவிற்கான இணைப்பு இயக்கப்பட்டது %s</string>\n    <string name=\"search_everywhere\">எல்லா இடங்களிலும் தேடுங்கள்</string>\n    <string name=\"error_disclaimer_report\">நீங்கள் டெவலப்பர்களிடம் பிழை அறிக்கையைச் சமர்ப்பிக்கலாம். இது சிக்கலை ஆராய்ந்து சரிசெய்ய எங்களுக்கு உதவும்.</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"theme_name_expressive\">வெளிப்படையான (சோதனை)</string>\n    <string name=\"reader_navigation_inverted\">வழிசெலுத்தல் கட்டுப்பாடுகள் தலைகீழ்</string>\n    <string name=\"reader_navigation_inverted_summary\">தொகுதி பொத்தான் மற்றும் திசை வன்பொருள் விசை வழிசெலுத்தலின் திசையை மாற்றவும் (இடது/மேல்/கீழ்/வலது)</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">அகவை வந்த மங்கா பரிந்துரைகளில் காட்டப்படாது. இந்த விருப்பம் சில ஆதாரங்களுடன் தவறானது</string>\n    <string name=\"include_disabled_sources\">ஊனமுற்ற ஆதாரங்களைச் சேர்க்கவும்</string>\n    <string name=\"suggestions_disabled_sources_summary\">ஊனமுற்றோர் உட்பட அனைத்து மங்கா மூலங்களிலிருந்தும் பரிந்துரைகளைக் காட்டுங்கள்</string>\n    <string name=\"tags_warnings\">ஆபத்தான வகைகளை முன்னிலைப்படுத்தவும்</string>\n    <string name=\"tags_warnings_summary\">பெரும்பாலான பயனர்களுக்கு பொருத்தமற்றதாக இருக்கும் வகைகளை முன்னிலைப்படுத்தவும்</string>\n    <string name=\"error_non_file_uri\">தேர்ந்தெடுக்கப்பட்ட பாதையை பயன்படுத்த முடியாது, ஏனெனில் அது ஒரு கோப்பு அல்லது கோப்பகத்தைக் குறிக்கவில்லை</string>\n    <string name=\"manga_override_hint\">இந்த மாற்றங்கள் பயன்பாட்டில் மங்கா எவ்வாறு காட்டப்படும் என்பதை பாதிக்கும்</string>\n    <string name=\"use_default_cover\">இயல்புநிலை அட்டையைப் பயன்படுத்தவும்</string>\n    <string name=\"pick_manga_page\">மங்கா பக்கத்தைத் தேர்ந்தெடுங்கள்</string>\n    <string name=\"pick_custom_file\">தனிப்பயன் கோப்பைத் தேர்ந்தெடுக்கவும்</string>\n    <string name=\"change_cover\">கவர் மாற்றவும்</string>\n    <string name=\"page_switch_timer\">பக்கம் ஒவ்வொரு ~%d விநாடிகளிலும் மாறும்</string>\n    <string name=\"dont_ask_again\">மீண்டும் கேட்க வேண்டாம்</string>\n    <string name=\"incognito_mode_hint_nsfw\">இந்த மங்காவில் அகவை வந்தோர் உள்ளடக்கம் இருக்கலாம். நீங்கள் மறைநிலை பயன்முறையைப் பயன்படுத்த விரும்புகிறீர்களா?</string>\n    <string name=\"incognito_for_nsfw\">NSFW மங்காவிற்கான மறைநிலை பயன்முறை</string>\n    <string name=\"additional_action_required\">கூடுதல் நடவடிக்கை தேவை</string>\n    <string name=\"hide_from_main_screen\">முதன்மையான திரையில் இருந்து மறைக்கவும்</string>\n    <string name=\"changelog\">மாற்றபதிவு</string>\n    <string name=\"changelog_summary\">அண்மைக் காலத்தில் வெளியிடப்பட்ட பதிப்புகளுக்கான வரலாற்றை மாற்றுகிறது</string>\n    <string name=\"collapse\">சரிவு</string>\n    <string name=\"expand\">விரிவாக்கு</string>\n    <string name=\"adblock\">உலாவியில் விளம்பரங்களைத் தடுக்கவும்</string>\n    <string name=\"adblock_summary\">உள்ளமைக்கப்பட்ட உலாவியில் (பீட்டா) தடுப்பு விளம்பரம்</string>\n    <string name=\"collapse_long_description\">நீண்ட விளக்கத்தை சரிவு</string>\n    <string name=\"creating_backup\">காப்புப்பிரதியை உருவாக்குதல்</string>\n    <string name=\"share_backup\">காப்புப்பிரதியைப் பகிரவும்</string>\n    <string name=\"reader_multitask\">ஒரு தனி பணியில் வாசகரைத் திறக்கவும்</string>\n    <string name=\"reader_multitask_summary\">ஒரே நேரத்தில் வெவ்வேறு மங்காவுடன் பல வாசகர்களை திறந்து வைக்க உங்களை அனுமதிக்கிறது</string>\n    <string name=\"theme_name_itsuka\">5 நாட்கள்</string>\n    <string name=\"theme_name_totoro\">டுடோரோ</string>\n    <string name=\"book_effect\">மஞ்சள் நிற பின்னணி (நீல வடிகட்டி)</string>\n    <string name=\"local_storage_cleanup\">உள்ளக சேமிப்பக தூய்மைப்படுத்தல்</string>\n    <string name=\"packup_creation_failed\">காப்புப்பிரதியை உருவாக்கத் தவறிவிட்டது</string>\n    <string name=\"main_screen\">முதன்மையான திரை</string>\n    <string name=\"main_screen_fab\">மிதக்கும் தொடர்ச்சியான பொத்தானைக் காட்டு</string>\n    <string name=\"main_screen_fab_summary\">ஒரே கிளிக்கில் தொடர்ந்து படிக்க அனுமதிக்கிறது. இந்த பொத்தான் மறைநிலை பயன்முறையில் அல்லது வரலாறு காலியாக இருக்கும்போது தோன்றாது</string>\n    <string name=\"error_corrupted_zip\">சிதைந்த சிப் காப்பகம் (%s)</string>\n    <string name=\"discord_rpc\">முரண்பாடு பணக்கார இருப்பு</string>\n    <string name=\"discord_token\">முரண்பாடு கிள்ளாக்கு</string>\n    <string name=\"discord_token_summary\">பணக்கார இருப்பை செயல்படுத்த உங்கள் டிச்கார்ட் கிள்ளாக்கை உள்ளிடவும்</string>\n    <string name=\"discord_token_description\">உங்கள் டிச்கார்ட் கிள்ளாக்கை உள்ளிடவும் அல்லது உலாவியைப் பயன்படுத்தி அதைப் பெற %s ஐக் சொடுக்கு செய்க</string>\n    <string name=\"discord_token_hint\">உங்கள் டிச்கார்ட் கிள்ளாக்கை இங்கே ஒட்டவும்</string>\n    <string name=\"discord_rpc_summary\">முரண்பாட்டில் உங்கள் வாசிப்பு நிலையைக் காட்டுங்கள்</string>\n    <string name=\"obtain\">பெறுங்கள்</string>\n    <string name=\"discord_rpc_description\">கோட்டாட்சுவில் மங்காவைப் படித்தல் - ஒரு மங்கா ரீடர் பயன்பாடு</string>\n    <string name=\"reading_s\">படித்தல் %s</string>\n    <string name=\"read_on_s\">%s படிக்கவும்</string>\n    <string name=\"rpc_skip_nsfw_summary\">வயதுவந்த உள்ளடக்கத்திற்கு RPC ஐப் பயன்படுத்த வேண்டாம்</string>\n    <string name=\"invalid_token\">தவறான டோக்கன்: %s</string>\n    <string name=\"show_floating_control_button\">மிதக்கும் கட்டுப்பாட்டு பொத்தானைக் காட்டு</string>\n    <string name=\"unavailable\">கிடைக்கவில்லை</string>\n    <string name=\"manga_restricted_description\">இந்த மூலத்தில் படிக்க இந்த மங்கா கிடைக்கவில்லை. மற்ற மூலங்களில் இதைத் தேட முயற்சிக்கவும் அல்லது மேலும் தகவலுக்கு உலாவியில் திறக்கவும்</string>\n    <string name=\"no_chapters_in_manga\">இந்த மங்காவில் எந்த அத்தியாயங்களும் இல்லை</string>\n    <string name=\"chapters_load_failed\">அத்தியாய பட்டியலை ஏற்றுவதில் தோல்வி</string>\n    <string name=\"telegram_integration\">தந்தி ஒருங்கிணைப்பு</string>\n    <string name=\"test_parser\">சோதனை மங்கா மூல</string>\n    <string name=\"pull_to_prev_chapter\">முந்தைய அத்தியாயத்தைத் திறக்க வெளியீடு</string>\n    <string name=\"pull_to_next_chapter\">அடுத்த அத்தியாயத்தைத் திறக்க வெளியீடு</string>\n    <string name=\"pull_top_no_prev\">முந்தைய அத்தியாயம் இல்லை</string>\n    <string name=\"pull_bottom_no_next\">அடுத்த அத்தியாயம் இல்லை</string>\n    <string name=\"enable_pull_gesture_title\">இழுக்கும் சைகையை இயக்கவும்</string>\n    <string name=\"enable_pull_gesture_summary\">வெப்டூனில் அத்தியாயங்களை மாற்ற புல் சைகையைப் பயன்படுத்தவும்</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-te/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d అంశం</item>\n        <item quantity=\"other\">%1$d అంశాలు</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d కొత్త అధ్యాయం</item>\n        <item quantity=\"other\">%1$d కొత్త అధ్యాయాలు</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d అధ్యాయం</item>\n        <item quantity=\"other\">%1$d అధ్యాయాలు</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d నిమిషం క్రితం</item>\n        <item quantity=\"other\">%1$d నిమిషాల క్రితం</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d గంట క్రితం</item>\n        <item quantity=\"other\">%1$d గంటల క్రితం</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d రోజు క్రితం</item>\n        <item quantity=\"other\">%1$d రోజుల క్రితం</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d నెల క్రితం</item>\n        <item quantity=\"other\">%1$d నెలల క్రితం</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d గంట</item>\n        <item quantity=\"other\">%1$d గంటలు</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d నిమిషం</item>\n        <item quantity=\"other\">%1$d నిమిషాలు</item>\n    </plurals>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-te/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"local_storage\">లోకల్ స్టోరేజ్</string>\n    <string name=\"favourites\">ఫేవరిట్స్</string>\n    <string name=\"history\">చరిత్ర</string>\n    <string name=\"error_occurred\">లోపం జరిగింది</string>\n    <string name=\"network_error\">నెట్‌వర్క్ లోపం</string>\n    <string name=\"details\">వివరాలు</string>\n    <string name=\"chapters\">అధ్యాయాలు</string>\n    <string name=\"list\">జాబితా</string>\n    <string name=\"detailed_list\">వివరమైన జాబితా</string>\n    <string name=\"grid\">గ్రిడ్</string>\n    <string name=\"list_mode\">జాబితా మోడ్</string>\n    <string name=\"settings\">సెట్టింగ్స్</string>\n    <string name=\"remote_sources\">మంగా సోర్సెస్</string>\n    <string name=\"loading_\">లోడ్ అవుతోంది…</string>\n    <string name=\"computing_\">లెక్కించబడుతోంది…</string>\n    <string name=\"chapter_d_of_d\">చాప్టర్ %1$d / %2$d</string>\n    <string name=\"close\">మూసివేయి</string>\n    <string name=\"try_again\">మళ్లీ ప్రయత్నించండి</string>\n    <string name=\"retry\">మళ్లీ ప్రయత్నించు</string>\n    <string name=\"clear_history\">చరిత్రని క్లియర్ చెయ్యి</string>\n    <string name=\"nothing_found\">ఏదీ దొరకలేదు</string>\n    <string name=\"history_is_empty\">చరిత్ర ఖాళీగా ఉంది</string>\n    <string name=\"read\">చదువు</string>\n    <string name=\"you_have_not_favourites_yet\">ఇంకా ఫేవరిట్స్ లేవు</string>\n    <string name=\"add_to_favourites\">ఫేవరెట్‌కి జోడించు</string>\n    <string name=\"add_new_category\">కొత్త వర్గం జోడించు</string>\n    <string name=\"add\">జోడించు</string>\n    <string name=\"save\">సేవ్</string>\n    <string name=\"share\">షేర్</string>\n    <string name=\"create_shortcut\">షార్ట్‌కట్ సృష్టించు</string>\n    <string name=\"share_s\">%sని షేర్ చెయ్యి</string>\n    <string name=\"search\">సెర్చ్</string>\n    <string name=\"search_manga\">మంగా సెర్చ్</string>\n    <string name=\"manga_downloading_\">మంగా డౌన్‌లోడ్ అవుతోంది…</string>\n    <string name=\"processing_\">ప్రాసెస్ అవుతోంది…</string>\n    <string name=\"download_complete\">డౌన్‌లోడ్ పూర్తైంది</string>\n    <string name=\"downloads\">డౌన్‌లోడ్లు</string>\n    <string name=\"by_name\">పేరుతో</string>\n    <string name=\"popular\">ప్రముఖమైనవి</string>\n    <string name=\"updated\">నవీకరించబడింది</string>\n    <string name=\"newest\">కొత్తవి</string>\n    <string name=\"by_rating\">రేటింగ్ ప్రకారం</string>\n    <string name=\"sort_order\">క్రమబద్ధత</string>\n    <string name=\"filter\">ఫిల్టర్</string>\n    <string name=\"theme\">థీమ్</string>\n    <string name=\"light\">లైట్</string>\n    <string name=\"dark\">డార్క్</string>\n    <string name=\"follow_system\">సిస్టమ్‌ను అనుసరించు</string>\n    <string name=\"pages\">పేజీలు</string>\n    <string name=\"clear\">క్లియర్ చెయ్యి</string>\n    <string name=\"remove\">తీసివేయి</string>\n    <string name=\"_s_deleted_from_local_storage\">“%s” లోకల్ స్టోరేజ్ నుండి తొలగించబడింది</string>\n    <string name=\"save_page\">పేజీ సేవ్ చెయ్యి</string>\n    <string name=\"page_saved\">పేజీలు సేవ్ అయ్యాయి</string>\n    <string name=\"pages_saved\">పేజీలు సేవ్ అయ్యాయి</string>\n    <string name=\"share_image\">చిత్రం షేర్ చెయ్యి</string>\n    <string name=\"_import\">ఇంపోర్ట్</string>\n    <string name=\"delete\">తొలగించు</string>\n    <string name=\"operation_not_supported\">ఈ ఆపరేషన్‌కు మద్దతు లేదు</string>\n    <string name=\"text_file_not_supported\">దయచేసి ZIP లేదా CBZ ఫైల్ ఎంచుకోండి</string>\n    <string name=\"no_description\">వివరణ లేదు</string>\n    <string name=\"clear_pages_cache\">పేజీల క్యాష్ క్లియర్ చెయ్యి</string>\n    <string name=\"text_file_sizes\">B | kB | MB | GB | TB</string>\n    <string name=\"standard\">స్టాండర్డ్</string>\n    <string name=\"webtoon\">వెబ్‌టూన్</string>\n    <string name=\"read_mode\">రీడ్ మోడ్</string>\n    <string name=\"grid_size\">గ్రిడ్ పరిమాణం</string>\n    <string name=\"search_on_s\">%s లో వెతుకు</string>\n    <string name=\"delete_manga\">మంగా తొలగించు</string>\n    <string name=\"text_delete_local_manga\">“%s”ను పరికరం నుండి శాశ్వతంగా తొలగించాలా?</string>\n    <string name=\"reader_settings\">రీడర్ సెట్టింగ్స్</string>\n    <string name=\"switch_pages\">పేజీలు మార్చు</string>\n    <string name=\"_continue\">కొనసాగించు</string>\n    <string name=\"error\">లోపం</string>\n    <string name=\"clear_thumbs_cache\">థంబ్నెయిల్ క్యాష్ క్లియర్ చెయ్యి</string>\n    <string name=\"clear_search_history\">వెతుకుల చరిత్ర క్లియర్ చెయ్యి</string>\n    <string name=\"search_history_cleared\">చరిత్ర క్లియర్ అయ్యింది</string>\n    <string name=\"internal_storage\">అంతర్గత స్టోరేజ్</string>\n    <string name=\"external_storage\">బాహ్య స్టోరేజ్</string>\n    <string name=\"domain\">డొమైన్</string>\n    <string name=\"app_update_available\">ఒక కొత్త వెర్షన్ అందుబాటులో ఉంది</string>\n    <string name=\"open_in_browser\">వెబ్ బ్రౌజర్‌లో తెరవండి</string>\n    <string name=\"notifications\">నోటిఫికేషన్స్</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d నుండి %2$d ఆన్</string>\n    <string name=\"new_chapters\">కొత్త చాప్టర్స్ / కొత్త అధ్యాయాలు</string>\n    <string name=\"download\">డౌన్‌లోడ్</string>\n    <string name=\"notifications_settings\">నోటిఫికేషన్స్ సెట్టింగ్స్</string>\n    <string name=\"notification_sound\">నోటిఫికేషన్ సౌండ్</string>\n    <string name=\"light_indicator\">LED సూచిక</string>\n    <string name=\"vibration\">వైబ్రేషన్</string>\n    <string name=\"favourites_categories\">ఫేవరెట్ వర్గాలు</string>\n    <string name=\"remove_category\">వర్గం తొలగించు</string>\n    <string name=\"text_empty_holder_primary\">ఇక్కడ ఏదీ లేదు…</string>\n    <string name=\"text_search_holder_secondary\">వెతుకును మరల ప్రయత్నించండి.</string>\n    <string name=\"text_history_holder_primary\">మీ చదివినవి ఇక్కడ చూపబడతాయి</string>\n    <string name=\"text_history_holder_secondary\">\\\"ఎక్స్‌ప్లోర్\\\" విభాగంలో చదవదగినవి వెతకండి</string>\n    <string name=\"text_empty_holder_secondary_filtered\">మీరు ఎంచుకున్న ఫిల్టర్లకు సరిపోలిన మంగా లేదు</string>\n    <string name=\"text_local_holder_primary\">ముందుగా ఏదైనా సేవ్ చేయండి</string>\n    <string name=\"text_local_holder_secondary\">ఆన్‌లైన్ కేటలాగ్ నుండి ఏదైనా సేవ్ చేయండి లేదా ఫైల్ నుండి దిగుమతి చేసుకోండి.</string>\n    <string name=\"manga_shelf\">షెల్ఫ్</string>\n    <string name=\"recent_manga\">తాజా మంగా</string>\n    <string name=\"pages_animation\">పేజీ యానిమేషన్</string>\n    <string name=\"manga_save_location\">డౌన్‌లోడ్స్ ఫోల్డర్</string>\n    <string name=\"not_available\">ఉపలబ్ధం లేదు</string>\n    <string name=\"cannot_find_available_storage\">లభ్యమైన స్టోరేజ్ లేదు</string>\n    <string name=\"other_storage\">ఇతర స్టోరేజ్</string>\n    <string name=\"done\">పూర్తయింది</string>\n    <string name=\"all_favourites\">అన్ని ఫేవరిట్స్</string>\n    <string name=\"favourites_category_empty\">వర్గం ఖాళీగా ఉంది</string>\n    <string name=\"read_later\">తరువాత చదవు</string>\n    <string name=\"updates\">నవీకరణలు</string>\n    <string name=\"text_feed_holder\">మీరు చదువుతున్న కొత్త అధ్యాయాలు ఇక్కడ చూపబడతాయి</string>\n    <string name=\"search_results\">వెతుకుపరి ఫలితాలు</string>\n    <string name=\"new_version_s\">కొత్త వెర్షన్: %s</string>\n    <string name=\"size_s\">సైజ్: %s</string>\n    <string name=\"clear_updates_feed\">నవీకరణ ఫీడ్ క్లియర్ చెయ్యి</string>\n    <string name=\"updates_feed_cleared\">ఫీడ్ క్లియర్ అయింది</string>\n    <string name=\"rotate_screen\">స్క్రీన్ తిరగబెట్టు</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-th/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d เดือนที่ผ่านมา</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d ตอน</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d ตอนใหม่</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d รายการ</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d วันที่ผ่านมา</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d นาทีที่แล้ว</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d ชั่วโมงที่แล้ว</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d ชั่วโมง</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d นาที</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-th/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"light\">สว่าง</string>\n    <string name=\"follow_system\">ตั้งค่าตามเครื่อง</string>\n    <string name=\"remove\">ลบ</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" ได้ถูกลบจากที่จัดเก็บในเครื่องแล้ว</string>\n    <string name=\"share_image\">แชร์รูปภาพ</string>\n    <string name=\"_import\">นำเข้า</string>\n    <string name=\"delete\">ลบ</string>\n    <string name=\"text_file_not_supported\">เลือกไฟล์ ZIP หรือ CBZ</string>\n    <string name=\"clear_pages_cache\">เคลียร์แคชหน้ามังงะ</string>\n    <string name=\"standard\">มาตรฐาน</string>\n    <string name=\"read_mode\">โหมดอ่าน</string>\n    <string name=\"grid_size\">ขนาดตาราง</string>\n    <string name=\"text_delete_local_manga\">ลบ \\\"%s\\\" ออกจากอุปกรณ์อย่างถาวรหรือไม่?</string>\n    <string name=\"switch_pages\">สลับหน้า</string>\n    <string name=\"error\">ข้อผิดพลาด</string>\n    <string name=\"app_update_available\">มีเวอร์ชันใหม่ของแอปนี้ให้ใช้งานแล้ว</string>\n    <string name=\"notifications\">การแจ้งเตือน</string>\n    <string name=\"notifications_settings\">ตั้งค่าการแจ้งเตือน</string>\n    <string name=\"vibration\">สั่น</string>\n    <string name=\"remove_category\">ลบ</string>\n    <string name=\"favourites\">รายการโปรด</string>\n    <string name=\"history\">ประวัติ</string>\n    <string name=\"error_occurred\">เกิดข้อผิดพลาด</string>\n    <string name=\"history_is_empty\">ยังไม่มีประวัติ</string>\n    <string name=\"read\">อ่าน</string>\n    <string name=\"you_have_not_favourites_yet\">ยังไม่มีรายการโปรด</string>\n    <string name=\"try_again\">ลองใหม่อีกครั้ง</string>\n    <string name=\"nothing_found\">ไม่พบอะไรเลย</string>\n    <string name=\"settings\">ตั้งค่า</string>\n    <string name=\"close\">ปิด</string>\n    <string name=\"loading_\">โปรดรอสักครู่…</string>\n    <string name=\"add\">เพิ่ม</string>\n    <string name=\"local_storage\">ที่เก็บข้อมูลในเครื่อง</string>\n    <string name=\"clear_history\">ลบประวัติ</string>\n    <string name=\"save\">บันทึก</string>\n    <string name=\"no_description\">ไม่มีคำอธิบาย</string>\n    <string name=\"network_error\">อินเตอร์เน็ตมีปัญหา</string>\n    <string name=\"share\">แชร์</string>\n    <string name=\"theme\">ธีม</string>\n    <string name=\"dark\">มืด</string>\n    <string name=\"pages\">หน้า</string>\n    <string name=\"clear\">เคลียร์</string>\n    <string name=\"page_saved\">บันทึกแล้ว</string>\n    <string name=\"save_page\">บันทึกหน้า</string>\n    <string name=\"search_history_cleared\">เคลียร์แล้ว</string>\n    <string name=\"clear_search_history\">เคลียร์ประวัติการค้นหา</string>\n    <string name=\"download\">ดาวน์โหลด</string>\n    <string name=\"notification_sound\">เสียงการแจ้งเตือน</string>\n    <string name=\"cannot_find_available_storage\">ไม่มีพื้นที่ว่างในอุปกรณ์</string>\n    <string name=\"done\">สำเร็จ</string>\n    <string name=\"text_feed_holder\">ตอนใหม่ของเรื่องที่คุณกําลังอ่านจะแสดงไว้ที่นี่</string>\n    <string name=\"passwords_mismatch\">รหัสผ่านไม่ตรงกัน</string>\n    <string name=\"text_empty_holder_primary\">ที่นี่มันว่างเปล่า…</string>\n    <string name=\"filter\">กรอง</string>\n    <string name=\"search_on_s\">ค้นหาบน %s</string>\n    <string name=\"delete_manga\">ลบมังงะ</string>\n    <string name=\"_continue\">ดำเนินการต่อ</string>\n    <string name=\"internal_storage\">ที่เก็บข้อมูลภายใน</string>\n    <string name=\"external_storage\">ที่จัดเก็บภายนอก</string>\n    <string name=\"open_in_browser\">เปิดในเบราว์เซอร์</string>\n    <string name=\"new_chapters\">ตอนใหม่</string>\n    <string name=\"favourites_categories\">หมวดหมู่รายการโปรด</string>\n    <string name=\"text_history_holder_primary\">สิ่งที่คุณอ่านจะแสดงที่นี่</string>\n    <string name=\"text_local_holder_primary\">บันทึกบางอย่างก่อน</string>\n    <string name=\"text_local_holder_secondary\">บันทึกบางอย่างจากแหล่งข้อมูลออนไลน์หรือนำเข้าจากไฟล์</string>\n    <string name=\"recent_manga\">ล่าสุด</string>\n    <string name=\"manga_save_location\">แฟ้มดาวน์โหลด</string>\n    <string name=\"other_storage\">พื้นที่จัดเก็บอื่น</string>\n    <string name=\"favourites_category_empty\">หมวดหมู่ที่ว่างเปล่า</string>\n    <string name=\"read_later\">อ่านทีหลัง</string>\n    <string name=\"updates\">อัพเดท</string>\n    <string name=\"search_results\">รายการค้นหา</string>\n    <string name=\"new_version_s\">เวอร์ชั่นใหม่: %s</string>\n    <string name=\"size_s\">ขนาด: %s</string>\n    <string name=\"update\">อัพเดท</string>\n    <string name=\"track_sources\">มองหาการอัพเดท</string>\n    <string name=\"wrong_password\">รหัสผ่านผิด</string>\n    <string name=\"repeat_password\">โปรดใส่รหัสผ่านอีกรอบ</string>\n    <string name=\"app_version\">เวอร์ชั่น %s</string>\n    <string name=\"check_for_updates\">ค้นหาการอัพเดท</string>\n    <string name=\"no_update_available\">ไม่พบการอัพเดท</string>\n    <string name=\"details\">รายละเอียด</string>\n    <string name=\"list\">รายการ</string>\n    <string name=\"add_to_favourites\">เพิ่มเข้ารายการโปรด</string>\n    <string name=\"add_new_category\">หมวดหมู่ใหม่</string>\n    <string name=\"manga_downloading_\">กำลังดาวน์โหลด…</string>\n    <string name=\"download_complete\">ดาวน์โหลดสำเร็จ</string>\n    <string name=\"by_name\">ชื่อ</string>\n    <string name=\"popular\">ความนิยม</string>\n    <string name=\"by_rating\">อันดับ</string>\n    <string name=\"share_s\">แชร์ %s</string>\n    <string name=\"search\">ค้นหา</string>\n    <string name=\"search_manga\">ค้นหามังงะ</string>\n    <string name=\"updated\">อัพเดทแล้ว</string>\n    <string name=\"remove_completed\">ลบสำเร็จแล้ว</string>\n    <string name=\"remove_completed_downloads_confirm\">ประวัติการดาวน์โหลดของคุณจะถูกลบอย่างถาวร ไฟล์ที่ดาวน์โหลดจะไม่ได้รับผลกระทบ</string>\n    <string name=\"text_downloads_list_holder\">คุณไม่มีอะไรในดาวน์โหลดเลย</string>\n    <string name=\"downloads_resumed\">การดาวน์โหลดของคุณได้ดำเนินการต่อแล้ว</string>\n    <string name=\"downloads_paused\">การดาวน์โหลดของคุณได้หยุดชั่วคราว</string>\n    <string name=\"data_and_privacy\">ข้อมูลและความเป็นส่วนตัว</string>\n    <string name=\"cookies_cleared\">คุกกี้ทั้งหมดได้ถูกลบแล้ว</string>\n    <string name=\"removed_from_history\">ได้ลบจากประวัติแล้ว</string>\n    <string name=\"removed_from_favourites\">ลบออกจากรายการโปรดแล้ว</string>\n    <string name=\"processing_\">ประมวลผล…</string>\n    <string name=\"downloads\">ดาวน์โหลด</string>\n    <string name=\"newest\">ใหม่ล่าสุด</string>\n    <string name=\"remote_sources\">แหล่งมังงะ</string>\n    <string name=\"computing_\">กำลังคำนวน…</string>\n    <string name=\"create_shortcut\">สร้าง shortcut…</string>\n    <string name=\"sort_order\">เรียวตามลำดับ</string>\n    <string name=\"enable\">เปิดใช้งาน</string>\n    <string name=\"downloads_removed\">การดาวน์โหลดของคุณได้ถูกนำออกแล้ว</string>\n    <string name=\"downloads_cancelled\">การดาวน์โหลดของคุณได้ถูกยกเลิกแล้ว</string>\n    <string name=\"downloaded\">ดาวน์โหลดแล้ว</string>\n    <string name=\"invert_colors\">กลับสี</string>\n    <string name=\"check_new_chapters_title\">ตรวจสอบตอนใหม่และแจ้งเตือน</string>\n    <string name=\"check_for_new_chapters\">ตรวจสอบบทความใหม่</string>\n    <string name=\"chapter_is_missing\">บทที่หายไป</string>\n    <string name=\"search_chapters\">ค้นหาตอน</string>\n    <string name=\"chapters_empty\">ไม่มีตอนในมังงะนี้</string>\n    <string name=\"chapters\">ตอน</string>\n    <string name=\"chapter_d_of_d\">บทที่ %1$d จาก %2$d</string>\n    <string name=\"disable_nsfw\">ปิด NSFW</string>\n    <string name=\"too_many_requests_message\">คำขอมากเกินไป ลองอีกครั้งในภายหลัง</string>\n    <string name=\"color_black\">ดำ</string>\n    <string name=\"color_white\">ขาว</string>\n    <string name=\"color_dark\">มืด</string>\n    <string name=\"color_light\">สว่าง</string>\n    <string name=\"description\">คำอธิบาย</string>\n    <string name=\"webtoon_zoom_summary\">อนุญาตให้ซูมเข้าในโหมดเว็บตูน</string>\n    <string name=\"show_pages_numbers_summary\">แสดงเลขหน้าที่มุมล่าง</string>\n    <string name=\"zoom_mode_fit_width\">พอดีกับความกว้าง</string>\n    <string name=\"black_dark_theme_summary\">ใช้พลังงานแบตเตอรี่บนจอหน้า AMOLED</string>\n    <string name=\"webtoon\">เว็บตูน</string>\n    <string name=\"operation_not_supported\">ไม่รองรับการดำเนินการนี้</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"domain\">โดเมน</string>\n    <string name=\"updates_feed_cleared\">เคลียร์แล้ว</string>\n    <string name=\"text_history_holder_secondary\">ค้นหาสิ่งที่ต้องอ่านในหมวด «สำรวจ»</string>\n    <string name=\"pages_animation\">การเคลื่อนไหวหน้ามังงะ</string>\n    <string name=\"all_favourites\">รายการโปรดทั้งหมด</string>\n    <string name=\"rotate_screen\">หมุนหน้าจอ</string>\n    <string name=\"dont_check\">ไม่ต้องตรวจสอบ</string>\n    <string name=\"enter_password\">ใส่รหัสผ่าน</string>\n    <string name=\"protect_application\">ป้องกันแอป</string>\n    <string name=\"protect_application_summary\">เมื่อเข้าแอป Kotatsu จะขึ้นให้ใส่รหัส</string>\n    <string name=\"about\">เกี่ยวกับ</string>\n    <string name=\"right_to_left\">ขวาไปซ้าย</string>\n    <string name=\"create_category\">หมวดหมู่ใหม่</string>\n    <string name=\"scale_mode\">โหมดสเกล</string>\n    <string name=\"zoom_mode_fit_center\">พอดีตรงกลาง</string>\n    <string name=\"zoom_mode_fit_height\">พอดีกับความสูง</string>\n    <string name=\"black_dark_theme\">ดำ</string>\n    <string name=\"just_now\">เมื่อเร็วนี้</string>\n    <string name=\"clear_feed\">เคลียร์ฟีด</string>\n    <string name=\"backup_restore\">สำรองและคืนค่า</string>\n    <string name=\"create_backup\">สร้างข้อมูลสำรอง</string>\n    <string name=\"restore_backup\">คืนค่าจากข้อมูลสำรอง</string>\n    <string name=\"data_restored\">คืนค่าแล้ว</string>\n    <string name=\"preparing_\">กำลังเตรียม…</string>\n    <string name=\"file_not_found\">ไม่พบไฟล์</string>\n    <string name=\"data_restored_success\">ข้อมูลทั้งหมดได้คืนค่าแล้ว</string>\n    <string name=\"data_restored_with_errors\">ข้อมูลทั้งหมดได้คืนค่าแล้วแต่พบเจอปัญหา</string>\n    <string name=\"yesterday\">เมื่อวาน</string>\n    <string name=\"long_ago\">นานมาแล้ว</string>\n    <string name=\"group\">กลุ่ม</string>\n    <string name=\"today\">วันนี้</string>\n    <string name=\"clear_cookies\">เคลียร์คุกกี้</string>\n    <string name=\"sign_in\">ลงชื่อเข้าใช้</string>\n    <string name=\"auth_required\">ลงชื่อเข้าใช้เพื่อเข้าสู่เนื้อหานี้</string>\n    <string name=\"default_s\">ค่าเริ่มต้น: %s</string>\n    <string name=\"next\">ต่อไป</string>\n    <string name=\"protect_application_subtitle\">ใส่รหัสผ่านเพื่อเริ่มแอปด้วย</string>\n    <string name=\"confirm\">ยืนยัน</string>\n    <string name=\"password_length_hint\">รหัสผ่านต้องมีความยาว 4 ตัวหรือมากกว่านั้น</string>\n    <string name=\"welcome\">ยินดีต้อนรับ</string>\n    <string name=\"backup_saved\">ข้อมูลสำรองได้บันทึกแล้ว</string>\n    <string name=\"feed\">ฟีด</string>\n    <string name=\"explore\">สำรวจ</string>\n    <string name=\"grid\">ตาราง</string>\n    <string name=\"list_mode\">โหมดรายการ</string>\n    <string name=\"detailed_list\">รายการแบบละเอียด</string>\n    <string name=\"network\">เครือข่าย</string>\n    <string name=\"password\">รหัสผ่าน</string>\n    <string name=\"username\">ชื่อผู้ใช้</string>\n    <string name=\"in_progress\">กำลังดำเนินการ</string>\n    <string name=\"languages\">ภาษา</string>\n    <string name=\"show\">แสดง</string>\n    <string name=\"order_added\">เพิ่มแล้ว</string>\n    <string name=\"progress\">ความคืบหน้า</string>\n    <string name=\"search_hint\">ใส่ชื่อเรื่องมังงะ,แนวหรือชื่อแหล่ง</string>\n    <string name=\"this_month\">เดือนนี้</string>\n    <string name=\"reader_settings\">ตั้งค่าการโปรแกรมอ่าน</string>\n    <string name=\"light_indicator\">ไฟ LED</string>\n    <string name=\"genres\">ประเภท</string>\n    <string name=\"exclude_nsfw_from_history\">ไม่รวมมังงะ NSFW ออกจากประวัติ</string>\n    <string name=\"state_finished\">เสร็จสิ้น</string>\n    <string name=\"state_ongoing\">กำลังดำเนินการอยู่</string>\n    <string name=\"system_default\">ค่าเริ่มต้น</string>\n    <string name=\"show_pages_numbers\">จํานวนหน้า</string>\n    <string name=\"screenshots_policy\">นโยบายการบันทึกภาพหน้าจอ</string>\n    <string name=\"screenshots_allow\">อนุญาต</string>\n    <string name=\"screenshots_block_nsfw\">บล็อกบน NSFW</string>\n    <string name=\"screenshots_block_all\">บล็อกตลอด</string>\n    <string name=\"suggestions\">คําแนะนํา</string>\n    <string name=\"suggestions_enable\">เปิดใช้คำแนะนำ</string>\n    <string name=\"manga_shelf\">ชั้น</string>\n    <string name=\"not_available\">ไม่สามารถใช้ได้</string>\n    <string name=\"backup_information\">คุณสามารถสํารองข้อมูลประวัติและรายการโปรดและกู้คืน</string>\n    <string name=\"tap_to_try_again\">แตะเพื่อลองอีกครั้ง</string>\n    <string name=\"silent\">เงียบ</string>\n    <string name=\"captcha_required\">ต้องมี CAPTCHA ถึงจะเข้าสำเร็จ</string>\n    <string name=\"captcha_solve\">แก้ไข</string>\n    <string name=\"text_clear_updates_feed_prompt\">ล้างประวัติการอัปเดตทั้งหมดอย่างถาวรไหม\\?</string>\n    <string name=\"reverse\">ย้อนกลับ</string>\n    <string name=\"tracker_warning\">บางอุปกรณ์มีการทำงานของระบบที่แตกต่างกัน ซึ่งอาจทำให้งานในเบื้องหลังเสียหายได้</string>\n    <string name=\"read_more\">อ่านเพิ่มเติม</string>\n    <string name=\"about_app_translation_summary\">แปลแอปพลิเคชันนี้</string>\n    <string name=\"about_app_translation\">การแปล</string>\n    <string name=\"auth_complete\">ได้รับอนุญาต</string>\n    <string name=\"default_mode\">โหมดเริ่มต้น</string>\n    <string name=\"new_sources_text\">มีแหล่งมังงะใหม่ให้เลือก</string>\n    <string name=\"crash_text\">มีบางอย่างผิดพลาด. กรุณารายงานข้อผิดพลาดไปยังนักพัฒนาเพื่อแก้ไข</string>\n    <string name=\"reader_mode_hint\">การตั้งค่าที่ตั้งไว้จะถูกบันทึกไว้สำหรับมังงะเรื่องนี้</string>\n    <string name=\"suggestions_summary\">แนะนำมังงะตามความต้องการของคุณ</string>\n    <string name=\"text_suggestion_holder\">เริ่มอ่านมังงะแล้วคุณจะได้รับการแนะนำมังงะตามความชอบของคุณ</string>\n    <string name=\"exclude_nsfw_from_suggestions\">อย่าแนะนำมังงะ NSFW</string>\n    <string name=\"reset_filter\">รีเซ็ตการกรอง</string>\n    <string name=\"onboard_text\">เลือกภาษาที่คุณต้องการอ่านมังงะ คุณสามารถเปลี่ยนได้ในภายหลังในการตั้งค่า</string>\n    <string name=\"never\">ไม่เคย</string>\n    <string name=\"only_using_wifi\">ใช้ Wi-Fi เท่านั้น</string>\n    <string name=\"always\">ตลอด</string>\n    <string name=\"preload_pages\">โหลดหน้าล่วงหน้า</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"text_delete_local_manga_batch\">ลบรายการที่เลือกออกจากอุปกรณ์อย่างถาวรไหม\\?</string>\n    <string name=\"removal_completed\">การลบเสร็จสมบูรณ์</string>\n    <string name=\"download_slowdown_summary\">ช่วยหลีกเลี่ยงการบล็อก IP ของคุณ</string>\n    <string name=\"canceled\">ยกเลิก</string>\n    <string name=\"account_already_exists\">บัญชีนี้มีอยู่แล้ว</string>\n    <string name=\"back\">กลับ</string>\n    <string name=\"enabled\">เปิดใช้งานแล้ว</string>\n    <string name=\"disabled\">ปิดการใช้งานแล้ว</string>\n    <string name=\"sync_title\">ซิงค์ข้อมูลของคุณ</string>\n    <string name=\"email_enter_hint\">กรอกอีเมลของคุณเพื่อดำเนินการต่อ</string>\n    <string name=\"hide\">ซ่อน</string>\n    <string name=\"notifications_enable\">เปิดการแจ้งเตือน</string>\n    <string name=\"name\">ชื่อ</string>\n    <string name=\"edit\">แก้ไข</string>\n    <string name=\"undo\">ย้อนกลับ</string>\n    <string name=\"disable_battery_optimization\">ปิดใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่</string>\n    <string name=\"disable_battery_optimization_summary\">ช่วยในการตรวจสอบการอัปเดตพื้นหลัง</string>\n    <string name=\"send\">ส่ง</string>\n    <string name=\"status_reading\">กำลังอ่าน</string>\n    <string name=\"status_re_reading\">อ่านซ้ำ</string>\n    <string name=\"status_completed\">อ่านเสร็จแล้ว</string>\n    <string name=\"status_on_hold\">พักไว้</string>\n    <string name=\"disable_all\">ปิดการใช้งานทั้งหมด</string>\n    <string name=\"report\">รีพอร์ต</string>\n    <string name=\"text_clear_cookies_prompt\">คุณจะออกจากระบบจากทุกแหล่ง</string>\n    <string name=\"clear_all_history\">ลบประวัติทั้งหมด</string>\n    <string name=\"data_deletion\">การลบข้อมูล</string>\n    <string name=\"show_reading_indicators\">แสดงตัวบอกความคืบหน้าในการอ่าน</string>\n    <string name=\"show_notification_new_chapters_off\">คุณจะไม่ได้รับการแจ้งเตือน แต่บทใหม่จะถูกเน้นในรายการ</string>\n    <string name=\"text_clear_search_history_prompt\">ลบคำค้นหาล่าสุดทั้งหมดอย่างถาวรไหม\\?</string>\n    <string name=\"detect_reader_mode_summary\">ตรวจสอบอัตโนมัติว่ามังงะเป็นเว็บตูนหรือไม่</string>\n    <string name=\"appwidget_recent_description\">มังงะที่คุณอ่านล่าสุด</string>\n    <string name=\"appearance\">รูปร่าง</string>\n    <string name=\"bookmark_remove\">ลบบุ๊คมาร์ค</string>\n    <string name=\"auth_not_supported_by\">%s ไม่รองรับการเข้าสู่ระบบ</string>\n    <string name=\"last_2_hours\">2 ชั่วโมงที่ผ่านมา</string>\n    <string name=\"edit_category\">แก้ไขหมวดหมู่</string>\n    <string name=\"bookmark_removed\">บุ๊คมาร์คถูกลบแล้ว</string>\n    <string name=\"suggestions_excluded_genres_summary\">ระบุประเภทที่คุณไม่ต้องการเห็นในคำแนะนำ</string>\n    <string name=\"dns_over_https\">DNS บน HTTPS</string>\n    <string name=\"appwidget_shelf_description\">มังงะจากรายการโปรดของคุณ</string>\n    <string name=\"bookmark_add\">เพิ่มบุ๊คมาร์ค</string>\n    <string name=\"logged_in_as\">เข้าสู่ระบบด้วย %s</string>\n    <string name=\"history_cleared\">ลบประวัติแล้ว</string>\n    <string name=\"bookmark_added\">บุ๊คมาร์คได้ถูกเพิ่มแล้ว</string>\n    <string name=\"suggestions_excluded_genres\">ยกเว้นประเภท</string>\n    <string name=\"exclude_nsfw_from_history_summary\">มังงะที่เป็น NSFW จะไม่ถูกเพิ่มเข้าไปในประวัติ และความคืบหน้าของคุณจะไม่ถูกบันทึก</string>\n    <string name=\"show_reading_indicators_summary\">แสดงเปอร์เซ็นต์การอ่านในประวัติและรายการโปรด</string>\n    <string name=\"manage\">จัดการ</string>\n    <string name=\"logout\">ออกจากระบบ</string>\n    <string name=\"no_bookmarks_yet\">ยังไม่มีบุ๊คมาร์ก</string>\n    <string name=\"bookmarks\">บุ๊คมาร์ค</string>\n    <string name=\"show_all\">แสดงทั้งหมด</string>\n    <string name=\"empty_favourite_categories\">ไม่มีหมวดหมู่ที่ชื่นชอบ</string>\n    <string name=\"invalid_domain_message\">โดเมนไม่ถูกต้อง</string>\n    <string name=\"tracking\">การติดตาม</string>\n    <string name=\"cancel_all\">ยกเลิกทั้งหมด</string>\n    <string name=\"reset\">รีเซ็ต</string>\n    <string name=\"source_disabled\">แหล่งที่มาถูกปิดใช้งาน</string>\n    <string name=\"storage_usage\">การใช้พื้นที่เก็บข้อมูล</string>\n    <string name=\"show_notification_new_chapters_on\">คุณจะได้รับการแจ้งเตือนเกี่ยวกับการอัปเดตมังงะที่คุณกำลังอ่าน</string>\n    <string name=\"various_languages\">ภาษาต่างๆ</string>\n    <string name=\"not_found_404\">ไม่พบหรือเนื้อหาอาจถูกลบแล้ว</string>\n    <string name=\"feed_will_update_soon\">การอัปเดตฟีดกำลังจะเริ่มเร็วๆ นี้</string>\n    <string name=\"server_error\">ข้อผิดพลาดฝั่งเซิร์ฟเวอร์ (%1$d) กรุณาลองใหม่อีกครั้งในภายหลัง</string>\n    <string name=\"network_unavailable_hint\">เปิด Wi-Fi หรือเครือข่ายมือถือเพื่ออ่านมังงะออนไลน์</string>\n    <string name=\"confirm_exit\">กดย้อนกลับอีกครั้งเพื่อออก</string>\n    <string name=\"sync_settings\">การตั้งค่าการซิงค์ข้อมูล</string>\n    <string name=\"other_cache\">แคชอื่นๆ</string>\n    <string name=\"downloads_wifi_only\">ดาวน์โหลดผ่าน Wi-Fi เท่านั้น</string>\n    <string name=\"discard\">ทิ้ง</string>\n    <string name=\"saved_manga\">มังงะที่บันทึกไว้</string>\n    <string name=\"automatic_scroll\">เลื่อนอัตโนมัติ</string>\n    <string name=\"invalid_port_number\">เลขพอร์ตไม่ถูกต้อง</string>\n    <string name=\"suggestions_info\">ข้อมูลทั้งหมดจะได้รับการวิเคราะห์ในอุปกรณ์นี้เท่านั้นและไม่เคยส่งไปที่ไหน</string>\n    <string name=\"contrast\">คอนทราสต์</string>\n    <string name=\"options\">ตัวเลือก</string>\n    <string name=\"download_slowdown\">ดาวน์โหลดช้าลง</string>\n    <string name=\"sync\">การซิงค์ข้อมูล</string>\n    <string name=\"random\">สุ่ม</string>\n    <string name=\"clear_updates_feed\">เคลียร์ฟีดอัปเดต</string>\n    <string name=\"import_completed_hint\">คุณสามารถลบไฟล์ต้นฉบับออกจากที่เก็บข้อมูลเพื่อประหยัดพื้นที่</string>\n    <string name=\"language\">ภาษา</string>\n    <string name=\"incognito_mode\">โหมดไม่ระบุตัวตน</string>\n    <string name=\"no_bookmarks_summary\">คุณสามารถสร้างบุ๊คมาร์กขณะอ่านมังงะได้</string>\n    <string name=\"available\">คงเหลือ</string>\n    <string name=\"network_unavailable\">เครือข่ายไม่พร้อมใช้งาน</string>\n    <string name=\"empty\">ว่างเปล่า</string>\n    <string name=\"text_unsaved_changes_prompt\">บันทึกหรือละทิ้งการเปลี่ยนแปลงที่ยังไม่ได้บันทึก\\?</string>\n    <string name=\"color_theme\">โทนสี</string>\n    <string name=\"brightness\">ความสว่าง</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"exit_confirmation_summary\">กดย้อนกลับสองครั้งเพื่อออกจากแอป</string>\n    <string name=\"bookmarks_removed\">ลบบุ๊คมาร์กแล้ว</string>\n    <string name=\"error_no_space_left\">ไม่มีพื้นที่เหลือบนอุปกรณ์แล้ว</string>\n    <string name=\"zoom_in\">ซูมเข้า</string>\n    <string name=\"detect_reader_mode\">โหมดตรวจจับโปรแกรมอ่านอัตโนมัติ</string>\n    <string name=\"frequency_every_day\">ทุกวัน</string>\n    <string name=\"categories\">หมวดหมู่</string>\n    <string name=\"backup_frequency\">ความถี่การสำรองข้อมูล</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d of %2$d on</string>\n    <string name=\"unknown\">ไม่ทราบ</string>\n    <string name=\"frequency_every_2_days\">ทุก 2 วัน</string>\n    <string name=\"frequency_once_per_week\">สัปดาห์ละครั้ง</string>\n    <string name=\"reader_zoom_buttons\">โชว์ปุ่มซูม</string>\n    <string name=\"select_range\">เลือกช่วง</string>\n    <string name=\"no_manga_sources\">ไม่พบแหล่งมังงะ</string>\n    <string name=\"frequency_twice_per_month\">สองครั้งต่อเดือน</string>\n    <string name=\"keep_screen_on\">เปิดหน้าจอไว้ตลอด</string>\n    <string name=\"categories_delete_confirm\">คุณแน่ใจหรือไม่ว่าต้องการลบหมวดหมู่รายการโปรดที่เลือก \\nมังงะทั้งหมดในนั้นจะหายไปและไม่สามารถยกเลิกได้</string>\n    <string name=\"frequency_once_per_month\">เดือนละครั้ง</string>\n    <string name=\"no_manga_sources_text\">เปิดใช้งานแหล่งมังงะเพื่ออ่านมังงะออนไลน์</string>\n    <string name=\"enhanced_colors\">โหมดสี 32 บิต</string>\n    <string name=\"background\">พื้นหลัง</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"use_fingerprint\">ใช้ข้อมูลทางชีวภาพหากมี</string>\n    <string name=\"zoom_out\">ซูมออก</string>\n    <string name=\"text_search_holder_secondary\">โปรดลองเรียบเรียงคำค้นหา</string>\n    <string name=\"last_successful_backup\">สำรองข้อมูลสำเร็จล่าสุด: %s</string>\n    <string name=\"content_type_manga\">มังงะ</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"reader_optimize\">ลดการใช้แรม (เบต้า)</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">อาจจะช่วยในการเริ่มต้นการดาวน์โหลดถ้าคุณมีปัญหา</string>\n    <string name=\"this_manga\">มังงะเรื่องนี้</string>\n    <string name=\"lock_screen_rotation\">ล็อคการหมุนหน้าจอ</string>\n    <string name=\"skip\">ข้าม</string>\n    <string name=\"state_paused\">หยุดชั่วคราว</string>\n    <string name=\"content_type_other\">อื่น</string>\n    <string name=\"pages_cache\">แคชหน้า</string>\n    <string name=\"reader_info_pattern\">บท. %1$d/%2$d หน้า. %3$d/%4$d</string>\n    <string name=\"queued\">เพิ่มเข้าคิวแล้ว</string>\n    <string name=\"exit_confirmation\">ยืนยันการออก</string>\n    <string name=\"reorder\">เรียงลำดับ</string>\n    <string name=\"clear_cookies_summary\">สามารถช่วยได้ในกรณีที่มีปัญหาบางอย่าง การอนุญาตทั้งหมดจะถูกยกเลิก</string>\n    <string name=\"folder_with_images\">แฟ้มที่มีรูปภาพ</string>\n    <string name=\"history_shortcuts\">แสดงทางลัดมังงะล่าสุด</string>\n    <string name=\"reader_slider\">แสดงแถบเลื่อนการสลับหน้า</string>\n    <string name=\"no_chapters\">ไม่มีตอน</string>\n    <string name=\"compact\">กระชับ</string>\n    <string name=\"sync_auth_hint\">คุณสามารถเข้าใช้บัญชีที่มีอยู่หรือสร้างบัญชีใหม่ได้</string>\n    <string name=\"suggestion_manga\">คําแนะนํา: %s</string>\n    <string name=\"suggestions_updating\">การอัปเดตคําแนะนํา</string>\n    <string name=\"local_manga_processing\">ประมวลผลมังงะที่บันทึกไว้</string>\n    <string name=\"reader_info_bar\">แสดงแถบข้อมูลในโปรแกรมอ่าน</string>\n    <string name=\"import_completed\">นําเข้าเสร็จสมบูรณ์</string>\n    <string name=\"import_will_start_soon\">การนําเข้าจะเริ่มเร็วๆ นี้</string>\n    <string name=\"color_correction\">การแก้ไขสี</string>\n    <string name=\"prefetch_content\">การโหลดเนื้อหาล่วงหน้า</string>\n    <string name=\"resume\">ดำเนินการต่อ</string>\n    <string name=\"paused\">หยุดชั่วคราว</string>\n    <string name=\"pause\">หยุด</string>\n    <string name=\"automatic\">อัตโนมัติ</string>\n    <string name=\"fullscreen_mode\">โหมดเต็มหน้าจอ</string>\n    <string name=\"remove_from_history\">ลบออกจากประวัติ</string>\n    <string name=\"config_reset_confirm\">รีเซ็ตการตั้งค่าเป็นค่าเริ่มต้นไหม? การกระทำนี้ไม่สามารถย้อนกลับได้</string>\n    <string name=\"reading_time_estimation\">แสดงเวลาในการอ่านโดยประมาณ</string>\n    <string name=\"reading_time_estimation_summary\">การประมาณเวลา ผลลัพธ์อาจจะไม่ถูกต้อง</string>\n    <string name=\"location\">ตำแหน่ง</string>\n    <string name=\"theme_name_mion\">มิอง</string>\n    <string name=\"theme_name_sakura\">ซากุระ</string>\n    <string name=\"nothing_here\">ไม่มีอะไรอยู่ที่นี่</string>\n    <string name=\"theme_name_asuka\">อาซึกะ</string>\n    <string name=\"theme_name_miku\">มิกุ</string>\n    <string name=\"download_option_manual_selection\">เลือกตอนด้วยตนเอง</string>\n    <string name=\"chapters_grid_view\">แสดงแบบตาราง</string>\n    <string name=\"popular_in_month\">เป็นที่นิยมในเดือนนี้</string>\n    <string name=\"popular_today\">เป็นที่นิยมในวันนี้</string>\n    <string name=\"popular_in_hour\">เป็นที่นิยมในชั่วโมงนี้</string>\n    <string name=\"year\">ปี</string>\n    <string name=\"original_language\">ภาษาต้นฉบับ</string>\n    <string name=\"popular_in_year\">เป็นที่นิยมในปีนี้</string>\n    <string name=\"years\">ปี</string>\n    <string name=\"enable_logging\">เปิดใช้งานการบันทึกข้อมูล</string>\n    <string name=\"share_logs\">แชร์บันทึกข้อมูล</string>\n    <string name=\"comics_archive_import_description\">คุณสามารถเลือกไฟล์ .cbz หรือ .zip ได้ตั้งแต่หนึ่งไฟล์ขึ้นไป โดยแต่ละไฟล์จะนับว่าเป็นมังงะแยกกัน</string>\n    <string name=\"popular_in_week\">เป็นที่นิยมสัปดาห์นี้</string>\n    <string name=\"added_long_ago\">เพิ่มมานานแล้ว</string>\n    <string name=\"recently_added\">เพิ่มล่าสุด</string>\n    <string name=\"telegram_group\">กลุ่ม Telegram</string>\n    <string name=\"user_manual\">คู่มือการใช้งาน</string>\n    <string name=\"source_code\">ซอร์สโค้ด</string>\n    <string name=\"theme_name_dynamic\">ไดนามิกส์</string>\n    <string name=\"show_suspicious_content\">แสดงเนื้อหาที่น่าสงสัย</string>\n    <string name=\"theme_name_mamimi\">มามิมิ</string>\n    <string name=\"theme_name_rikka\">ริกกะ</string>\n    <string name=\"download_started\">เริ่มดาวน์โหลดแล้ว</string>\n    <string name=\"got_it\">เข้าใจแล้ว</string>\n    <string name=\"sources_reorder_tip\">แตะค้างไว้เพื่อเรียงลำดับใหม่</string>\n    <string name=\"error_image_format\">รูปแบบไฟล์รูปภาพที่ไม่รองรับ: %s</string>\n    <string name=\"invalid_server_address_message\">ที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง</string>\n    <string name=\"theme_name_kanade\">คานาเดะ</string>\n    <string name=\"downloads_wifi_only_summary\">หยุดดาวน์โหลดเมื่อสลับไปใช้เครือข่ายมือถือ</string>\n    <string name=\"cancel_all_downloads_confirm\">การดาวน์โหลดที่ทำงานอยู่ทั้งหมดจะถูกยกเลิก ข้อมูลที่ดาวน์โหลดบางส่วนจะสูญหาย</string>\n    <string name=\"start_download\">เริ่มดาวน์โหลด</string>\n    <string name=\"save_manga\">บันทึกมังงะ</string>\n    <string name=\"enable_logging_summary\">บันทึกข้อมูลบางอย่างเพื่อใช้ในการแก้ไขข้อบกพร่อง อย่าเปิดถ้าคุณไม่รู้ว่ากำลังทำอะไรอยู่</string>\n    <string name=\"show_in_grid_view\">แสดงในแบบมุมมองตาราง</string>\n    <string name=\"settings_apply_restart_required\">โปรดรีสตาร์ทแอปพลิเคชันเพื่อใช้การเปลี่ยนแปลงเหล่านี้</string>\n    <string name=\"genre\">ประเภท</string>\n    <string name=\"download_added\">เพิ่มการดาวน์โหลดแล้ว</string>\n    <string name=\"chapters_all\">ตอนทั้งหมด</string>\n    <string name=\"more_options\">ตัวเลือกเพิ่มเติม</string>\n    <string name=\"retry\">ลองอีกครั้ง</string>\n    <string name=\"download_over_cellular\">กำลังดาวน์โหลดผ่านเครือข่ายมือถือ</string>\n    <string name=\"download_cellular_confirm\">อนุญาตให้ดาวน์โหลดผ่านเครือข่ายมือถือหรือไม่?</string>\n    <string name=\"ask_every_time\">ถามทุกครั้ง</string>\n    <string name=\"allow_always\">อนุญาตเสมอ</string>\n    <string name=\"dont_allow\">ไม่อนุญาต</string>\n    <string name=\"pages_saved\">บันทึกแล้ว</string>\n    <string name=\"clear_thumbs_cache\">ล้างแคชรูปย่อ</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-tr/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d öge</item>\n        <item quantity=\"other\">%1$d öge</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d bölüm</item>\n        <item quantity=\"other\">%1$d bölüm</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d saat önce</item>\n        <item quantity=\"other\">%1$d saat önce</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d yeni bölüm</item>\n        <item quantity=\"other\">%1$d yeni bölüm</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d gün önce</item>\n        <item quantity=\"other\">%1$d gün önce</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d dakika önce</item>\n        <item quantity=\"other\">%1$d dakika önce</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d ay önce</item>\n        <item quantity=\"other\">%1$d ay önce</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d saat</item>\n        <item quantity=\"other\">%1$d saat</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d dakika</item>\n        <item quantity=\"other\">%1$d dakika</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"network_error\">Ağ hatası</string>\n    <string name=\"local_storage\">Dahili Depolama</string>\n    <string name=\"favourites\">Favoriler</string>\n    <string name=\"history\">Geçmiş</string>\n    <string name=\"chapters\">Bölümler</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"detailed_list\">Detaylı liste</string>\n    <string name=\"grid\">Izgara</string>\n    <string name=\"list_mode\">Liste modu</string>\n    <string name=\"loading_\">Yükleniyor…</string>\n    <string name=\"close\">Kapat</string>\n    <string name=\"try_again\">Tekrar dene</string>\n    <string name=\"clear_history\">Geçmişi temizle</string>\n    <string name=\"nothing_found\">Hiçbir şey bulunamadı</string>\n    <string name=\"history_is_empty\">Geçmiş yok</string>\n    <string name=\"read\">Oku</string>\n    <string name=\"you_have_not_favourites_yet\">Henüz favoriniz yok</string>\n    <string name=\"add_to_favourites\">Favorilere ekle</string>\n    <string name=\"add_new_category\">Yeni kategori</string>\n    <string name=\"add\">Ekle</string>\n    <string name=\"save\">Kaydet</string>\n    <string name=\"share\">Paylaş</string>\n    <string name=\"share_s\">%s Paylaş</string>\n    <string name=\"search\">Ara</string>\n    <string name=\"search_manga\">Manga ara</string>\n    <string name=\"manga_downloading_\">İndiriliyor…</string>\n    <string name=\"processing_\">İşleniyor…</string>\n    <string name=\"download_complete\">İndirildi</string>\n    <string name=\"downloads\">İndirmeler</string>\n    <string name=\"by_name\">Ad</string>\n    <string name=\"updated\">Güncellenenler</string>\n    <string name=\"newest\">En yeniler</string>\n    <string name=\"by_rating\">Puanlama</string>\n    <string name=\"filter\">Filtre</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"light\">Açık</string>\n    <string name=\"dark\">Koyu</string>\n    <string name=\"follow_system\">Sistemle uyumlu</string>\n    <string name=\"pages\">Sayfalar</string>\n    <string name=\"clear\">Temizle</string>\n    <string name=\"remove\">Kaldır</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" yerel depolamadan silindi</string>\n    <string name=\"save_page\">Sayfayı kaydet</string>\n    <string name=\"share_image\">Görseli paylaş</string>\n    <string name=\"popular\">Popüler</string>\n    <string name=\"details\">Detaylar</string>\n    <string name=\"settings\">Ayarlar</string>\n    <string name=\"page_saved\">Sayfa kaydedildi</string>\n    <string name=\"error_occurred\">Bir hata oluştu</string>\n    <string name=\"remote_sources\">Manga kaynakları</string>\n    <string name=\"search_history_cleared\">Temizlendi</string>\n    <string name=\"_continue\">Devam Et</string>\n    <string name=\"not_available\">Mevcut değil</string>\n    <string name=\"favourites_category_empty\">Boş kategori</string>\n    <string name=\"remove_category\">Kaldır</string>\n    <string name=\"delete\">Sil</string>\n    <string name=\"chapter_d_of_d\">Bölüm %1$d / %2$d</string>\n    <string name=\"text_file_not_supported\">Bir ZIP veya CBZ dosyası seçin.</string>\n    <string name=\"read_mode\">Okuma modu</string>\n    <string name=\"grid_size\">Izgara boyutu</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"reader_settings\">Okuyucu ayarları</string>\n    <string name=\"error\">Hata</string>\n    <string name=\"clear_thumbs_cache\">Küçük resim önbelleğini temizle</string>\n    <string name=\"domain\">Alan adı</string>\n    <string name=\"open_in_browser\">Web tarayıcısında aç</string>\n    <string name=\"new_chapters\">Yeni bölümler</string>\n    <string name=\"notifications_settings\">Bildirim ayarları</string>\n    <string name=\"notification_sound\">Bildirim sesi</string>\n    <string name=\"light_indicator\">LED göstergesi</string>\n    <string name=\"vibration\">Titreşim</string>\n    <string name=\"other_storage\">Diğer depolama</string>\n    <string name=\"updates\">Güncellemeler</string>\n    <string name=\"create_shortcut\">Kısayol oluştur</string>\n    <string name=\"_import\">İçe aktar</string>\n    <string name=\"delete_manga\">Mangayı sil</string>\n    <string name=\"computing_\">Bilgi işleniyor…</string>\n    <string name=\"sort_order\">Sıralama düzeni</string>\n    <string name=\"no_description\">Açıklama yok</string>\n    <string name=\"operation_not_supported\">Bu işlem desteklenmiyor</string>\n    <string name=\"standard\">Standart</string>\n    <string name=\"clear_pages_cache\">Sayfa önbelleğini temizle</string>\n    <string name=\"search_on_s\">%s üzerinde ara</string>\n    <string name=\"internal_storage\">Dahili depolama</string>\n    <string name=\"notifications\">Bildirimler</string>\n    <string name=\"switch_pages\">Sayfalarda ilerleme</string>\n    <string name=\"download\">İndir</string>\n    <string name=\"manga_save_location\">İndirilenler klasörü</string>\n    <string name=\"external_storage\">Harici depolama</string>\n    <string name=\"app_update_available\">Uygulamanın yeni bir sürümü mevcut</string>\n    <string name=\"favourites_categories\">Favori kategoriler</string>\n    <string name=\"done\">Bitti</string>\n    <string name=\"read_later\">Sonra oku</string>\n    <string name=\"pages_animation\">Sayfa animasyonu</string>\n    <string name=\"cannot_find_available_storage\">Kullanılabilir depolama alanı yok</string>\n    <string name=\"text_delete_local_manga\">\\\"%s\\\" cihazdan kalıcı olarak silinsin mi?</string>\n    <string name=\"clear_search_history\">Arama geçmişini temizle</string>\n    <string name=\"text_empty_holder_primary\">Burası biraz boş…</string>\n    <string name=\"rotate_screen\">Ekranı döndür</string>\n    <string name=\"scale_mode\">Boyutlandırma modu</string>\n    <string name=\"zoom_mode_fit_height\">Yüksekliğe sığdır</string>\n    <string name=\"black_dark_theme\">Siyah</string>\n    <string name=\"zoom_mode_keep_start\">Başlangıçta tut</string>\n    <string name=\"clear_feed\">Akışı temizle</string>\n    <string name=\"restore_backup\">Yedekten geri yükle</string>\n    <string name=\"update\">Güncelle</string>\n    <string name=\"sign_in\">Oturum aç</string>\n    <string name=\"state_finished\">Tamamlandı</string>\n    <string name=\"about\">Hakkında</string>\n    <string name=\"auth_required\">Bu içeriği görüntülemek için oturum açın</string>\n    <string name=\"confirm\">Onayla</string>\n    <string name=\"auth_complete\">Yetkilendirildi</string>\n    <string name=\"just_now\">Az önce</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%1$d / %2$d açık</string>\n    <string name=\"text_search_holder_secondary\">Sorguyu yeniden biçimlendirmeyi deneyin.</string>\n    <string name=\"text_history_holder_primary\">Okuduklarınız burada görüntülenecek</string>\n    <string name=\"text_history_holder_secondary\">«Keşfet» kısmında neler okuyacağınızı bulun</string>\n    <string name=\"text_local_holder_primary\">Önce bir şey kaydedin</string>\n    <string name=\"text_local_holder_secondary\">Çevrim içi kaynaklardan kaydedin veya dosyaları içe aktarın.</string>\n    <string name=\"manga_shelf\">Raf</string>\n    <string name=\"recent_manga\">Son</string>\n    <string name=\"size_s\">Boyut: %s</string>\n    <string name=\"updates_feed_cleared\">Temizlendi</string>\n    <string name=\"enter_password\">Parola gir</string>\n    <string name=\"protect_application_summary\">Kotatsu başlatılırken parola sor</string>\n    <string name=\"track_sources\">Güncellemeleri ara</string>\n    <string name=\"clear_updates_feed\">Güncelleme akışını temizle</string>\n    <string name=\"feed_will_update_soon\">Akış güncellemesi yakında başlayacak</string>\n    <string name=\"app_version\">Sürüm %s</string>\n    <string name=\"check_for_updates\">Güncellemeleri kontrol et</string>\n    <string name=\"zoom_mode_fit_center\">Merkeze sığdır</string>\n    <string name=\"zoom_mode_fit_width\">Genişliğe sığdır</string>\n    <string name=\"black_dark_theme_summary\">AMOLED ekranlarda daha az güç kullanır</string>\n    <string name=\"backup_restore\">Yedekle ve geri yükle</string>\n    <string name=\"create_backup\">Yedek oluştur</string>\n    <string name=\"data_restored\">Geri yüklendi</string>\n    <string name=\"preparing_\">Hazırlanıyor…</string>\n    <string name=\"yesterday\">Dün</string>\n    <string name=\"group\">Grup</string>\n    <string name=\"silent\">Sessiz</string>\n    <string name=\"captcha_solve\">Çöz</string>\n    <string name=\"clear_cookies\">Çerezleri temizle</string>\n    <string name=\"default_s\">Varsayılan: %s</string>\n    <string name=\"reverse\">Tersten</string>\n    <string name=\"password_length_hint\">Parola 4 veya daha fazla karakterden oluşmalıdır</string>\n    <string name=\"welcome\">Hoş geldiniz</string>\n    <string name=\"queued\">Sıraya alındı</string>\n    <string name=\"chapter_is_missing\">Bölüm eksik</string>\n    <string name=\"about_app_translation_summary\">Bu uygulamayı çevirin</string>\n    <string name=\"about_app_translation\">Çeviri</string>\n    <string name=\"state_ongoing\">Devam ediyor</string>\n    <string name=\"text_clear_cookies_prompt\">Tüm kaynaklardaki oturumunuz kapatılacak</string>\n    <string name=\"exclude_nsfw_from_history\">Uygunsuz mangayı geçmişten hariç tut</string>\n    <string name=\"show_pages_numbers\">Sayfa numaraları</string>\n    <string name=\"search_results\">Arama sonuçları</string>\n    <string name=\"repeat_password\">Parolayı tekrarla</string>\n    <string name=\"dont_check\">Kontrol etme</string>\n    <string name=\"wrong_password\">Yanlış parola</string>\n    <string name=\"backup_information\">Geçmişinizin ve favorilerinizin yedeğini oluşturabilir ve bunları geri yükleyebilirsiniz</string>\n    <string name=\"long_ago\">Uzun zaman önce</string>\n    <string name=\"today\">Bugün</string>\n    <string name=\"no_update_available\">Güncelleme yok</string>\n    <string name=\"all_favourites\">Tüm favoriler</string>\n    <string name=\"text_feed_holder\">Okuduklarınızın yeni bölümleri burada gösterilir</string>\n    <string name=\"new_version_s\">Yeni sürüm: %s</string>\n    <string name=\"protect_application\">Uygulamayı koru</string>\n    <string name=\"passwords_mismatch\">Parolalar eşleşmiyor</string>\n    <string name=\"right_to_left\">Sağdan-sola</string>\n    <string name=\"create_category\">Yeni kategori</string>\n    <string name=\"file_not_found\">Dosya bulunamadı</string>\n    <string name=\"data_restored_success\">Tüm veriler geri yüklendi</string>\n    <string name=\"data_restored_with_errors\">Veriler geri yüklendi, ancak hatalar var</string>\n    <string name=\"tap_to_try_again\">Tekrar denemek için dokunun</string>\n    <string name=\"next\">İleri</string>\n    <string name=\"captcha_required\">CAPTCHA gerekli</string>\n    <string name=\"cookies_cleared\">Tüm çerezler kaldırıldı</string>\n    <string name=\"reader_mode_hint\">Seçilen yapılandırma bu manga için hatırlanacak</string>\n    <string name=\"text_clear_updates_feed_prompt\">Tüm güncelleme geçmişi kalıcı olarak silinsin mi\\?</string>\n    <string name=\"protect_application_subtitle\">Uygulamayı başlatmak için bir parola girin</string>\n    <string name=\"text_clear_search_history_prompt\">Tüm son arama sorguları kalıcı olarak kaldırılsın mı\\?</string>\n    <string name=\"backup_saved\">Yedek kaydedildi</string>\n    <string name=\"genres\">Türler</string>\n    <string name=\"system_default\">Varsayılan</string>\n    <string name=\"auth_not_supported_by\">%s üzerinde oturum açma desteklenmiyor</string>\n    <string name=\"read_more\">Daha fazla oku</string>\n    <string name=\"tracker_warning\">Bazı cihazların arka plan görevlerini bozabilecek farklı sistem davranışları vardır.</string>\n    <string name=\"screenshots_policy\">Ekran görüntüleri</string>\n    <string name=\"screenshots_block_nsfw\">Uygunsuzlarda engelle</string>\n    <string name=\"screenshots_block_all\">Her zaman engelle</string>\n    <string name=\"screenshots_allow\">İzin ver</string>\n    <string name=\"check_for_new_chapters\">Yeni bölümleri kontrol et</string>\n    <string name=\"suggestions\">Öneriler</string>\n    <string name=\"suggestions_enable\">Önerileri etkinleştir</string>\n    <string name=\"suggestions_summary\">Tercihlerinize göre manga önerileri alın</string>\n    <string name=\"suggestions_info\">Tüm veriler bu cihazda yalnızca yerel olarak analiz edilir ve asla hiçbir yere gönderilmez.</string>\n    <string name=\"text_suggestion_holder\">Manga okumaya başladıktan sonra kişiselleştirilmiş öneriler alacaksınız</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Uygunsuz manga önerme</string>\n    <string name=\"enabled\">Etkin</string>\n    <string name=\"disabled\">Devre dışı</string>\n    <string name=\"reset_filter\">Filtreyi sıfırla</string>\n    <string name=\"onboard_text\">Manga okumak istediğiniz dilleri seçin. Daha sonra ayarlardan değiştirebilirsiniz.</string>\n    <string name=\"always\">Her zaman</string>\n    <string name=\"never\">Hiçbir zaman</string>\n    <string name=\"only_using_wifi\">Yalnızca Wi-Fi\\'de</string>\n    <string name=\"preload_pages\">Sayfaları önceden yükle</string>\n    <string name=\"logged_in_as\">%s olarak oturum açıldı</string>\n    <string name=\"nsfw\">+18</string>\n    <string name=\"various_languages\">Çeşitli diller</string>\n    <string name=\"search_chapters\">Bölüm bul</string>\n    <string name=\"chapters_empty\">Bu mangada bölüm yok</string>\n    <string name=\"percent_string_pattern\">%%%1$s</string>\n    <string name=\"suggestions_updating\">Öneriler güncelleniyor</string>\n    <string name=\"appearance\">Görünüm</string>\n    <string name=\"suggestions_excluded_genres\">Türleri hariç tut</string>\n    <string name=\"suggestions_excluded_genres_summary\">Önerilerde görmek istemediğiniz türleri belirtin</string>\n    <string name=\"text_delete_local_manga_batch\">Seçilen ögeler aygıttan kalıcı olarak silinsin mi\\?</string>\n    <string name=\"removal_completed\">Kaldırma tamamlandı</string>\n    <string name=\"chapters_will_removed_background\">Bölümler arka planda kaldırılacak</string>\n    <string name=\"download_slowdown\">İndirme yavaşlatma</string>\n    <string name=\"download_slowdown_summary\">IP adresinizin engellenmesinden kaçınmanıza yardımcı olur</string>\n    <string name=\"local_manga_processing\">Kaydedilen manga işleme</string>\n    <string name=\"hide\">Gizle</string>\n    <string name=\"new_sources_text\">Yeni manga kaynakları var</string>\n    <string name=\"show_notification_new_chapters_off\">Bildirim almayacaksınız ancak yeni bölümler listelerde vurgulanacak</string>\n    <string name=\"notifications_enable\">Bildirimleri etkinleştir</string>\n    <string name=\"check_new_chapters_title\">Yeni bölümleri kontrol et ve bildirim gönder</string>\n    <string name=\"show_notification_new_chapters_on\">Okuduğunuz manga güncellemeleri hakkında bildirim alacaksınız</string>\n    <string name=\"empty_favourite_categories\">Favori kategori yok</string>\n    <string name=\"name\">Ad</string>\n    <string name=\"edit\">Düzenle</string>\n    <string name=\"edit_category\">Kategoriyi düzenle</string>\n    <string name=\"bookmark_add\">Yer imi ekle</string>\n    <string name=\"bookmark_remove\">Yer imini kaldır</string>\n    <string name=\"bookmarks\">Yer imleri</string>\n    <string name=\"bookmark_removed\">Yer imi kaldırıldı</string>\n    <string name=\"bookmark_added\">Yer imi eklendi</string>\n    <string name=\"undo\">Geri al</string>\n    <string name=\"removed_from_history\">Geçmişten kaldırıldı</string>\n    <string name=\"dns_over_https\">HTTPS üzerinden DNS</string>\n    <string name=\"detect_reader_mode\">Okuyucu modunu otomatik algıla</string>\n    <string name=\"detect_reader_mode_summary\">Manganın webtoon olup olmadığını otomatik olarak algıla</string>\n    <string name=\"default_mode\">Varsayılan mod</string>\n    <string name=\"disable_battery_optimization\">Pil iyileştirmesini devre dışı bırak</string>\n    <string name=\"disable_battery_optimization_summary\">Arka planda güncelleme kontrollerine yardımcı olur</string>\n    <string name=\"crash_text\">Bir şeyler yanlış gitti. Düzeltmemize yardımcı olması için lütfen geliştiricilere bir hata bildirimi gönderin.</string>\n    <string name=\"send\">Gönder</string>\n    <string name=\"disable_all\">Tümünü devre dışı bırak</string>\n    <string name=\"use_fingerprint\">Varsa biyometrik kullan</string>\n    <string name=\"appwidget_shelf_description\">Favorilerinizden mangalar</string>\n    <string name=\"appwidget_recent_description\">Son okuduğunuz mangalar</string>\n    <string name=\"report\">Bildir</string>\n    <string name=\"tracking\">İzleme</string>\n    <string name=\"logout\">Oturumu kapat</string>\n    <string name=\"status_reading\">Okunuyor</string>\n    <string name=\"status_completed\">Tamamlandı</string>\n    <string name=\"show_reading_indicators\">Okuma ilerleme göstergelerini göster</string>\n    <string name=\"data_deletion\">Verileri sil</string>\n    <string name=\"show_reading_indicators_summary\">Geçmişte ve favorilerde okunma yüzdesini göster</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Uygunsuz olarak işaretlenen mangalar asla geçmişe eklenmeyecek ve ilerlemeniz kaydedilmeyecektir</string>\n    <string name=\"clear_cookies_summary\">Bazı sorunlarda yardımcı olabilir. Tüm yetkilendirmeler geçersiz kılınacaktır</string>\n    <string name=\"status_on_hold\">Beklemede</string>\n    <string name=\"status_dropped\">Bırakıldı</string>\n    <string name=\"status_planned\">Planlandı</string>\n    <string name=\"status_re_reading\">Yeniden okunuyor</string>\n    <string name=\"show_all\">Tümünü göster</string>\n    <string name=\"invalid_domain_message\">Geçersiz etki alanı</string>\n    <string name=\"select_range\">Aralık seç</string>\n    <string name=\"not_found_404\">İçerik bulunamadı veya kaldırıldı</string>\n    <string name=\"canceled\">İptal edilmiş</string>\n    <string name=\"account_already_exists\">Hesap zaten var</string>\n    <string name=\"back\">Geri</string>\n    <string name=\"sync\">Eşitleme</string>\n    <string name=\"sync_title\">Verinizi eşitleyin</string>\n    <string name=\"email_enter_hint\">Devam etmek için E-Postanızı girin</string>\n    <string name=\"clear_all_history\">Tüm gecmişi temizle</string>\n    <string name=\"last_2_hours\">Son 2 saat</string>\n    <string name=\"history_cleared\">Geçmiş temizlendi</string>\n    <string name=\"manage\">Yönet</string>\n    <string name=\"no_bookmarks_yet\">Yer işareti yok</string>\n    <string name=\"no_bookmarks_summary\">Manga okurken yer imi oluşturabilirsiniz</string>\n    <string name=\"bookmarks_removed\">Yer işaretleri kaldırıldı</string>\n    <string name=\"no_manga_sources\">Manga kaynağı yok</string>\n    <string name=\"no_manga_sources_text\">Çevrim içi manga okumak için manga kaynaklarını aktif edin</string>\n    <string name=\"random\">Rastgele</string>\n    <string name=\"empty\">Boş</string>\n    <string name=\"explore\">Keşfet</string>\n    <string name=\"confirm_exit\">Çıkmak için tekrar Geri tıkla</string>\n    <string name=\"exit_confirmation_summary\">Çıkmak için iki defa Geri tıkla</string>\n    <string name=\"removed_from_favourites\">Favorilerden kaldırıldı</string>\n    <string name=\"exit_confirmation\">Çıkış doğrulaması</string>\n    <string name=\"comics_archive\">Çizgi roman arşivi</string>\n    <string name=\"manga_error_description_pattern\">Hata ayrıntıları:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Kaynağında bulunduğundan emin olmak için &lt;a href=%2$s&gt;mangayı bir web tarayıcısında açmayı&lt;/a&gt; deneyin&lt;br&gt;2. &lt;a href=kotatsu://about&gt;Kotatsu\\'nun en son sürümünü&lt;/a&gt;&lt;br&gt;3 kullandığınızdan emin olun. Varsa, geliştiricilere bir hata raporu gönderin.</string>\n    <string name=\"categories_delete_confirm\">Seçilen favori kategorileri silmek istediğinizden emin misiniz? \\nİçindeki tüm mangalar kaybolur ve bu işlem geri alınamaz.</string>\n    <string name=\"reorder\">Yeniden sırala</string>\n    <string name=\"pages_cache\">Sayfa önbelleği</string>\n    <string name=\"other_cache\">Diğer önbellekler</string>\n    <string name=\"storage_usage\">Depolama kullanımı</string>\n    <string name=\"available\">Mevcut</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"options\">Seçenekler</string>\n    <string name=\"incognito_mode\">Gizli mod</string>\n    <string name=\"no_chapters\">Bölüm yok</string>\n    <string name=\"automatic_scroll\">Otomatik kaydır</string>\n    <string name=\"reader_info_pattern\">Böl. %1$d/%2$d Sayf. %3$d/%4$d</string>\n    <string name=\"reader_info_bar\">Okuyucuda bilgi çubuğu göster</string>\n    <string name=\"folder_with_images\">Resimlerle klasör</string>\n    <string name=\"importing_manga\">Manga içe aktarılıyor</string>\n    <string name=\"import_completed\">İçe aktarım tamamlandı</string>\n    <string name=\"import_completed_hint\">Yer açmak için orijinal dosyayı depolamadan silebilirsiniz</string>\n    <string name=\"import_will_start_soon\">İçe aktarım birazdan başlayacak</string>\n    <string name=\"feed\">Akış</string>\n    <string name=\"history_shortcuts\">En son manga kısayollarını göster</string>\n    <string name=\"reader_control_ltr\">Ergonomik okuyucu denetimi</string>\n    <string name=\"color_correction\">Renk düzeltme</string>\n    <string name=\"brightness\">Parlaklık</string>\n    <string name=\"contrast\">Kontrast</string>\n    <string name=\"reset\">Sıfırla</string>\n    <string name=\"text_unsaved_changes_prompt\">Kaydedilmeyen değişiklikler kaydedilsin mi yoksa yok mu sayılsın?</string>\n    <string name=\"discard\">Yoksay</string>\n    <string name=\"error_no_space_left\">Cihazda yer yok</string>\n    <string name=\"webtoon_zoom\">Webtoon yakınlaştırma</string>\n    <string name=\"reader_slider\">Sayfa değiştirme kaydırıcısını göster</string>\n    <string name=\"clear_new_chapters_counters\">Ayrıca yeni bölümler hakkındaki bilgileri temizle</string>\n    <string name=\"compact\">Sıkı</string>\n    <string name=\"network_unavailable\">Ağ kullanılamıyor</string>\n    <string name=\"network_unavailable_hint\">Çevrim içi manga okumak için Wi-Fi veya mobil ağı açın</string>\n    <string name=\"server_error\">Sunucu hatası (%1$d). Lütfen daha sonra tekrar deneyin</string>\n    <string name=\"saved_manga\">Kaydedilen mangalar</string>\n    <string name=\"history_shortcuts_summary\">Uygulama simgesine uzun basarak son mangaları kullanılabilir hale getirin</string>\n    <string name=\"reader_control_ltr_summary\">Sayfa değiştirme yönü okuyucu moduna göre ayarlanmasın, örn. sağ tuşa basıldığında her zaman bir sonraki sayfaya geçilir. Bu seçenek yalnızca donanımsal giriş aygıtlarını etkiler</string>\n    <string name=\"source_disabled\">Kaynak devre dışı</string>\n    <string name=\"prefetch_content\">İçerik ön yüklemesi</string>\n    <string name=\"mark_as_current\">Geçerli olarak işaretle</string>\n    <string name=\"language\">Dil</string>\n    <string name=\"share_logs\">Günlükleri paylaş</string>\n    <string name=\"enable_logging\">Günlük kaydını etkinleştir</string>\n    <string name=\"enable_logging_summary\">Hata ayıklama amacıyla bazı eylemleri kaydedin. Ne yaptığınızdan emin değilseniz açmayın</string>\n    <string name=\"show_suspicious_content\">Şüpheli içeriği göster</string>\n    <string name=\"services\">Hizmetler</string>\n    <string name=\"scrobbling_empty_hint\">Okuma ilerlemesini izlemek için manga ayrıntıları ekranında Menü → İzle\\'yi seçin.</string>\n    <string name=\"nothing_here\">burada hiçbir şey yok</string>\n    <string name=\"theme_name_dynamic\">Dinamik</string>\n    <string name=\"color_theme\">Renk teması</string>\n    <string name=\"show_in_grid_view\">Izgara görünümünde göster</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"user_agent\">UserAgent başlığı</string>\n    <string name=\"allow_unstable_updates_summary\">Kararsız versiyonlar hakkında bildirim alın</string>\n    <string name=\"allow_unstable_updates\">Kararsız güncellemelere izin ver</string>\n    <string name=\"download_started\">İndirme başladı</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"settings_apply_restart_required\">Bu değişiklikleri uygulamak için lütfen uygulamayı yeniden başlatın</string>\n    <string name=\"find_similar\">Benzerini bul</string>\n    <string name=\"web_view_unavailable\">WebView kullanılamıyor: WebView sağlayıcısının kurulu olup olmadığını kontrol edin</string>\n    <string name=\"enable\">Etkinleştir</string>\n    <string name=\"no_thanks\">Hayır teşekkürler</string>\n    <string name=\"clear_network_cache\">İnternet geçmişini temizle</string>\n    <string name=\"sync_settings\">Eşitleme ayarları</string>\n    <string name=\"server_address\">Sunucu adresi</string>\n    <string name=\"sync_host_description\">Kendi eşitleme sunucunuzu veya varsayılan bir sunucuyu kullanabilirsiniz. Ne yaptığınızdan emin değilseniz bunu değiştirmeyin.</string>\n    <string name=\"mirror_switching_summary\">Hata durumlarında manga kaynakları için yedek bağlantılar mevcutsa otomatik olarak bu bağlantıları kullan</string>\n    <string name=\"downloads_wifi_only_summary\">Mobil ağa geçerken indirmeyi durdur</string>\n    <string name=\"remove_completed\">Bitirilenleri kaldır</string>\n    <string name=\"cancel_all\">Hepsini iptal et</string>\n    <string name=\"downloads_wifi_only\">Sadece Wi-Fi ile indir</string>\n    <string name=\"got_it\">Anladım</string>\n    <string name=\"sources_reorder_tip\">Yeniden sıralamak için bir öğeye dokunun ve basılı tutun</string>\n    <string name=\"comics_archive_import_description\">Bir veya daha fazla .cbz veya .zip dosyası seçebilirsiniz, her dosya ayrı bir manga olarak tanınacaktır.</string>\n    <string name=\"folder_with_images_import_description\">Arşivler veya resimler içeren bir konum seçebilirsiniz. Her arşiv (veya alt konum) bir bölüm olarak tanınacaktır.</string>\n    <string name=\"speed\">Hız</string>\n    <string name=\"show_on_shelf\">Rafta Göster</string>\n    <string name=\"sync_auth_hint\">Mevcut bir hesapta oturum açabilir veya yeni bir hesap oluşturabilirsiniz</string>\n    <string name=\"ignore_ssl_errors\">SSL hatalarını görmezden gel</string>\n    <string name=\"pause\">Durdur</string>\n    <string name=\"resume\">Devam et</string>\n    <string name=\"paused\">Durduruldu</string>\n    <string name=\"suggestion_manga\">Öneri:%s</string>\n    <string name=\"suggestions_notifications_summary\">Bazen manga öneri bildirimlerilerini göster</string>\n    <string name=\"more\">Daha fazla</string>\n    <string name=\"cancel_all_downloads_confirm\">Tüm aktif indirmeler iptal edilecek, kısmen indirilen veriler kaybolacak</string>\n    <string name=\"remove_completed_downloads_confirm\">İndirme geçmişiniz tamamen silinecek. İndirilen dosyalar etkilenmeyecektir</string>\n    <string name=\"text_downloads_list_holder\">Hiçbir indirmeniz yok</string>\n    <string name=\"downloads_resumed\">İndirmeler devam ettirildi</string>\n    <string name=\"downloads_paused\">İndirmeler durduruldu</string>\n    <string name=\"downloads_removed\">İndirmeler silindi</string>\n    <string name=\"mirror_switching\">Yedek bağlantıyı otomatik olarak seç</string>\n    <string name=\"downloads_cancelled\">İndirmeler iptal edildi</string>\n    <string name=\"suggestions_enable_prompt\">Kişiselleştirilmiş manga önerileri almak istiyor musunuz\\?</string>\n    <string name=\"address\">Adres</string>\n    <string name=\"type\">Tür</string>\n    <string name=\"port\">Port</string>\n    <string name=\"proxy\">Vekil sunucu</string>\n    <string name=\"invalid_value_message\">Geçersiz değer</string>\n    <string name=\"invalid_port_number\">Geçersiz port numarası</string>\n    <string name=\"invert_colors\">Renkleri ters çevir</string>\n    <string name=\"images_proxy_title\">Görüntü iyileştirme vekil sunucusu</string>\n    <string name=\"password\">Şifre</string>\n    <string name=\"authorization_optional\">Doğrulama (isteğe bağlı)</string>\n    <string name=\"downloaded\">İndirildi</string>\n    <string name=\"images_procy_description\">Trafik kullanımını azaltmak ve mümkünse resim yüklemeyi hızlandırmak için wsrv.nl hizmetini kullanın</string>\n    <string name=\"username\">Kullanıcı adı</string>\n    <string name=\"network\">Ağ</string>\n    <string name=\"data_and_privacy\">Veri ve gizlilik</string>\n    <string name=\"restore_summary\">Önceden oluşturulmuş yedeği geri yükle</string>\n    <string name=\"webtoon_zoom_summary\">Webtoon modunda yakınlaştırma hareketine izin ver</string>\n    <string name=\"reader_info_bar_summary\">Geçerli saati ve okuma ilerlemesini ekranın üst kısmında gösterin</string>\n    <string name=\"show_pages_numbers_summary\">Sayfa numaralarını alt köşede göster</string>\n    <string name=\"languages\">Diller</string>\n    <string name=\"zoom_in\">Yakınlaştır</string>\n    <string name=\"captcha_required_summary\">Düzgün okunmak için %s captcha gerektiriyor</string>\n    <string name=\"download_option_all_unread\">Tüm okunmamış bölümler</string>\n    <string name=\"progress\">İlerleme</string>\n    <string name=\"error_corrupted_file\">Geçersiz veri alındı veya dosya bozuk</string>\n    <string name=\"pick_custom_directory\">Özel konum seçin</string>\n    <string name=\"related_manga_summary\">İlgili mangaların bir listesini göster. Bazı durumlarda yanlış veya eksik olabilir</string>\n    <string name=\"reader_zoom_buttons_summary\">Sağ alt köşede yakınlaştırma kontrol düğmelerini gösterir</string>\n    <string name=\"tracker_wifi_only_summary\">Tarifeli ağ bağlantılarını kullanarak yeni bölümleri kontrol etmeyin</string>\n    <string name=\"order_added\">Eklendi</string>\n    <string name=\"on_device\">Cihazda</string>\n    <string name=\"download_option_whole_manga\">Bütün manga</string>\n    <string name=\"clear_source_cookies_summary\">Yalnızca belirtilen alan adı için çerezleri temizleyin. Çoğu durumda yetkilendirmeyi geçersiz kılar</string>\n    <string name=\"suggest_new_sources\">Uygulama güncellemesinden sonra yeni kaynaklar önerin</string>\n    <string name=\"moved_to_top\">Üste taşındı</string>\n    <string name=\"data_not_restored_text\">Doğru yedek dosyasını seçtiğinizden emin olun</string>\n    <string name=\"unknown\">Bilinmeyen</string>\n    <string name=\"in_progress\">Devam ediyor</string>\n    <string name=\"download_option_manual_selection\">Bölümleri elle seç</string>\n    <string name=\"enhanced_colors_summary\">Çizgileşmeyi azaltır, ancak performansı etkileyebilir</string>\n    <string name=\"items_limit_exceeded\">Daha fazla öğe eklenemez</string>\n    <string name=\"data_not_restored\">Veri geri yüklemedi</string>\n    <string name=\"directories\">Konumlar</string>\n    <string name=\"local_manga_directories\">Yerel manga konumları</string>\n    <string name=\"manage_categories\">Kategorileri yönet</string>\n    <string name=\"color_light\">Açık</string>\n    <string name=\"search_hint\">Manga başlığı girin, tür veya kaynak adı</string>\n    <string name=\"description\">Açıklama</string>\n    <string name=\"reader_zoom_buttons\">Yakınlaştırma butonlarını göster</string>\n    <string name=\"main_screen_sections\">Ana ekran bölümleri</string>\n    <string name=\"advanced\">Gelişmiş</string>\n    <string name=\"download_option_all_unread_b\">Tüm okunmamış bölümler (%s)</string>\n    <string name=\"color_dark\">Koyu</string>\n    <string name=\"too_many_requests_message\">Çok fazla istek. Daha sonra tekrar deneyin</string>\n    <string name=\"related_manga\">İlgili manga</string>\n    <string name=\"state_abandoned\">Bırakıldı</string>\n    <string name=\"download_option_first_n_chapters\">Önce %s</string>\n    <string name=\"keep_screen_on\">Ekranı açık tut</string>\n    <string name=\"suggestions_wifi_only_summary\">Tarifeli bağlantılarını kullanarak önerileri güncellemeyin</string>\n    <string name=\"enhanced_colors\">32-bit renk modu</string>\n    <string name=\"background\">Arkaplan</string>\n    <string name=\"no_access_to_file\">Bu dosyaya veya konuma erişim izniniz yok</string>\n    <string name=\"zoom_out\">Uzaklaştır</string>\n    <string name=\"keep_screen_on_summary\">Manga okurken ekranı kapatmayın</string>\n    <string name=\"download_option_next_unread_n_chapters\">Sonraki okunmamış %s</string>\n    <string name=\"voice_search\">Sesli arama</string>\n    <string name=\"manga_list\">Manga listesi</string>\n    <string name=\"disable_nsfw\">NSFW\\'yi devre dışı bırak</string>\n    <string name=\"color_white\">Beyaz</string>\n    <string name=\"to_top\">Üste</string>\n    <string name=\"show\">Göster</string>\n    <string name=\"suggest_new_sources_summary\">Uygulamayı güncelledikten sonra yeni eklenen kaynakları etkinleştirmeyi bildir</string>\n    <string name=\"download_option_all_chapters\">%s çevirisi olan tüm bölümler</string>\n    <string name=\"color_black\">Siyah</string>\n    <string name=\"this_month\">Bu ay</string>\n    <string name=\"categories\">Kategoriler</string>\n    <string name=\"list_options\">Seçenekleri listele</string>\n    <string name=\"online_variant\">Çevrim içi türü</string>\n    <string name=\"by_relevance\">İlgi düzeyine göre</string>\n    <string name=\"frequency_every_day\">Her gün</string>\n    <string name=\"backup_frequency\">Yedek oluşturma sıklığı</string>\n    <string name=\"periodic_backups_enable\">Zamanlı yedeklemeleri etkinleştirin</string>\n    <string name=\"frequency_every_2_days\">2 günde 1</string>\n    <string name=\"frequency_once_per_week\">Haftada 1 kez</string>\n    <string name=\"periodic_backups\">Zamanlı yedekleme</string>\n    <string name=\"frequency_twice_per_month\">Ayda 2 kere</string>\n    <string name=\"frequency_once_per_month\">Ayda 1 kere</string>\n    <string name=\"backups_output_directory\">Yedekleme konumu</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"last_successful_backup\">Son başarılı yedekleme: %s</string>\n    <string name=\"sources_catalog\">Kaynak kataloğu</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"catalog\">Katalog</string>\n    <string name=\"reader_optimize\">Bellek kullanımını düşür (beta)</string>\n    <string name=\"manage_sources\">Kaynakları yönet</string>\n    <string name=\"reader_optimize_summary\">Daha az bellek kullanımı için ekran dışı sayfaların çözünürlüğünü düşür</string>\n    <string name=\"source_enabled\">Kaynak etkinleştirildi</string>\n    <string name=\"no_manga_sources_catalog_text\">Bu bölümde kullanılabilir kaynak yok ya da hepsi zaten eklenmiş olabilir.\n\\nTakipte kalın</string>\n    <string name=\"state_paused\">Durduruldu</string>\n    <string name=\"content_type_other\">Diğer</string>\n    <string name=\"error_multiple_states_not_supported\">Birden fazla duruma göre filtreleme bu manga kaynağı tarafından desteklenmemektedir</string>\n    <string name=\"source_summary_pattern\">%1$s,%2$s</string>\n    <string name=\"content_type_comics\">Karikatür</string>\n    <string name=\"no_manga_sources_found\">Sorgunuza göre mevcut manga kaynağı bulunamadı</string>\n    <string name=\"error_multiple_genres_not_supported\">Birden fazla türe göre filtreleme bu manga kaynağı tarafından desteklenmemektedir</string>\n    <string name=\"lock_screen_rotation\">Ekran döndürmeyi kilitle</string>\n    <string name=\"error_search_not_supported\">Arama bu manga kaynağı tarafından desteklenmemektedir</string>\n    <string name=\"manual\">Manuel</string>\n    <string name=\"disable_nsfw_summary\">NSFW kaynakları devre dışı bırak ve yetişkinlere yönelik mangaları listeden gizle</string>\n    <string name=\"available_d\">Mevcut:%1$d</string>\n    <string name=\"state\">Durum</string>\n    <string name=\"error_filter_states_genre_not_supported\">Hem türlere hem de duruma göre filtreleme bu kaynak tarafından desteklenmiyor</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Hem türlere hem de yerel ayara göre filtreleme bu kaynak tarafından desteklenmiyor</string>\n    <string name=\"apply\">Uygula</string>\n    <string name=\"genres_search_hint\">Tür adını yazmaya başlayın</string>\n    <string name=\"globally\">Genel</string>\n    <string name=\"downloads_settings_info\">Sunucu taraflı engelleme ile ilgili sorun yaşıyorsanız, kaynak ayarlarında her manga kaynağı için ayrı ayrı indirme yavaşlatmayı etkinleştirebilirsiniz</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Herhangi bir sorun yaşarsanız indirmeyi başlatmanıza yardımcı olabilir</string>\n    <string name=\"this_manga\">Bu manga</string>\n    <string name=\"skip\">Atla</string>\n    <string name=\"color_correction_apply_text\">Bu ayarlar genel olarak veya yalnızca geçerli mangaya uygulanabilir. Genel olarak uygulanırsa, bireysel ayarlar geçersiz kılınmaz.</string>\n    <string name=\"grayscale\">Gri tonlamalı</string>\n    <string name=\"welcome_text\">Lütfen hangi içerik kaynaklarını etkinleştirmek istediğinizi seçin. Bu daha sonra ayarlardan da yapılandırılabilir</string>\n    <string name=\"sync_auth\">Eşitleme hesabında oturum aç</string>\n    <string name=\"restore\">Geri yükle</string>\n    <string name=\"backup_date_\">Yedekleme tarihi: %s</string>\n    <string name=\"state_upcoming\">Yakında</string>\n    <string name=\"by_name_reverse\">Tersten ad</string>\n    <string name=\"content_rating\">İçerik oylaması</string>\n    <string name=\"genres_exclude\">Türleri hariç bırak</string>\n    <string name=\"rating_safe\">Herkese uygun</string>\n    <string name=\"rating_adult\">Yetişkin</string>\n    <string name=\"rating_suggestive\">Uygunsuz</string>\n    <string name=\"default_tab\">Varsayılan sekme</string>\n    <string name=\"mark_as_completed\">Tamamlandı olarak işaretle</string>\n    <string name=\"mark_as_completed_prompt\">Seçili mangayı tamamlandı olarak işaretle?\n\\n\n\\nUyarı: Şu anki ilerleme kaybolacaktır.</string>\n    <string name=\"category_hidden_done\">Bu kategori ana ekrandan gizlendi ve Menü → Kategorileri yönet aracılığıyla erişilebilir</string>\n    <string name=\"volume_\">%d. cilt</string>\n    <string name=\"volume_unknown\">Bilinmeyen cilt</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"incognito_mode_hint\">Okuma ilerleme durumunuz kaydedilmeyecek</string>\n    <string name=\"vertical\">Dikey</string>\n    <string name=\"last_read\">Son okunan</string>\n    <string name=\"toggle_ui\">Kullanıcı arayüzünü göster/gizle</string>\n    <string name=\"reader_actions\">Okuyucu eylemleri</string>\n    <string name=\"reader_actions_summary\">Dokunulabilir ekran alanları için eylemleri yapılandırın</string>\n    <string name=\"switch_pages_volume_buttons\">Ses düğmelerini etkinleştir</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Sayfa değiştirmek için ses düğmelerini kullanın</string>\n    <string name=\"tap_action\">Dokunma eylemi</string>\n    <string name=\"long_tap_action\">Uzun dokunma eylemi</string>\n    <string name=\"none\">Hiçbiri</string>\n    <string name=\"show_menu\">Menüyü göster</string>\n    <string name=\"prev_chapter\">Önceki bölüm</string>\n    <string name=\"next_chapter\">Sonraki bölüm</string>\n    <string name=\"prev_page\">Önceki sayfa</string>\n    <string name=\"next_page\">Sonraki sayfa</string>\n    <string name=\"config_reset_confirm\">Ayarlar varsayılan değerlere sıfırlansın mı? Bu eylem geri alınamaz.</string>\n    <string name=\"use_two_pages_landscape\">Yatay yönde iki sayfa düzeni kullan (beta)</string>\n    <string name=\"email_password_enter_hint\">Devam etmek için e-posta adresinizi ve parolanızı girin</string>\n    <string name=\"default_webtoon_zoom_out\">Varsayılan webtoon uzaklaştırması</string>\n    <string name=\"fullscreen_mode\">Tam ekran modu</string>\n    <string name=\"reader_fullscreen_summary\">Sistem durumunu ve gezinme çubuklarını gizle</string>\n    <string name=\"suggestions_unavailable_text\">Öneriler özelliği devre dışı</string>\n    <string name=\"check_for_new_chapters_disabled\">Yeni bölümlerin denetlenmesi devre dışı</string>\n    <string name=\"reading_time_estimation\">Tahmini okuma süresini göster</string>\n    <string name=\"reading_time_estimation_summary\">Süre tahmin değeri yanlış olabilir</string>\n    <string name=\"show_labels_in_navbar\">Gezinme çubuğunda etiketleri göster</string>\n    <string name=\"pages_saving\">Sayfalar kaydediliyor</string>\n    <string name=\"ask_for_dest_dir_every_time\">Hedef konumu her zaman sor</string>\n    <string name=\"default_page_save_dir\">Varsayılan sayfa kaydetme konumu</string>\n    <string name=\"remove_from_history\">Geçmişten kaldır</string>\n    <string name=\"location\">Konum</string>\n    <string name=\"automatic\">Otomatik</string>\n    <string name=\"single_cbz_file\">Tek CBZ dosyası</string>\n    <string name=\"preferred_download_format\">Tercih edilen indirme biçimi</string>\n    <string name=\"multiple_cbz_files\">Birden fazla CBZ dosyası</string>\n    <string name=\"other_manga\">Diğer manga</string>\n    <string name=\"week\">Hafta</string>\n    <string name=\"month\">Ay</string>\n    <string name=\"all_time\">Tüm zamanlar</string>\n    <string name=\"day\">Gün</string>\n    <string name=\"three_months\">Üç ay</string>\n    <string name=\"empty_stats_text\">Seçilen dönem için istatistik yok</string>\n    <string name=\"pages_read_s\">Okunan sayfalar: %s</string>\n    <string name=\"clear_stats\">İstatistikleri temizle</string>\n    <string name=\"reading_stats\">Okuma istatistikleri</string>\n    <string name=\"statistics\">İstatistikler</string>\n    <string name=\"less_than_minute\">Bir dakikadan az</string>\n    <string name=\"stats_cleared\">İstatistikler temizlendi</string>\n    <string name=\"clear_stats_confirm\">Gerçekten tüm okuma istatistiklerini temizlemek istiyor musunuz? Bu işlem geri alınamaz.</string>\n    <string name=\"alternatives\">Alternatifler</string>\n    <string name=\"migrate\">Taşı</string>\n    <string name=\"migrate_confirmation\">\\\"%1$s\\\" mangası (\\\"%2$s\\\"den), geçmişinizde ve favorilerinizde (varsa) \\\"%3$s\\\" ile (\\\"%4$s \\\"den) değiştirilecektir</string>\n    <string name=\"manga_migration\">Manga taşıması</string>\n    <string name=\"migration_completed\">Taşıma işlemi tamamlandı</string>\n    <string name=\"delete_read_chapters\">Okunan bölümleri sil</string>\n    <string name=\"no_chapters_deleted\">Hiçbir bölüm silinmedi</string>\n    <string name=\"chapters_deleted_pattern\">%1$s kaldırıldı, %2$s temizlendi</string>\n    <string name=\"delete_read_chapters_summary\">Yer açmak için yerel depolama alanından daha önce okuduğunuz bölümleri silin</string>\n    <string name=\"delete_read_chapters_prompt\">Bu işlem, okundu olarak işaretlenen tüm bölümleri yerel depolama alanınızdan kalıcı olarak silecektir. Daha sonra yeniden indirebilirsiniz, ancak içe aktarılan bölümler sonsuza kadar kaybolabilir</string>\n    <string name=\"chapters_grid_view\">Izgara görünümü</string>\n    <string name=\"delete_read_chapters_auto\">Okunan bölümleri otomatik olarak sil</string>\n    <string name=\"runs_on_app_start\">Uygulama başladığında çalışır</string>\n    <string name=\"split_by_translations\">Çevirilere göre böl</string>\n    <string name=\"split_by_translations_summary\">Farklı çevirilere sahip bölümleri tek bir liste yerine ayrı ayrı göster</string>\n    <string name=\"order_oldest\">En eski</string>\n    <string name=\"long_ago_read\">Uzun zaman önce okundu</string>\n    <string name=\"unread\">Okunmadı</string>\n    <string name=\"unsupported_source\">Bu manga kaynağı desteklenmiyor</string>\n    <string name=\"enable_source\">Kaynağı etkinleştir</string>\n    <string name=\"show_pages_thumbs_summary\">Ayrıntılar ekranında \\\"Sayfalar\\\" sekmesini etkinleştir</string>\n    <string name=\"show_pages_thumbs\">Sayfa küçük resimlerini göster</string>\n    <string name=\"error_no_data_received\">Sunucudan veri alınamadı</string>\n    <string name=\"unsupported_backup_message\">Lütfen uygun bir Kotatsu yedekleme dosyası seçin</string>\n    <string name=\"hours_short\">%d sa</string>\n    <string name=\"minutes_short\">%d dak</string>\n    <string name=\"hours_minutes_short\">%1$d sa %2$d dak</string>\n    <string name=\"fix\">Düzelt</string>\n    <string name=\"missing_storage_permission\">Harici depolama alanındaki mangalara erişim izni yok</string>\n    <string name=\"last_used\">Son kullanılan</string>\n    <string name=\"show_updated\">Güncellenenleri göster</string>\n    <string name=\"webtoon_gaps\">Webtoon modunda boşluklar</string>\n    <string name=\"webtoon_gaps_summary\">Webtoon modunda sayfalar arasında dikey boşluklar göster</string>\n    <string name=\"frequency_of_check\">Denetleme sıklığı</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Gezinme arayüzünü sabitle</string>\n    <string name=\"less_frequently\">Daha seyrek</string>\n    <string name=\"more_frequently\">Daha sık</string>\n    <string name=\"pin_navigation_ui_summary\">Kaydırma sırasında gezinme çubuğu ve arama görünümü gizlenmesin</string>\n    <string name=\"search_suggestions\">Arama önerileri</string>\n    <string name=\"recent_queries\">Son sorgular</string>\n    <string name=\"suggested_queries\">Önerilen sorgular</string>\n    <string name=\"authors\">Yazarlar</string>\n    <string name=\"blocked_by_server_message\">Sunucu tarafından engellendiniz. Farklı bir ağ bağlantısı kullanmayı deneyin (VPN, vekil sunucu, vb.)</string>\n    <string name=\"disable\">Devre dışı bırak</string>\n    <string name=\"sources_disabled\">Kaynaklar devre dışı bırakıldı</string>\n    <string name=\"disable_connectivity_check\">Bağlantı denetimini devre dışı bırak</string>\n    <string name=\"ignore_ssl_errors_summary\">Ağ kaynaklarına erişirken SSL ile ilgili bir sorunla karşılaşmanız durumunda SSL sertifikaları doğrulamasını devre dışı bırakabilirsiniz. Bu durum güvenliğinizi etkileyebilir. Bu ayarı değiştirdikten sonra uygulamanın yeniden başlatılması gerekir.</string>\n    <string name=\"disable_connectivity_check_summary\">Sorun yaşamanız durumunda bağlantı denetimini atlayın (örneğin, ağ bağlı olmasına rağmen çevrim dışı moda geçiş)</string>\n    <string name=\"disable_nsfw_notifications\">Uygunsuz bildirimleri devre dışı bırak</string>\n    <string name=\"disable_nsfw_notifications_summary\">Uygunsuz manga güncellemeleri hakkında bildirim gösterilmesin</string>\n    <string name=\"tracker_debug_info\">Yeni bölümler günlüğü denetleniyor</string>\n    <string name=\"tracker_debug_info_summary\">Yeni bölümler için arka plan denetimleri hakkında hata ayıklama bilgileri</string>\n    <string name=\"_new\">Yeni</string>\n    <string name=\"all_languages\">Tüm diller</string>\n    <string name=\"screenshots_block_incognito\">Gizli moddayken engelle</string>\n    <string name=\"image_server\">Tercih edilen resim sunucusu</string>\n    <string name=\"crop_pages\">Sayfaları kırp</string>\n    <string name=\"pin\">Sabitle</string>\n    <string name=\"unpin\">Sabitlemeyi kaldır</string>\n    <string name=\"source_pinned\">Kaynak sabitlendi</string>\n    <string name=\"source_unpinned\">Kaynağın sabitlenmesi kaldırıldı</string>\n    <string name=\"sources_unpinned\">Kaynakların sabitlenmesi kaldırıldı</string>\n    <string name=\"sources_pinned\">Kaynaklar sabitlendi</string>\n    <string name=\"recent_sources\">Son kaynaklar</string>\n    <string name=\"percent_read\">Okunan yüzde</string>\n    <string name=\"percent_left\">Kalan yüzde</string>\n    <string name=\"chapters_read\">Okunan bölüm</string>\n    <string name=\"chapters_left\">Kalan bölüm</string>\n    <string name=\"external_source\">Harici/eklenti</string>\n    <string name=\"plugin_incompatible\">Uyumsuz eklenti veya dahili hata. Eklentinin ve Kotatsu\\'nun en son sürümünü kullandığınızdan emin olun</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Seçtiğiniz filtrelerle eşleşen manga yok</string>\n    <string name=\"connection_ok\">Bağlantı tamam</string>\n    <string name=\"invalid_proxy_configuration\">Geçersiz vekil sunucu yapılandırması</string>\n    <string name=\"show_quick_filters\">Hızlı filtreleri göster</string>\n    <string name=\"show_quick_filters_summary\">Manga listelerini belirli değerlere göre filtreleme olanağı sağlar</string>\n    <string name=\"invalid_server_address_message\">Geçersiz sunucu adresi</string>\n    <string name=\"sfw\">Uygun</string>\n    <string name=\"minutes_seconds_short\">%1$d dak %2$d sn</string>\n    <string name=\"stuck\">Takıldı</string>\n    <string name=\"retry\">Yeniden dene</string>\n    <string name=\"seconds_short\">%d sn</string>\n    <string name=\"too_many_requests_message_retry\">Çok fazla istek. %s sonra tekrar deneyin</string>\n    <string name=\"skip_all\">Tümünü atla</string>\n    <string name=\"not_in_favorites\">Favorilerde yok</string>\n    <string name=\"unpopular\">Popüler değil</string>\n    <string name=\"low_rating\">Düşük puan</string>\n    <string name=\"updated_long_ago\">Uzun zaman önce güncellendi</string>\n    <string name=\"sort_order_asc\">Artan</string>\n    <string name=\"by_date\">Tarih</string>\n    <string name=\"sort_order_desc\">Azalan</string>\n    <string name=\"popularity\">Popülerlik</string>\n    <string name=\"scrobbler_auth_required\">Devam etmek için %s\\'de oturum açın</string>\n    <string name=\"scrobbler_auth_intro\">%s ile bütünleşmeyi ayarlamak için oturum açın. Bu, manga okuma ilerlemenizi ve durumunuzu izlemenizi sağlayacaktır</string>\n    <string name=\"unstable_feature\">Kararsız özellik</string>\n    <string name=\"unstable_feature_summary\">Bu işlev deneyseldir. Veri kaybını önlemek için lütfen yedeğiniz olduğundan emin olun</string>\n    <string name=\"downloads_background\">Arka plan indirmeleri</string>\n    <string name=\"download_new_chapters\">Yeni bölümleri indir</string>\n    <string name=\"manga_with_downloaded_chapters\">İndirilen bölümleri olan mangalar</string>\n    <string name=\"manga_replaced\">\\\"%1$s\\\" (%2$s) mangası \\\"%3$s\\\" (%4$s) ile değiştirildi</string>\n    <string name=\"fixing_manga\">Manga düzeltiliyor</string>\n    <string name=\"fixed\">Başarıyla düzeltildi</string>\n    <string name=\"no_fix_required\">\\\"%s\\\" için düzeltme gerekli değil</string>\n    <string name=\"no_alternatives_found\">\\\"%s\\\" için alternatif bulunamadı</string>\n    <string name=\"manga_fix_prompt\">Bu işlev seçilen manga için alternatif kaynaklar bulacaktır. Görev biraz zaman alacak ve arka planda çalışacaktır</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"recently_added\">Son eklenenler</string>\n    <string name=\"added_long_ago\">Uzun zaman önce eklenenler</string>\n    <string name=\"popular_in_hour\">Bu saatte popüler</string>\n    <string name=\"popular_in_week\">Bu hafta popüler</string>\n    <string name=\"popular_in_month\">Bu ay popüler</string>\n    <string name=\"popular_in_year\">Bu yıl popüler</string>\n    <string name=\"original_language\">Orijinal dili</string>\n    <string name=\"year\">Yıl</string>\n    <string name=\"popular_today\">Bugün popüler</string>\n    <string name=\"content_type_novel\">Roman</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"demographics\">Hedef kitle</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"years\">Yıl</string>\n    <string name=\"any\">Hepsi</string>\n    <string name=\"filter_search_warning\">Bu kaynak filtrelerle aramayı desteklemiyor. Filtreleriniz temizlendi</string>\n    <string name=\"demographic_kodomo\">Kodomo</string>\n    <string name=\"content_type_one_shot\">Bir kerelik</string>\n    <string name=\"content_type_image_set\">Resim kümesi</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"content_type_artist_cg\">Sanatçı CG</string>\n    <string name=\"content_type_game_cg\">Oyun CG</string>\n    <string name=\"debug\">Hata ayıkla</string>\n    <string name=\"user_manual\">Kullanıcı kılavuzu</string>\n    <string name=\"source_code\">Kaynak kodu</string>\n    <string name=\"telegram_group\">Telegram grubu</string>\n    <string name=\"error_image_format\">Desteklenmeyen resim biçimi: %s</string>\n    <string name=\"start_download\">İndirmeye başla</string>\n    <string name=\"save_manga_confirm\">Seçilen manga kaydedilsin mi? Bu veri kullanabilir ve disk alanı tüketebilir</string>\n    <string name=\"save_manga\">Mangayı kaydet</string>\n    <string name=\"genre\">Tür</string>\n    <string name=\"download_added\">İndirme eklendi</string>\n    <string name=\"more_options\">Daha fazla seçenek</string>\n    <string name=\"destination_directory\">Hedef konum</string>\n    <string name=\"chapter_selection_hint\">Bölüm listesindeki bir ögeye uzun tıklayarak indirilecek bölümleri seçebilirsiniz.</string>\n    <string name=\"chapters_all\">Tümü</string>\n    <string name=\"download_over_cellular\">Hücresel ağ üzerinden indirme</string>\n    <string name=\"dont_allow\">İzin verilmesin</string>\n    <string name=\"download_cellular_confirm\">Hücresel ağ üzerinden indirmeye izin verilsin mi?</string>\n    <string name=\"allow_always\">Her zaman izin verilsin</string>\n    <string name=\"allow_once\">Bir kez izin verilsin</string>\n    <string name=\"ask_every_time\">Her zaman sorulsun</string>\n    <string name=\"screen_orientation\">Ekran yönü</string>\n    <string name=\"portrait\">Dikey</string>\n    <string name=\"landscape\">Yatay</string>\n    <string name=\"plugin_incompatible_with_cause\">Eklenti hatası: %s\\n Eklentinin ve Kotatsu\\'nun son sürümünü kullandığınızdan emin olun</string>\n    <string name=\"error_not_image\">Geçersiz biçim: görsel yerine %s alındı</string>\n    <string name=\"access_denied_403\">Erişim reddedildi (403)</string>\n    <string name=\"max_backups_count\">Maks yedek sayısı</string>\n    <string name=\"pages_saved\">Sayfalar kaydedildi</string>\n    <string name=\"delete_old_backups\">Eski yedekleri sil</string>\n    <string name=\"delete_old_backups_summary\">Depolama kullanımını azaltmak için eski yedek dosyalarını otomatik olarak sil</string>\n    <string name=\"email\">E-posta</string>\n    <string name=\"handle_links\">Bağlantıları aç</string>\n    <string name=\"handle_links_summary\">Harici uygulamalardaki (örn. web tarayıcısı) manga bağlantılarını uygulamada açın. Uygulamanın sistem ayarlarından bunu aktifleştirmeniz gerekebilir</string>\n    <string name=\"captcha_required_message\">Bu kaynağın kullanılabilmesi için bir captcha çözülmesi gerekiyor</string>\n    <string name=\"author\">Yazar</string>\n    <string name=\"rating\">Puan</string>\n    <string name=\"source\">Kaynak</string>\n    <string name=\"translation\">Çeviri</string>\n    <string name=\"show_slider\">Kaydırıcıyı göster</string>\n    <string name=\"incognito\">Gizli Mod</string>\n    <string name=\"error_connection_reset\">Bağlantı ana bilgisayar tarafından sıfırlandı</string>\n    <string name=\"backup_tg_echo\">Mesaj testi</string>\n    <string name=\"telegram_chat_id\">Telegram sohbet ID\\'si</string>\n    <string name=\"open_telegram_bot\">Telegram botunu aç</string>\n    <string name=\"clear_database\">Veri tabanını temizle</string>\n    <string name=\"clear_database_summary\">Kullanılmayan mangalar ile ilgili bilgileri silin</string>\n    <string name=\"backup_tg_check\">API durumunu kontrol et</string>\n    <string name=\"backup_tg_id_not_set\">Sohbet ID\\'si ayarlanmadı</string>\n    <string name=\"send_backups_telegram\">Yedekleri Telegram\\'a gönder</string>\n    <string name=\"test_connection\">Bağlantıyı test et</string>\n    <string name=\"telegram_chat_id_summary\">Yedeklerin gönderileceği sohbetin ID\\'sini girin</string>\n    <string name=\"open_telegram_bot_summary\">Kotatsu Yedek Botu ile sohbeti açmak için dokunun</string>\n    <string name=\"enable_all_sources\">Bütün manga kaynaklarını etkinleştir</string>\n    <string name=\"enable_all_sources_summary\">Bütün kullanılabilir manga kaynakları kalıcı olarak etkinleştirilecektir</string>\n    <string name=\"all_sources_enabled\">Bütün kaynaklar etkinleştirildi</string>\n    <string name=\"reader_info_bar_transparent\">Şeffaf bilgi çubuğu</string>\n    <string name=\"backup_restored_background\">Yedek arka planda geri yüklenecektir</string>\n    <string name=\"restoring_backup\">Yedek geri yükleniyor</string>\n    <string name=\"pages_slider\">Sayfa değiştirme kaydırıcısı</string>\n    <string name=\"screen_rotation_locked\">Ekran yönü kilitlendi</string>\n    <string name=\"screen_rotation_unlocked\">Ekran yön kilidi açıldı</string>\n    <string name=\"reader_controls_in_bottom_bar\">Alt çubukta okuyucu kontrolleri</string>\n    <string name=\"chapters_and_pages\">Bölümler ve sayfalar</string>\n    <string name=\"simple\">Basit</string>\n    <string name=\"global_search\">Genel arama</string>\n    <string name=\"search_everywhere\">Her yerde ara</string>\n    <string name=\"badges_in_lists\">Listelerde rozetler</string>\n    <string name=\"disable_captcha_notifications\">Captcha bildirimlerini kapat</string>\n    <string name=\"disable_captcha_notifications_summary\">Bu kaynak için CAPTCHA doğrulaması bildirimleri almayacaksınız ancak bu, arka plan işlemlerinin çalışmamasına neden olabilir (yeni bölümleri kontrol etme, önerileri alma vb.)</string>\n    <string name=\"chapter_volume_number\">Cilt %1$s Bölüm %2$s</string>\n    <string name=\"chapter_number\">Bölüm %s</string>\n    <string name=\"unnamed_chapter\">İsimsiz bölüm</string>\n    <string name=\"search_disabled_sources\">Devre dışı bırakılmış kaynaklarda ara</string>\n    <string name=\"error_details\">Hata ayrıntıları</string>\n    <string name=\"error_disclaimer_app_outdated\">Kullandığınız Kotatsu versiyonu güncel değil gibi görünüyor. Bütün hata düzeltmelerini almak için lütfen en son sürümü indirin.</string>\n    <string name=\"error_disclaimer_manga\">Manganın kaynakta var olduğundan emin olmak için bir tarayıcıda açmayı deneyin.</string>\n    <string name=\"error_disclaimer_report\">Geliştiricilere bir hata bildiriminde bulunabilirsiniz. Sorunu araştırmamıza ve çözmemize yardımcı olacaktır.</string>\n    <string name=\"link_to_manga_on_s\">%s üzerinde manganın bağlantısı</string>\n    <string name=\"link_to_manga_in_app\">Kotatsu üzerinde manganın bağlantısı</string>\n    <string name=\"clear_browser_data\">Tarayıcı geçmişini temizle</string>\n    <string name=\"clear_browser_data_summary\">Önbellek ve çerezler gibi tarayıcı verilerini temizleyin. Uyarı: Manga kaynakları için yapılan doğrulamalar geçersiz kalabilir</string>\n    <string name=\"no_write_permission_to_file\">Dosya yazma izni yok</string>\n    <string name=\"suggestions_disabled_sources_summary\">Tüm manga kaynaklarından önerileri göster, devre dışı olanlar dahil</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Yetişkin mangaları önerilerde gösterilmeyecektir. Bu seçenek her kaynak için doğru çalışmayabilir</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"include_disabled_sources\">Devre dışı bırakılmış kaynakları dahil et</string>\n    <string name=\"tags_warnings\">Riskli türleri işaretle</string>\n    <string name=\"tags_warnings_summary\">Çoğu kullanıcılar için uygunsuz olabilecek türleri işaretle</string>\n    <string name=\"error_non_file_uri\">Seçilen yol herhangi bir dosyaya ya da konuma ulaşmadığı için kullanılamıyor</string>\n    <string name=\"use_default_cover\">Varsayılan kapağı kullan</string>\n    <string name=\"pick_custom_file\">Özel dosya seç</string>\n    <string name=\"change_cover\">Kapağı değiştir</string>\n    <string name=\"pick_manga_page\">Manga sayfası seç</string>\n    <string name=\"manga_override_hint\">Bu değişiklikler mangaların uygulamada nasıl göründüğünü etkileyecektir</string>\n    <string name=\"page_switch_timer\">Sayfa ~%d saniyede bir değişecektir</string>\n    <string name=\"dont_ask_again\">Tekrar sorma</string>\n    <string name=\"incognito_mode_hint_nsfw\">Bu manga yetişkin içeriği içerebilir. Gizli modu kullanmak ister misiniz?</string>\n    <string name=\"incognito_for_nsfw\">NSFW mangaları için gizli mod</string>\n    <string name=\"additional_action_required\">Ek işlemler gerekli</string>\n    <string name=\"theme_name_expressive\">İfadesel (Test)</string>\n    <string name=\"hide_from_main_screen\">Ana ekrandan gizle</string>\n    <string name=\"changelog\">Değişim kaydı</string>\n    <string name=\"changelog_summary\">Yeni yayınlanan versiyonlar için değişikliklerin kaydı</string>\n    <string name=\"collapse\">Küçült</string>\n    <string name=\"expand\">Genişlet</string>\n    <string name=\"adblock\">Tarayıcıda reklamları engelle</string>\n    <string name=\"adblock_summary\">Dahili tarayıcıda reklamları engelle (beta)</string>\n    <string name=\"collapse_long_description\">Uzun açıklamayı küçült</string>\n    <string name=\"creating_backup\">Yedek oluşturuluyor</string>\n    <string name=\"share_backup\">Yedeği paylaş</string>\n    <string name=\"reader_multitask\">Okuyucuyu başka bir görevde aç</string>\n    <string name=\"reader_multitask_summary\">Aynı anda farklı okuyucularda farklı mangaları açmanızı sağlar</string>\n    <string name=\"theme_name_itsuka\">İtsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"reader_navigation_inverted\">Navigasyon kontrollerini ters çevir</string>\n    <string name=\"reader_navigation_inverted_summary\">Ses tuşlarının yönlerini değiştir ve donanımsal yön tuşlarını yer değiştir (sol/yukarı/aşağı/sağ)</string>\n    <string name=\"book_effect\">Sarımsı arkaplan (mavi filtre)</string>\n    <string name=\"local_storage_cleanup\">Yerel depolama temizliği</string>\n    <string name=\"packup_creation_failed\">Yedek oluşturma başarısız oldu</string>\n    <string name=\"main_screen\">Ana ekran</string>\n    <string name=\"main_screen_fab\">Yüzen Devam Et butonunu göster</string>\n    <string name=\"main_screen_fab_summary\">Tek bir tıklamayla okumaya devam ettirir. Bu buton gizli moddayken veya geçmiş açık değilken gözükmeyecek</string>\n    <string name=\"error_corrupted_zip\">Bozuk ZIP arşivi (%s)</string>\n    <string name=\"discord_rpc\">Discord Etkinlik Durumu</string>\n    <string name=\"discord_token\">Discord Token</string>\n    <string name=\"discord_token_summary\">Etkinlik Durumu\\'nu kullanabilmek için Discord Token\\'inizi girin</string>\n    <string name=\"discord_token_description\">Discord Token\\'inizi girin ya da Token\\'i tarayıcıdan almak için buraya tıklayın: %s</string>\n    <string name=\"discord_token_hint\">Discord Token\\'inizi buraya yapıştırın</string>\n    <string name=\"discord_rpc_summary\">Okuma durumunu Discord\\'da göster</string>\n    <string name=\"obtain\">Al</string>\n    <string name=\"discord_rpc_description\">Kotatsu\\'da manga okuyor - manga okumak için bir uygulama</string>\n    <string name=\"reading_s\">%s okuyor</string>\n    <string name=\"read_on_s\">%s üzerinde oku</string>\n    <string name=\"rpc_skip_nsfw_summary\">Yetişkin içerik için Etkinlik Durumu\\'nu kullanma</string>\n    <string name=\"invalid_token\">Geçersiz Token: %s</string>\n    <string name=\"show_floating_control_button\">Kayan kontrol butonunu göster</string>\n    <string name=\"unavailable\">Mevcut değil</string>\n    <string name=\"manga_restricted_description\">Bu manga, okumak için bu kaynakta mevcut değil. Başka kaynaklarda aramayı ya da daha fazla bilgi için tarayıcıda açmayı deneyin</string>\n    <string name=\"no_chapters_in_manga\">Bu manga bölüm içermiyor</string>\n    <string name=\"chapters_load_failed\">Bölüm listesi yüklenemedi</string>\n    <string name=\"telegram_integration\">Telegram entegrasyonu</string>\n    <string name=\"test_parser\">Manga kaynağını test et</string>\n    <string name=\"pull_to_prev_chapter\">Önceki bölümü açmak için bırakın</string>\n    <string name=\"pull_to_next_chapter\">Sıradaki bölümü açmak için bırakın</string>\n    <string name=\"pull_top_no_prev\">Önceki bölüm yok</string>\n    <string name=\"pull_bottom_no_next\">Sonraki bölüm yok</string>\n    <string name=\"enable_pull_gesture_title\">Çekme hareketini etkinleştir</string>\n    <string name=\"enable_pull_gesture_summary\">Webtoon modunda bölüm değiştirmek için çekme hareketi kullan</string>\n    <string name=\"two_page_scroll_sensitivity\">İki Sayfa Kaydırma Hassaslığı</string>\n    <string name=\"saved_filters\">Kaydedilen filtreler</string>\n    <string name=\"enter_name\">Ad girin</string>\n    <string name=\"reader_chapter_toast\">Bölüm değişikliği açılır penceresini göster</string>\n    <string name=\"reader_chapter_toast_summary\">Değiştirildiğinde bölüm başlığını içeren bir açılır pencere mesajı göster</string>\n    <string name=\"rename\">Yeniden adlandır</string>\n    <string name=\"save_filter\">Filtreyi kaydet</string>\n    <string name=\"overwrite\">Üzerine yaz</string>\n    <string name=\"filter_overwrite_confirm\">\\\"%s\\\" adında bir filtre zaten var. Üzerine yazmak istiyor musunuz?</string>\n    <string name=\"storage_and_network\">Depolama ve ağ</string>\n    <string name=\"create_or_restore_backup\">Yedekleme oluştur veya geri yükle</string>\n    <string name=\"data_removal\">Verilerin silinmesi</string>\n    <string name=\"privacy\">Gizlilik</string>\n    <string name=\"source_broken_warning\">Bu manga kaynağı bozuk olarak işaretlendi. Bazı özellikler çalışmayabilir</string>\n    <string name=\"download_default_directory\">Mangaların indirileceği varsayılan dizin</string>\n    <string name=\"private_app_directory_warning\">Uygulamayı kaldırırsanız, bu dizin ve içindeki tüm veriler silinecektir</string>\n    <string name=\"available_pattern\">%1$s var</string>\n    <string name=\"frequency_every_6_hours\">6 saatte bir</string>\n    <string name=\"pinned_sources_only\">Yalnızca sabitlenen kaynaklar</string>\n    <string name=\"hide_empty_sources\">Boş kaynakları gizle</string>\n    <string name=\"auto_double_foldable\">Otomatik İki Sayfa Katlanabilir</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-uk/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"one\">%1$d новий розділ</item>\n        <item quantity=\"few\">%1$d нові розділи</item>\n        <item quantity=\"many\">%1$d нових розділів</item>\n        <item quantity=\"other\">%1$d нових розділів</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"one\">%1$d хвилину тому</item>\n        <item quantity=\"few\">%1$d хвилини тому</item>\n        <item quantity=\"many\">%1$d хвилин тому</item>\n        <item quantity=\"other\">%1$d хвилин тому</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"one\">%1$d годину тому</item>\n        <item quantity=\"few\">%1$d години тому</item>\n        <item quantity=\"many\">%1$d годин тому</item>\n        <item quantity=\"other\">%1$d годин тому</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"one\">%1$d день тому</item>\n        <item quantity=\"few\">%1$d дні тому</item>\n        <item quantity=\"many\">%1$d днів тому</item>\n        <item quantity=\"other\">%1$d днів тому</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"one\">%1$d елемент</item>\n        <item quantity=\"few\">%1$d елементи</item>\n        <item quantity=\"many\">%1$d елементів</item>\n        <item quantity=\"other\">%1$d елементів</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"one\">%1$d розділ</item>\n        <item quantity=\"few\">%1$d розділи</item>\n        <item quantity=\"many\">%1$d розділів</item>\n        <item quantity=\"other\">%1$d розділів</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"one\">%1$d місяць тому</item>\n        <item quantity=\"few\">%1$d місяця тому</item>\n        <item quantity=\"many\">%1$d місяців тому</item>\n        <item quantity=\"other\">%1$d місяців тому</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"one\">%1$d година</item>\n        <item quantity=\"few\">%1$d години</item>\n        <item quantity=\"many\">%1$d годин</item>\n        <item quantity=\"other\">%1$d годин</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"one\">%1$d хвилина</item>\n        <item quantity=\"few\">%1$d хвилини</item>\n        <item quantity=\"many\">%1$d хвилин</item>\n        <item quantity=\"other\">%1$d хвилин</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"delete\">Видалити</string>\n    <string name=\"nothing_found\">Нічого не знайдено</string>\n    <string name=\"add_to_favourites\">До улюблених</string>\n    <string name=\"clear_history\">Очистити історію</string>\n    <string name=\"history_is_empty\">Історії ще немає</string>\n    <string name=\"add\">Додати</string>\n    <string name=\"save\">Зберегти</string>\n    <string name=\"local_storage\">Локальне сховище</string>\n    <string name=\"network_error\">Помилка мережі</string>\n    <string name=\"details\">Деталі</string>\n    <string name=\"try_again\">Спробуйте ще раз</string>\n    <string name=\"you_have_not_favourites_yet\">Улюблених ще немає</string>\n    <string name=\"add_new_category\">Нова категорія</string>\n    <string name=\"download_complete\">Завантажено</string>\n    <string name=\"favourites\">Уподобання</string>\n    <string name=\"history\">Історія</string>\n    <string name=\"error_occurred\">Сталася помилка</string>\n    <string name=\"chapters\">Розділи</string>\n    <string name=\"list\">Список</string>\n    <string name=\"detailed_list\">Детальний список</string>\n    <string name=\"list_mode\">Режим списку</string>\n    <string name=\"settings\">Налаштування</string>\n    <string name=\"remote_sources\">Джерела манґи</string>\n    <string name=\"loading_\">Завантаження…</string>\n    <string name=\"computing_\">Обчислення…</string>\n    <string name=\"chapter_d_of_d\">Розділ %1$d із %2$d</string>\n    <string name=\"close\">Закрити</string>\n    <string name=\"read\">Читати</string>\n    <string name=\"grid\">Таблиця</string>\n    <string name=\"share\">Поділитися</string>\n    <string name=\"create_shortcut\">Створити ярлик</string>\n    <string name=\"share_s\">Поділитися %s</string>\n    <string name=\"search\">Пошук</string>\n    <string name=\"search_manga\">Пошук манґи</string>\n    <string name=\"processing_\">Обробка…</string>\n    <string name=\"by_name\">Ім\\'я</string>\n    <string name=\"popular\">Популярна</string>\n    <string name=\"updated\">Оновлена</string>\n    <string name=\"newest\">Нова</string>\n    <string name=\"by_rating\">Рейтинг</string>\n    <string name=\"sort_order\">Порядок сортування</string>\n    <string name=\"filter\">Фільтр</string>\n    <string name=\"theme\">Тема</string>\n    <string name=\"light\">Світла</string>\n    <string name=\"dark\">Темна</string>\n    <string name=\"pages\">Сторінки</string>\n    <string name=\"remove\">Видалити</string>\n    <string name=\"_s_deleted_from_local_storage\">«%s» видалено з локального сховища</string>\n    <string name=\"save_page\">Зберегти сторінку</string>\n    <string name=\"page_saved\">Сторінку збережено</string>\n    <string name=\"share_image\">Поділитись зображенням</string>\n    <string name=\"operation_not_supported\">Ця операція не підтримується</string>\n    <string name=\"text_file_not_supported\">Виберіть файл ZIP або CBZ.</string>\n    <string name=\"no_description\">Немає опису</string>\n    <string name=\"clear_pages_cache\">Очистити кеш сторінок</string>\n    <string name=\"text_file_sizes\">Б|кБ|МБ|ГБ|ТБ</string>\n    <string name=\"standard\">Стандартний</string>\n    <string name=\"webtoon\">Манхва</string>\n    <string name=\"read_mode\">Режим читання</string>\n    <string name=\"grid_size\">Розмір сітки</string>\n    <string name=\"search_on_s\">Пошук по %s</string>\n    <string name=\"delete_manga\">Видалити манґу</string>\n    <string name=\"text_delete_local_manga\">Назавжди видалити «%s» із пристрою?</string>\n    <string name=\"reader_settings\">Налаштування читача</string>\n    <string name=\"switch_pages\">Перегортання сторінок</string>\n    <string name=\"error\">Помилка</string>\n    <string name=\"clear_thumbs_cache\">Очистити кеш мініатюр</string>\n    <string name=\"clear_search_history\">Очистити історію пошуку</string>\n    <string name=\"search_history_cleared\">Очищено</string>\n    <string name=\"internal_storage\">Внутрішнє сховище</string>\n    <string name=\"external_storage\">Зовнішнє сховище</string>\n    <string name=\"domain\">Домен</string>\n    <string name=\"app_update_available\">Доступна нова версія застосунку</string>\n    <string name=\"notifications\">Сповіщення</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Увімкнено %1$d з %2$d</string>\n    <string name=\"new_chapters\">Нові розділи</string>\n    <string name=\"download\">Завантажити</string>\n    <string name=\"vibration\">Вібрація</string>\n    <string name=\"favourites_categories\">Улюблені категорії</string>\n    <string name=\"remove_category\">Видалити</string>\n    <string name=\"text_empty_holder_primary\">Тут якось пусто…</string>\n    <string name=\"text_search_holder_secondary\">Спробуйте переформулювати запит.</string>\n    <string name=\"text_history_holder_primary\">Те, що ви читаєте, буде показано тут</string>\n    <string name=\"text_history_holder_secondary\">Знайдіть, що почитати, у розділі «Огляд»</string>\n    <string name=\"text_local_holder_primary\">Спочатку збережіть щось</string>\n    <string name=\"text_local_holder_secondary\">Збережіть щось із онлайн-каталогу або імпортуйте це з файлу.</string>\n    <string name=\"manga_shelf\">Полиця</string>\n    <string name=\"recent_manga\">Недавні</string>\n    <string name=\"pages_animation\">Анімація перегортання</string>\n    <string name=\"manga_save_location\">Папка завантажень</string>\n    <string name=\"other_storage\">Інше сховище</string>\n    <string name=\"done\">Готово</string>\n    <string name=\"all_favourites\">Усі улюблені</string>\n    <string name=\"favourites_category_empty\">Порожня категорія</string>\n    <string name=\"read_later\">Прочитати пізніше</string>\n    <string name=\"updates\">Оновлення</string>\n    <string name=\"new_version_s\">Нова версія: %s</string>\n    <string name=\"size_s\">Розмір: %s</string>\n    <string name=\"clear_updates_feed\">Очистити стрічку оновлень</string>\n    <string name=\"updates_feed_cleared\">Очищено</string>\n    <string name=\"rotate_screen\">Повернути екран</string>\n    <string name=\"update\">Оновити</string>\n    <string name=\"feed_will_update_soon\">Оновлення скоро почнеться</string>\n    <string name=\"track_sources\">Стежити за оновленнями</string>\n    <string name=\"dont_check\">Не перевіряти</string>\n    <string name=\"wrong_password\">Неправильний пароль</string>\n    <string name=\"protect_application\">Захистити застосунок</string>\n    <string name=\"protect_application_summary\">Запитувати пароль під час запуску Kotatsu</string>\n    <string name=\"repeat_password\">Повторіть пароль</string>\n    <string name=\"passwords_mismatch\">Паролі не співпадають</string>\n    <string name=\"about\">Про застосунок</string>\n    <string name=\"app_version\">Версія %s</string>\n    <string name=\"check_for_updates\">Перевірити наявність оновлень</string>\n    <string name=\"no_update_available\">Немає доступних оновлень</string>\n    <string name=\"create_category\">Нова категорія</string>\n    <string name=\"scale_mode\">Режим масштабування</string>\n    <string name=\"zoom_mode_fit_center\">Вмістити в екран</string>\n    <string name=\"zoom_mode_fit_height\">Підігнати по висоті</string>\n    <string name=\"zoom_mode_fit_width\">Підігнати по ширині</string>\n    <string name=\"zoom_mode_keep_start\">Вихідний розмір</string>\n    <string name=\"black_dark_theme\">Чорна</string>\n    <string name=\"black_dark_theme_summary\">Споживає менше енергії на екранах AMOLED</string>\n    <string name=\"backup_restore\">Резервне копіювання та відновлення</string>\n    <string name=\"data_restored\">Відновлено</string>\n    <string name=\"preparing_\">Підготовка…</string>\n    <string name=\"file_not_found\">Файл не знайдено</string>\n    <string name=\"data_restored_with_errors\">Дані відновлено, але є деякі помилки</string>\n    <string name=\"backup_information\">Ви можете створити резервну копію своєї історії та уподобань і відновити їх</string>\n    <string name=\"just_now\">Тільки що</string>\n    <string name=\"tap_to_try_again\">Торкніться, щоб спробувати ще раз</string>\n    <string name=\"reader_mode_hint\">Обраний режим буде запам\\'ятован для цієї манґи</string>\n    <string name=\"captcha_required\">Потрібна CAPTCHA</string>\n    <string name=\"captcha_solve\">Пройти</string>\n    <string name=\"clear_cookies\">Очистити кукі</string>\n    <string name=\"cookies_cleared\">Всі кукі були видалені</string>\n    <string name=\"clear_feed\">Очистити стрічку</string>\n    <string name=\"check_for_new_chapters\">Перевірити нові розділи</string>\n    <string name=\"reverse\">В зворотньому порядку</string>\n    <string name=\"sign_in\">Увійти</string>\n    <string name=\"auth_required\">Увійдіть, щоб переглянути цей вміст</string>\n    <string name=\"default_s\">За умовчанням: %s</string>\n    <string name=\"next\">Далі</string>\n    <string name=\"protect_application_subtitle\">Введіть пароль для запуску застосунку</string>\n    <string name=\"confirm\">Підтвердити</string>\n    <string name=\"password_length_hint\">Пароль має містити 4 символи або більше</string>\n    <string name=\"welcome\">Ласкаво просимо</string>\n    <string name=\"backup_saved\">Резервна копія збережена</string>\n    <string name=\"read_more\">Докладніше</string>\n    <string name=\"queued\">У черзі</string>\n    <string name=\"about_app_translation_summary\">Допомогти з перекладом застосунку</string>\n    <string name=\"about_app_translation\">Переклад</string>\n    <string name=\"auth_complete\">Авторизація виконана</string>\n    <string name=\"auth_not_supported_by\">Вхід на %s не підтримується</string>\n    <string name=\"text_clear_cookies_prompt\">Ви вийдете з усіх джерел</string>\n    <string name=\"state_finished\">Завершена</string>\n    <string name=\"state_ongoing\">Триває</string>\n    <string name=\"exclude_nsfw_from_history\">Виключити NSFW манґу з історії</string>\n    <string name=\"show_pages_numbers\">Показувати номери сторінок</string>\n    <string name=\"screenshots_policy\">Політика щодо знімків екрана</string>\n    <string name=\"screenshots_allow\">Дозволити</string>\n    <string name=\"suggestions_summary\">Пропонувати манґу на основі ваших уподобань</string>\n    <string name=\"suggestions_info\">Усі дані аналізуються лише локально на цьому пристрої й ніколи нікуди не надсилаються.</string>\n    <string name=\"text_suggestion_holder\">Почніть читати манґу, і ви отримаєте персоналізовані пропозиції</string>\n    <string name=\"enabled\">Увімкнено</string>\n    <string name=\"disabled\">Вимкнено</string>\n    <string name=\"reset_filter\">Скинути фільтр</string>\n    <string name=\"onboard_text\">Виберіть мови, якими ви хочете читати манґу. Це можливо змінити пізніше в налаштуваннях.</string>\n    <string name=\"only_using_wifi\">Тільки по Wi-Fi</string>\n    <string name=\"preload_pages\">Попереднє завантаження сторінок</string>\n    <string name=\"logged_in_as\">Ви увійшли як %s</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Різні мови</string>\n    <string name=\"search_chapters\">Знайти розділ</string>\n    <string name=\"chapters_empty\">Немає розділів у цій манзі</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"suggestions_updating\">Оновлення пропозицій</string>\n    <string name=\"text_delete_local_manga_batch\">Видалити вибрані елементи з пристрою назавжди\\?</string>\n    <string name=\"removal_completed\">Видалення завершено</string>\n    <string name=\"download_slowdown\">Сповільнення завантаження</string>\n    <string name=\"local_manga_processing\">Обробка збереженої манґи</string>\n    <string name=\"hide\">Приховати</string>\n    <string name=\"new_sources_text\">Доступні нові джерела манґи</string>\n    <string name=\"manga_downloading_\">Завантаження…</string>\n    <string name=\"clear\">Очистити</string>\n    <string name=\"downloads\">Завантаження</string>\n    <string name=\"follow_system\">Як в системі</string>\n    <string name=\"chapter_is_missing\">Розділ відсутній</string>\n    <string name=\"genres\">Жанри</string>\n    <string name=\"system_default\">За умовчанням</string>\n    <string name=\"always\">Завжди</string>\n    <string name=\"_continue\">Продовжити</string>\n    <string name=\"_import\">Імпорт</string>\n    <string name=\"notifications_settings\">Налаштування сповіщень</string>\n    <string name=\"open_in_browser\">Відкрити у веб-браузері</string>\n    <string name=\"not_available\">Недоступно</string>\n    <string name=\"cannot_find_available_storage\">Немає доступного сховища</string>\n    <string name=\"text_feed_holder\">Нові розділи того, що ви читаєте, показано тут</string>\n    <string name=\"search_results\">Результати пошуку</string>\n    <string name=\"enter_password\">Введіть пароль</string>\n    <string name=\"notification_sound\">Звук сповіщень</string>\n    <string name=\"light_indicator\">Світлодіодний індикатор</string>\n    <string name=\"yesterday\">Учора</string>\n    <string name=\"right_to_left\">Справа наліво (←)</string>\n    <string name=\"create_backup\">Створити резервну копію</string>\n    <string name=\"restore_backup\">Відновити з резервної копії</string>\n    <string name=\"data_restored_success\">Всі дані були відновлені</string>\n    <string name=\"group\">Групувати</string>\n    <string name=\"today\">Сьогодні</string>\n    <string name=\"silent\">Без звуку</string>\n    <string name=\"long_ago\">Давно</string>\n    <string name=\"text_clear_updates_feed_prompt\">Очистити всю історію оновлень назавжди\\?</string>\n    <string name=\"tracker_warning\">Деякі пристрої мають різну поведінку системи, що може порушити фонові завдання.</string>\n    <string name=\"text_clear_search_history_prompt\">Видалити всі останні пошукові запити назавжди\\?</string>\n    <string name=\"screenshots_block_nsfw\">Блок на NSFW</string>\n    <string name=\"screenshots_block_all\">Завжди блокувати</string>\n    <string name=\"suggestions\">Пропозиції</string>\n    <string name=\"suggestions_enable\">Увімкнути пропозиції</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Не пропонувати NSFW манґу</string>\n    <string name=\"never\">Ніколи</string>\n    <string name=\"appearance\">Зовнішній вигляд</string>\n    <string name=\"suggestions_excluded_genres\">Виключити жанри</string>\n    <string name=\"suggestions_excluded_genres_summary\">Укажіть жанри, які ви не хочете бачити в пропозиціях</string>\n    <string name=\"download_slowdown_summary\">Допомагає уникнути блокування вашої IP-адреси</string>\n    <string name=\"chapters_will_removed_background\">Розділи буде видалено у фоновому режимі</string>\n    <string name=\"check_new_chapters_title\">Перевіряти наявність нових розділів і повідомляти про них</string>\n    <string name=\"show_notification_new_chapters_on\">Ви будете отримувати повідомлення про оновлення манґи, яку ви читаєте</string>\n    <string name=\"notifications_enable\">Увімкнути сповіщення</string>\n    <string name=\"show_notification_new_chapters_off\">Ви не будете отримувати повідомлення, але нові розділи будуть відображатися у списку</string>\n    <string name=\"empty_favourite_categories\">Немає улюблених категорій</string>\n    <string name=\"name\">Назва</string>\n    <string name=\"edit\">Змінити</string>\n    <string name=\"edit_category\">Змінити категорію</string>\n    <string name=\"bookmark_add\">Додати закладку</string>\n    <string name=\"bookmark_remove\">Видалити закладку</string>\n    <string name=\"bookmarks\">Закладки</string>\n    <string name=\"bookmark_removed\">Закладка видалена</string>\n    <string name=\"bookmark_added\">Додано закладку</string>\n    <string name=\"undo\">Відмінити</string>\n    <string name=\"removed_from_history\">Видалено з історії</string>\n    <string name=\"dns_over_https\">DNS через HTTPS</string>\n    <string name=\"default_mode\">Режим за умовчанням</string>\n    <string name=\"detect_reader_mode_summary\">Автоматично визначати, чи є манґа манхвою</string>\n    <string name=\"detect_reader_mode\">Автовизначення режиму читання</string>\n    <string name=\"disable_battery_optimization\">Вимкнути оптимізацію акумулятора</string>\n    <string name=\"disable_battery_optimization_summary\">Допомагає з перевірками фонових оновлень</string>\n    <string name=\"crash_text\">Щось пішло не так. Будь ласка, надішліть звіт про помилку розробникам, щоб допомогти нам її виправити.</string>\n    <string name=\"send\">Надіслати</string>\n    <string name=\"disable_all\">Вимкнути все</string>\n    <string name=\"use_fingerprint\">Використовувати біометричні дані, якщо вони доступні</string>\n    <string name=\"appwidget_shelf_description\">Манґа з Ваших обраних</string>\n    <string name=\"appwidget_recent_description\">Манґа, яку Ви нещодавно читали</string>\n    <string name=\"invalid_domain_message\">Недійсний домен</string>\n    <string name=\"report\">Звіт</string>\n    <string name=\"tracking\">Відстеження</string>\n    <string name=\"logout\">Вийти</string>\n    <string name=\"status_planned\">Заплановано</string>\n    <string name=\"status_reading\">Читаю</string>\n    <string name=\"status_re_reading\">Перечитую</string>\n    <string name=\"status_completed\">Завершено</string>\n    <string name=\"status_on_hold\">Відкладено</string>\n    <string name=\"status_dropped\">Занедбано</string>\n    <string name=\"show_reading_indicators\">Показувати індикатори прогресу читання</string>\n    <string name=\"data_deletion\">Видалення даних</string>\n    <string name=\"show_reading_indicators_summary\">Показати відсоток прочитаного в історії та обраному</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Манґа, позначена як NSFW, ніколи не буде додана до історії і ваш прогрес не буде збережений</string>\n    <string name=\"clear_cookies_summary\">Може допомогти в разі виникнення проблем. Усі авторизації будуть анульовані</string>\n    <string name=\"show_all\">Показати всі</string>\n    <string name=\"select_range\">Виберіть діапазон</string>\n    <string name=\"not_found_404\">Вміст не знайдено або видалено</string>\n    <string name=\"back\">Назад</string>\n    <string name=\"email_enter_hint\">Введіть свою електронну пошту, щоб продовжити</string>\n    <string name=\"history_cleared\">Історія очищена</string>\n    <string name=\"manage\">Керувати</string>\n    <string name=\"no_bookmarks_yet\">Закладок ще немає</string>\n    <string name=\"no_bookmarks_summary\">Ви можете створювати закладки під час читання манґи</string>\n    <string name=\"bookmarks_removed\">Закладки видалено</string>\n    <string name=\"no_manga_sources\">Немає джерел манґи</string>\n    <string name=\"no_manga_sources_text\">Увімкніть джерела манґи, щоб читати онлайн</string>\n    <string name=\"random\">Випадкова</string>\n    <string name=\"categories_delete_confirm\">Ви впевнені, що бажаєте видалити вибрані улюблені категорії? \\nУсю манґу в них буде втрачено, і це неможливо скасувати.</string>\n    <string name=\"reorder\">Впорядкувати</string>\n    <string name=\"empty\">Порожньо</string>\n    <string name=\"explore\">Огляд</string>\n    <string name=\"saved_manga\">Збережена манґа</string>\n    <string name=\"pages_cache\">Кеш сторінок</string>\n    <string name=\"storage_usage\">Використання сховища</string>\n    <string name=\"available\">Доступні</string>\n    <string name=\"options\">Параметри</string>\n    <string name=\"incognito_mode\">Режим інкогніто</string>\n    <string name=\"no_chapters\">Немає розділів</string>\n    <string name=\"automatic_scroll\">Автоматична прокрутка</string>\n    <string name=\"reader_info_bar\">Показувати інформаційну панель у режимі читання</string>\n    <string name=\"comics_archive\">Архів коміксів</string>\n    <string name=\"folder_with_images\">Папка із зображеннями</string>\n    <string name=\"importing_manga\">Імпорт манґи</string>\n    <string name=\"import_completed\">Імпорт завершено</string>\n    <string name=\"import_completed_hint\">Ви можете видалити оригінальний файл зі сховища, щоб заощадити місце</string>\n    <string name=\"import_will_start_soon\">Імпорт почнеться незабаром</string>\n    <string name=\"feed\">Стрічка</string>\n    <string name=\"reader_info_pattern\">Розд. %1$d/%2$d Стор. %3$d/%4$d</string>\n    <string name=\"account_already_exists\">Обліковий запис уже існує</string>\n    <string name=\"sync_title\">Синхронізуйте ваші дані</string>\n    <string name=\"canceled\">Скасовано</string>\n    <string name=\"confirm_exit\">Натисніть «Назад» ще раз, щоб вийти</string>\n    <string name=\"sync\">Синхронізація</string>\n    <string name=\"clear_all_history\">Очистити всю історію</string>\n    <string name=\"last_2_hours\">Останні 2 години</string>\n    <string name=\"exit_confirmation_summary\">Двічі натисніть «Назад», щоб вийти зі застосунку</string>\n    <string name=\"exit_confirmation\">Підтвердження виходу</string>\n    <string name=\"removed_from_favourites\">Видалено з уподобань</string>\n    <string name=\"other_cache\">Інший кеш</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"manga_error_description_pattern\">Деталі помилки:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Спробуйте &lt;a href=%2$s&gt;відкрити манґу у веб-браузері&lt;/a&gt;, щоб переконатися, що вона доступна в джерелі&lt;br&gt;2. Переконайтеся, що ви використовуєте &lt;a href=kotatsu://about&gt;останню версію Kotatsu&lt;/a&gt;&lt;br&gt;3. Якщо він доступний, надішліть звіт про помилку розробникам.</string>\n    <string name=\"history_shortcuts\">Показувати ярлики останньої прочитаної манґи</string>\n    <string name=\"history_shortcuts_summary\">Зробити нещодавно прочитану манґу доступною за довгим натисканням на іконку застосунку</string>\n    <string name=\"reader_control_ltr_summary\">Не налаштовуйте напрям перемикання сторінок у режим читання, наприклад, натискання правої клавіші завжди перемикається на наступну сторінку. Ця опція впливає лише на апаратні пристрої введення</string>\n    <string name=\"reader_control_ltr\">Ергономічне керування режимом читання</string>\n    <string name=\"brightness\">Яскравість</string>\n    <string name=\"color_correction\">Корекція кольору</string>\n    <string name=\"contrast\">Контрастність</string>\n    <string name=\"reset\">Скинути</string>\n    <string name=\"text_unsaved_changes_prompt\">Зберегти чи скасувати незбережені зміни\\?</string>\n    <string name=\"discard\">Скасувати</string>\n    <string name=\"server_error\">Помилка на стороні сервера (%1$d). Будь ласка спробуйте пізніше</string>\n    <string name=\"clear_new_chapters_counters\">Також очистити інформацію про нові розділи</string>\n    <string name=\"error_no_space_left\">На пристрої не залишилося вільного місця</string>\n    <string name=\"network_unavailable\">Мережа недоступна</string>\n    <string name=\"network_unavailable_hint\">Увімкніть Wi-Fi або мобільну мережу, щоб читати манґу онлайн</string>\n    <string name=\"webtoon_zoom\">Масштабування в режимі манхви</string>\n    <string name=\"reader_slider\">Відображати повзунок перемикання сторінок</string>\n    <string name=\"compact\">Компактно</string>\n    <string name=\"prefetch_content\">Передвчасне завантаження контенту</string>\n    <string name=\"source_disabled\">Джерело відключено</string>\n    <string name=\"mark_as_current\">Позначити як актуальне</string>\n    <string name=\"language\">Мова</string>\n    <string name=\"theme_name_kanade\">Канаде</string>\n    <string name=\"services\">Служби</string>\n    <string name=\"nothing_here\">Тут нічого нема</string>\n    <string name=\"scrobbling_empty_hint\">Щоб відстежувати прогрес читання, виберіть Меню → Відстежити на екрані деталей манґи.</string>\n    <string name=\"theme_name_miku\">Міку</string>\n    <string name=\"theme_name_asuka\">Аска</string>\n    <string name=\"theme_name_mion\">Міон</string>\n    <string name=\"theme_name_rikka\">Рікка</string>\n    <string name=\"theme_name_sakura\">Сакура</string>\n    <string name=\"share_logs\">Поділиться логами</string>\n    <string name=\"enable_logging\">Увімкнути логування</string>\n    <string name=\"enable_logging_summary\">Записувати деякі дії з метою подальшого налагодження. Включайте лише якщо знаєте, що робите</string>\n    <string name=\"theme_name_mamimi\">Мамімі</string>\n    <string name=\"show_suspicious_content\">Показувати сумнівний контент</string>\n    <string name=\"theme_name_dynamic\">Динамічний</string>\n    <string name=\"color_theme\">Колірний акцент</string>\n    <string name=\"show_in_grid_view\">Показати у вигляді сітки</string>\n    <string name=\"allow_unstable_updates\">Дозволити нестабільні оновлення</string>\n    <string name=\"allow_unstable_updates_summary\">Отримувати сповіщення про нестабільні збірки</string>\n    <string name=\"download_started\">Завантаження розпочато</string>\n    <string name=\"user_agent\">Заголовок UserAgent</string>\n    <string name=\"settings_apply_restart_required\">Перезапустіть програму, щоб застосувати зміни</string>\n    <string name=\"got_it\">Зрозумів</string>\n    <string name=\"comics_archive_import_description\">Ви можете вибрати один або кілька файлів .cbz або .zip, кожен файл буде розпізнано як окрема манґа.</string>\n    <string name=\"folder_with_images_import_description\">Ви можете вибрати каталог з архівами або зображеннями. Кожен архів (або підкаталог) буде розпізнано як розділ.</string>\n    <string name=\"speed\">Швидкість</string>\n    <string name=\"show_on_shelf\">Показати на полиці</string>\n    <string name=\"sources_reorder_tip\">Натисніть і утримуйте елемент, щоб змінити його порядок</string>\n    <string name=\"sync_auth_hint\">Ви можете увійти в існуючий аккаунт, або створити новий</string>\n    <string name=\"find_similar\">Знайти схожі</string>\n    <string name=\"sync_settings\">Параметри синхронізації</string>\n    <string name=\"server_address\">Адреса сервера</string>\n    <string name=\"sync_host_description\">Ви можете використовувати власний сервер синхронізації або сервер за умовчанням. Не змінюйте це, якщо ви не впевнені, що робите.</string>\n    <string name=\"ignore_ssl_errors\">Ігноруйте помилки SSL</string>\n    <string name=\"mirror_switching\">Виберіть дзеркало автоматично</string>\n    <string name=\"paused\">Призупинено</string>\n    <string name=\"remove_completed\">Видалити завершені</string>\n    <string name=\"downloads_wifi_only\">Завантажувати тільки через Wi-Fi</string>\n    <string name=\"cancel_all\">Скасувати все</string>\n    <string name=\"mirror_switching_summary\">Автоматично перемикайте домени для джерел у разі помилок, якщо дзеркала доступні</string>\n    <string name=\"resume\">Відновити</string>\n    <string name=\"pause\">Пауза</string>\n    <string name=\"downloads_wifi_only_summary\">Зупинити завантаження при переході на мобільну мережу</string>\n    <string name=\"enable\">Увімкнути</string>\n    <string name=\"no_thanks\">Ні, дякую</string>\n    <string name=\"suggestion_manga\">Пропозиція: %s</string>\n    <string name=\"suggestions_notifications_summary\">Іноді показувати сповіщення з запропонованою манґою</string>\n    <string name=\"more\">Більше</string>\n    <string name=\"cancel_all_downloads_confirm\">Усі активні завантаження буде скасовано, частково завантажені дані буде втрачено</string>\n    <string name=\"remove_completed_downloads_confirm\">Вашу історію завантажень буде видалено назавжди. Завантажені файли не будуть порушені</string>\n    <string name=\"text_downloads_list_holder\">У вас немає завантажень</string>\n    <string name=\"downloads_resumed\">Завантаження відновлено</string>\n    <string name=\"downloads_paused\">Завантаження призупинено</string>\n    <string name=\"downloads_removed\">Завантаження видалено</string>\n    <string name=\"downloads_cancelled\">Завантаження скасовано</string>\n    <string name=\"suggestions_enable_prompt\">Хочете отримувати персоналізовані пропозиції щодо манґи\\?</string>\n    <string name=\"web_view_unavailable\">WebView недоступний: перевірте, чи встановлено провайдер WebView</string>\n    <string name=\"clear_network_cache\">Очистити мережевий кеш</string>\n    <string name=\"port\">Порт</string>\n    <string name=\"proxy\">Проксі</string>\n    <string name=\"type\">Тип</string>\n    <string name=\"address\">Адреса</string>\n    <string name=\"invalid_value_message\">Недійсне значення</string>\n    <string name=\"invalid_port_number\">Недійсний номер порту</string>\n    <string name=\"images_procy_description\">Використовуйте службу wsrv.nl, щоб зменшити використання трафіку та прискорити завантаження зображень, якщо це можливо</string>\n    <string name=\"downloaded\">Завантажено</string>\n    <string name=\"images_proxy_title\">Проксі для оптимізації зображень</string>\n    <string name=\"authorization_optional\">Авторизація (необов\\'язково)</string>\n    <string name=\"password\">Пароль</string>\n    <string name=\"username\">Ім\\'я користувача</string>\n    <string name=\"invert_colors\">Інвертувати кольори</string>\n    <string name=\"network\">Мережа</string>\n    <string name=\"data_and_privacy\">Дані та конфіденційність</string>\n    <string name=\"restore_summary\">Відновити раніше створену резервну копію</string>\n    <string name=\"reader_info_bar_summary\">Показувати поточний час і прогрес читання у верхній частині екрана</string>\n    <string name=\"show_pages_numbers_summary\">Показати номери сторінок у нижньому куті</string>\n    <string name=\"webtoon_zoom_summary\">Дозволити жести масштабування в режимі манхви</string>\n    <string name=\"clear_source_cookies_summary\">Очистити файли cookie лише для вказаного домену. У більшості випадків авторизація анулюється</string>\n    <string name=\"pick_custom_directory\">Вибрати користувальницький каталог</string>\n    <string name=\"no_access_to_file\">Ви не маєте доступу до цього файлу чи каталогу</string>\n    <string name=\"download_option_all_unread\">Усі непрочитані розділи</string>\n    <string name=\"download_option_all_chapters\">Усі розділи з перекладом %s</string>\n    <string name=\"download_option_whole_manga\">Манґа цілком</string>\n    <string name=\"download_option_all_unread_b\">Усі непрочитані розділи (%s)</string>\n    <string name=\"download_option_manual_selection\">Виберіть розділи вручну</string>\n    <string name=\"local_manga_directories\">Локальні каталоги манґи</string>\n    <string name=\"download_option_first_n_chapters\">Перші %s</string>\n    <string name=\"download_option_next_unread_n_chapters\">Перші непрочитані %s</string>\n    <string name=\"description\">Опис</string>\n    <string name=\"this_month\">Цього місяця</string>\n    <string name=\"color_dark\">Темний</string>\n    <string name=\"color_black\">Чорний</string>\n    <string name=\"background\">Фон</string>\n    <string name=\"data_not_restored\">Дані не відновлено</string>\n    <string name=\"data_not_restored_text\">Переконайтеся, що ви вибрали правильний файл резервної копії</string>\n    <string name=\"suggestions_wifi_only_summary\">Не оновлювати пропозиції під час використання лімітного інтернет-з\\'єднання</string>\n    <string name=\"tracker_wifi_only_summary\">Не перевіряти наявність нових розділів під час використання лімітного інтернет-з\\'єднання</string>\n    <string name=\"search_hint\">Введіть назву манґи, жанр або назву джерела</string>\n    <string name=\"progress\">Прогрес</string>\n    <string name=\"order_added\">Додано</string>\n    <string name=\"show\">Показати</string>\n    <string name=\"voice_search\">Голосовий пошук</string>\n    <string name=\"related_manga\">Схожа манґа</string>\n    <string name=\"color_light\">Світлий</string>\n    <string name=\"color_white\">Білий</string>\n    <string name=\"manage_categories\">Керування категоріями</string>\n    <string name=\"captcha_required_summary\">Для належної роботи %s потрібна перевірка captcha</string>\n    <string name=\"languages\">Мови</string>\n    <string name=\"unknown\">Невідомий</string>\n    <string name=\"in_progress\">В процесі</string>\n    <string name=\"disable_nsfw\">Вимкнути NSFW</string>\n    <string name=\"related_manga_summary\">Показати список пов\\'язаної манґи. У деяких випадках він може бути неточним або відсутнім</string>\n    <string name=\"too_many_requests_message\">Занадто багато запитів. Спробуйте пізніше</string>\n    <string name=\"advanced\">Просунута</string>\n    <string name=\"manga_list\">Список манґи</string>\n    <string name=\"error_corrupted_file\">Повертаються неправильні дані або файл пошкоджено</string>\n    <string name=\"on_device\">На пристрої</string>\n    <string name=\"moved_to_top\">Перенесено вгору</string>\n    <string name=\"items_limit_exceeded\">Більше не можна додавати елементи</string>\n    <string name=\"directories\">Каталоги</string>\n    <string name=\"main_screen_sections\">Розділи головного екрана</string>\n    <string name=\"to_top\">Вгору</string>\n    <string name=\"zoom_in\">Наблизити</string>\n    <string name=\"reader_zoom_buttons_summary\">Чи показувати елементи керування масштабуванням у нижньому правому куті</string>\n    <string name=\"reader_zoom_buttons\">Показати кнопки масштабування</string>\n    <string name=\"zoom_out\">Віддалити</string>\n    <string name=\"keep_screen_on\">Тримати екран увімкненим</string>\n    <string name=\"keep_screen_on_summary\">Не вимикати екран під час читання манґи</string>\n    <string name=\"state_abandoned\">Кинута</string>\n    <string name=\"categories\">Категорії</string>\n    <string name=\"list_options\">Параметри списку</string>\n    <string name=\"suggest_new_sources\">Пропонувати нові джерела після оновлення застосунку</string>\n    <string name=\"enhanced_colors_summary\">Зменшує смуги, але може вплинути на продуктивність</string>\n    <string name=\"by_relevance\">Актуальність</string>\n    <string name=\"enhanced_colors\">32-бітний колірний режим</string>\n    <string name=\"suggest_new_sources_summary\">Пропонує включити нові джерела манґи після оновлення застосунку</string>\n    <string name=\"online_variant\">Онлайн варіант</string>\n    <string name=\"frequency_every_day\">Кожен день</string>\n    <string name=\"backup_frequency\">Частота резервного копіювання</string>\n    <string name=\"periodic_backups_enable\">Увімкніть періодичне резервне копіювання</string>\n    <string name=\"frequency_every_2_days\">Кожні 2 дні</string>\n    <string name=\"frequency_once_per_week\">Раз на тиждень</string>\n    <string name=\"periodic_backups\">Періодичне резервне копіювання</string>\n    <string name=\"frequency_twice_per_month\">Двічі на місяць</string>\n    <string name=\"frequency_once_per_month\">Раз на місяць</string>\n    <string name=\"last_successful_backup\">Останнє успішне резервне копіювання: %s</string>\n    <string name=\"backups_output_directory\">Вихідний каталог резервних копій</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"lock_screen_rotation\">Блокування повороту екрану</string>\n    <string name=\"sources_catalog\">Каталог джерел</string>\n    <string name=\"content_type_manga\">Манґа</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_hentai\">Хентай</string>\n    <string name=\"content_type_comics\">Комікси</string>\n    <string name=\"catalog\">Каталог</string>\n    <string name=\"manage_sources\">Керування джерелами</string>\n    <string name=\"no_manga_sources_found\">За вашим запитом не знайдено доступних джерел манґи</string>\n    <string name=\"manual\">Вручну</string>\n    <string name=\"source_enabled\">Джерело включено</string>\n    <string name=\"disable_nsfw_summary\">Вимкніть джерела NSFW і приховайте манґу для дорослих зі списку, якщо можливо</string>\n    <string name=\"no_manga_sources_catalog_text\">У цьому розділі немає доступних джерел, або всі вони могли бути додані.\n\\nСлідкуйте за оновленнями</string>\n    <string name=\"available_d\">Доступно: %1$d</string>\n    <string name=\"content_type_other\">Інший</string>\n    <string name=\"state_paused\">Призупинено</string>\n    <string name=\"error_multiple_states_not_supported\">Фільтрування за кількома станами не підтримується цим джерелом манґи</string>\n    <string name=\"reader_optimize\">Зменшення споживання пам\\'яті (бета)</string>\n    <string name=\"error_multiple_genres_not_supported\">Фільтрація за кількома жанрами не підтримується цим джерелом манґи</string>\n    <string name=\"error_search_not_supported\">Пошук не підтримується цим джерелом манґи</string>\n    <string name=\"reader_optimize_summary\">Зменшити якість закадрових сторінок, щоб використовувати менше пам\\'яті</string>\n    <string name=\"state\">Стан</string>\n    <string name=\"error_filter_states_genre_not_supported\">Це джерело не підтримує фільтрацію за жанрами та станами</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Це джерело не підтримує фільтрацію за жанрами та локалями</string>\n    <string name=\"apply\">Застосувати</string>\n    <string name=\"genres_search_hint\">Почніть вводити назву жанру</string>\n    <string name=\"globally\">Глобально</string>\n    <string name=\"downloads_settings_info\">Ви можете ввімкнути повільне завантаження для кожного джерела манґи окремо в налаштуваннях джерела, якщо у вас є проблеми з блокуванням на стороні сервера</string>\n    <string name=\"this_manga\">Ця манґа</string>\n    <string name=\"skip\">Пропустити</string>\n    <string name=\"color_correction_apply_text\">Ці налаштування можна застосувати глобально або лише до поточної манґи. Користувацькі налаштування не будуть замінені, якщо застосовувати їх глобально.</string>\n    <string name=\"grayscale\">Відтінки сірого</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Може допомогти з початком завантаження, якщо у вас виникають проблеми з нею</string>\n    <string name=\"welcome_text\">Виберіть, які джерела вмісту ви хочете включити. Це також можна налаштувати пізніше в налаштуваннях</string>\n    <string name=\"restore\">Відновити</string>\n    <string name=\"backup_date_\">Дата створення резервної копії: %s</string>\n    <string name=\"sync_auth\">Увійти до синхронізації</string>\n    <string name=\"by_name_reverse\">Ім\\'я (навпаки)</string>\n    <string name=\"state_upcoming\">Очікується</string>\n    <string name=\"genres_exclude\">Виключити жанри</string>\n    <string name=\"rating_safe\">Безпечний</string>\n    <string name=\"rating_adult\">Дорослий</string>\n    <string name=\"default_tab\">Типова вкладка</string>\n    <string name=\"content_rating\">Рейтинг контенту</string>\n    <string name=\"rating_suggestive\">З натяками</string>\n    <string name=\"mark_as_completed\">Позначити як завершене</string>\n    <string name=\"mark_as_completed_prompt\">Позначити вибрану манґу як повністю прочитану?\n\\n\n\\nПопередження: поточний прогрес читання буде втрачено.</string>\n    <string name=\"category_hidden_done\">Ця категорія була прихована на головному екрані і доступна через Меню → Керування категоріями</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"incognito_mode_hint\">Ваш прогрес читання не буде збережений</string>\n    <string name=\"volume_\">Том %d</string>\n    <string name=\"volume_unknown\">Невідомий том</string>\n    <string name=\"vertical\">Вертикальний</string>\n    <string name=\"last_read\">Останнє прочитане</string>\n    <string name=\"show_menu\">Показати меню</string>\n    <string name=\"toggle_ui\">Показати/приховати інтерфейс</string>\n    <string name=\"prev_chapter\">Попередня глава</string>\n    <string name=\"next_chapter\">Наступна глава</string>\n    <string name=\"prev_page\">Попередня сторінка</string>\n    <string name=\"next_page\">Наступна сторінка</string>\n    <string name=\"none\">Нічого</string>\n    <string name=\"reader_actions\">Дії в режимі читання</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Використовуйте кнопки гучності для перегортання сторінок</string>\n    <string name=\"tap_action\">Дія при натисканні</string>\n    <string name=\"long_tap_action\">Дія при тривалому натисканні</string>\n    <string name=\"switch_pages_volume_buttons\">Увімкнути кнопки гучності</string>\n    <string name=\"reader_actions_summary\">Налаштування дій для сенсорних областей екрана</string>\n    <string name=\"email_password_enter_hint\">Введіть свою адресу електронної пошти та пароль, щоб продовжити</string>\n    <string name=\"config_reset_confirm\">Повернути налаштування до значень за замовчуванням? Цю дію неможливо скасувати.</string>\n    <string name=\"use_two_pages_landscape\">Використовувати макет двох сторінок у альбомній орієнтації (бета)</string>\n    <string name=\"fullscreen_mode\">Повноекранний режим</string>\n    <string name=\"reader_fullscreen_summary\">Приховувати інтерфейс системи</string>\n    <string name=\"default_webtoon_zoom_out\">Віддалення у режимі манхви</string>\n    <string name=\"reading_time_estimation\">Відображати передбачуваний час читання</string>\n    <string name=\"reading_time_estimation_summary\">Це значення може бути неточним</string>\n    <string name=\"check_for_new_chapters_disabled\">Перевірка нових глав вимкнена</string>\n    <string name=\"suggestions_unavailable_text\">Функція пропозицій вимкнена</string>\n    <string name=\"pages_saving\">Збереження сторінок</string>\n    <string name=\"ask_for_dest_dir_every_time\">Щоразу запитувати директорію призначення</string>\n    <string name=\"remove_from_history\">Видалити з історії</string>\n    <string name=\"show_labels_in_navbar\">Показувати мітки на панелі навігації</string>\n    <string name=\"default_page_save_dir\">Директорія збереження сторінки за замовчуванням</string>\n    <string name=\"location\">Розташування</string>\n    <string name=\"automatic\">Автоматичний</string>\n    <string name=\"multiple_cbz_files\">Декілька файлів CBZ</string>\n    <string name=\"preferred_download_format\">Бажаний формат для завантажень</string>\n    <string name=\"single_cbz_file\">Один файл CBZ</string>\n    <string name=\"reading_stats\">Статистика читання</string>\n    <string name=\"other_manga\">Інша манґа</string>\n    <string name=\"less_than_minute\">Менше хвилини</string>\n    <string name=\"statistics\">Статистика</string>\n    <string name=\"clear_stats\">Очистити статистику</string>\n    <string name=\"stats_cleared\">Статистика очищена</string>\n    <string name=\"week\">Тиждень</string>\n    <string name=\"all_time\">Весь час</string>\n    <string name=\"three_months\">Три місяці</string>\n    <string name=\"pages_read_s\">Прочитані сторінки: %s</string>\n    <string name=\"clear_stats_confirm\">Ви дійсно хочете очистити всю статистику читання? Цю дію не можна скасувати.</string>\n    <string name=\"month\">Місяць</string>\n    <string name=\"day\">День</string>\n    <string name=\"empty_stats_text\">Статистика за вибраний період відсутня</string>\n    <string name=\"alternatives\">Альтернативи</string>\n    <string name=\"migrate\">Перенести</string>\n    <string name=\"migrate_confirmation\">Манґа «%1$s» з «%2$s» буде замінена на «%3$s» з «%4$s» у вашій історії та у вибраному (якщо доступно)</string>\n    <string name=\"manga_migration\">Перенесення манґи</string>\n    <string name=\"migration_completed\">Перенесення завершено</string>\n    <string name=\"chapters_grid_view\">Вид сітки</string>\n    <string name=\"no_chapters_deleted\">Жодна глава не була видалена</string>\n    <string name=\"delete_read_chapters_summary\">Видаліть розділи, які ви вже прочитали, з локального сховища, щоб звільнити місце</string>\n    <string name=\"chapters_deleted_pattern\">Видалено %1$s, очищено %2$s</string>\n    <string name=\"delete_read_chapters\">Видалити прочитані розділи</string>\n    <string name=\"split_by_translations\">Поділити за перекладами</string>\n    <string name=\"split_by_translations_summary\">Показувати розділи з різними перекладами окремо, а не в одному списку</string>\n    <string name=\"order_oldest\">Найстаріша</string>\n    <string name=\"long_ago_read\">Давно прочитана</string>\n    <string name=\"unread\">Непрочитана</string>\n    <string name=\"runs_on_app_start\">Запускається під час запуску застосунка</string>\n    <string name=\"delete_read_chapters_auto\">Автоматично видаляти прочитані розділи</string>\n    <string name=\"delete_read_chapters_prompt\">Це призведе до остаточного видалення всіх розділів, позначених як прочитані, з вашого локального сховища. Ви можете завантажити їх пізніше, але імпортовані розділи можуть бути втрачені назавжди</string>\n    <string name=\"enable_source\">Увімкнути джерело</string>\n    <string name=\"unsupported_source\">Це джерело манґи не підтримується</string>\n    <string name=\"show_pages_thumbs\">Показати мініатюри сторінок</string>\n    <string name=\"show_pages_thumbs_summary\">Увімкніть вкладку «Сторінки» на екрані відомостей</string>\n    <string name=\"error_no_data_received\">Жодних даних не було отримано з сервера</string>\n    <string name=\"unsupported_backup_message\">Виберіть правильний файл резервної копії Kotatsu</string>\n    <string name=\"last_used\">Останнє використання</string>\n    <string name=\"hours_short\">%d г.</string>\n    <string name=\"minutes_short\">%d хв.</string>\n    <string name=\"hours_minutes_short\">%1$d г. %2$d хв.</string>\n    <string name=\"webtoon_gaps\">Прогалини в режимі манхви</string>\n    <string name=\"webtoon_gaps_summary\">Показувати вертикальні проміжки між сторінками в режимі манхви</string>\n    <string name=\"show_updated\">Показати оновлено</string>\n    <string name=\"fix\">Виправити</string>\n    <string name=\"missing_storage_permission\">Немає дозволу на доступ до манґи на зовнішній пам’яті</string>\n    <string name=\"search_suggestions\">Пошукові пропозиції</string>\n    <string name=\"recent_queries\">Останні запити</string>\n    <string name=\"suggested_queries\">Пропоновані запити</string>\n    <string name=\"authors\">Автори</string>\n    <string name=\"less_frequently\">Рідше</string>\n    <string name=\"more_frequently\">Частіше</string>\n    <string name=\"frequency_of_check\">Частота перевірки</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Закріпити інтерфейс навігації</string>\n    <string name=\"pin_navigation_ui_summary\">Не ховати навігаційну панель та рядок пошуку під час прокручування</string>\n    <string name=\"blocked_by_server_message\">Ви заблоковані сервером. Спробуйте використовувати інше підключення до мережі (VPN, проксі тощо)</string>\n    <string name=\"disable_connectivity_check\">Вимкнути перевірку підключення</string>\n    <string name=\"ignore_ssl_errors_summary\">Перевірку сертифіката SSL можна вимкнути, якщо під час доступу до мережних ресурсів виникають проблеми, пов\\'язані з SSL. Це може вплинути на вашу безпеку. Після зміни цього параметра буде потрібно перезавантажити застосунок.</string>\n    <string name=\"disable_connectivity_check_summary\">Пропустити перевірки підключення у разі проблем із підключенням (наприклад, перехід в автономний режим, навіть якщо мережа підключена)</string>\n    <string name=\"disable_nsfw_notifications\">Вимкнути повідомлення NSFW</string>\n    <string name=\"tracker_debug_info\">Журнал перевірки нових розділів</string>\n    <string name=\"tracker_debug_info_summary\">Відлагоджувальна інформація про фонову перевірку наявності нових розділів</string>\n    <string name=\"disable_nsfw_notifications_summary\">Не відображати повідомлення про оновлення манґи NSFW</string>\n    <string name=\"disable\">Вимкнути</string>\n    <string name=\"sources_disabled\">Джерела вимкнено</string>\n    <string name=\"_new\">Нове</string>\n    <string name=\"all_languages\">Всі мови</string>\n    <string name=\"screenshots_block_incognito\">Блокувати в режимі інкогніто</string>\n    <string name=\"image_server\">Сервер зображень</string>\n    <string name=\"unpin\">Відкріпити</string>\n    <string name=\"source_pinned\">Джерело закріплено</string>\n    <string name=\"sources_pinned\">Джерела закріплені</string>\n    <string name=\"recent_sources\">Нещодавні джерела</string>\n    <string name=\"crop_pages\">Обрізати сторінки</string>\n    <string name=\"sources_unpinned\">Джерела відкріплені</string>\n    <string name=\"pin\">Закріпити</string>\n    <string name=\"source_unpinned\">Джерело відкріплено</string>\n    <string name=\"percent_read\">Відсоток прочитаного</string>\n    <string name=\"percent_left\">Відсоток, що залишився</string>\n    <string name=\"chapters_read\">Прочитані глави</string>\n    <string name=\"chapters_left\">Розділів залишилося</string>\n    <string name=\"external_source\">Зовнішній/плагін</string>\n    <string name=\"plugin_incompatible\">Несумісний плагін чи внутрішня помилка. Переконайтеся, що ви використовуєте останню версію плагіна та Kotatsu</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Немає манґи, що відповідає вибраним вами фільтрам</string>\n    <string name=\"connection_ok\">З\\'єднання в порядку</string>\n    <string name=\"invalid_proxy_configuration\">Неправильна конфігурація проксі</string>\n    <string name=\"retry\">Повторити</string>\n    <string name=\"pages_saved\">Сторінки збережені</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"invalid_server_address_message\">Невірна адреса сервера</string>\n    <string name=\"theme_name_expressive\">Виразний (Тест)</string>\n    <string name=\"too_many_requests_message_retry\">Забагато запитів. Спробуйте ще раз через %s</string>\n    <string name=\"reader_navigation_inverted\">Інвертувати елементи керування навігацією</string>\n    <string name=\"reader_navigation_inverted_summary\">Поміняти місцями напрямок кнопки регулювання гучності та апаратної клавіші навігації (вліво/вгору/вниз/вправо)</string>\n    <string name=\"seconds_short\">%d с</string>\n    <string name=\"minutes_seconds_short\">%1$d хв %2$d с</string>\n    <string name=\"plugin_incompatible_with_cause\">Помилка плагіна: %s\\nПереконайтеся, що ви використовуєте останню версію плагіна та Kotatsu</string>\n    <string name=\"show_quick_filters\">Показати швидкі фільтри</string>\n    <string name=\"show_quick_filters_summary\">Надає можливість фільтрувати списки манги за певними параметрами</string>\n    <string name=\"sfw\">SFW</string>\n    <string name=\"skip_all\">Пропустити все</string>\n    <string name=\"stuck\">Зависло</string>\n    <string name=\"not_in_favorites\">Немає в обраних</string>\n    <string name=\"updated_long_ago\">Оновлено давно</string>\n    <string name=\"unpopular\">Непопулярний</string>\n    <string name=\"low_rating\">Низький рейтинг</string>\n    <string name=\"sort_order_asc\">За зростанням</string>\n    <string name=\"sort_order_desc\">За спаданням</string>\n    <string name=\"by_date\">Дата</string>\n    <string name=\"popularity\">Популярність</string>\n    <string name=\"scrobbler_auth_required\">Увійдіть у %s, щоб продовжити</string>\n    <string name=\"scrobbler_auth_intro\">Увійдіть, щоб настроїти інтеграцію з %s. Це дозволить вам відстежувати прогрес та статус читання манги</string>\n    <string name=\"unstable_feature\">Нестабільна функція</string>\n    <string name=\"unstable_feature_summary\">Ця функція є експериментальною. Переконайтеся, що у вас є резервна копія, щоб уникнути втрати даних</string>\n    <string name=\"downloads_background\">Фонові завантаження</string>\n    <string name=\"download_new_chapters\">Завантажити нові розділи</string>\n    <string name=\"manga_with_downloaded_chapters\">Манга із завантаженими розділами</string>\n    <string name=\"manga_replaced\">Манґу «%1$s» (%2$s) замінено на «%3$s» (%4$s)</string>\n    <string name=\"fixing_manga\">Виправлення манґи</string>\n    <string name=\"fixed\">Виправлено успішно</string>\n    <string name=\"no_fix_required\">Виправлення для «%s» не потрібно</string>\n    <string name=\"no_alternatives_found\">Альтернативи для «%s» не знайдені</string>\n    <string name=\"manga_fix_prompt\">Ця функція знайде альтернативні джерела обраної манги. Завдання займе деякий час і виконуватиметься у фоновому режимі</string>\n    <string name=\"content_type_novel\">Новела</string>\n    <string name=\"content_type_manhua\">Маньхуа</string>\n    <string name=\"content_type_manhwa\">Манхва</string>\n    <string name=\"recently_added\">Нещодавно додано</string>\n    <string name=\"added_long_ago\">Додано давно</string>\n    <string name=\"popular_in_hour\">Популярне в цю годину</string>\n    <string name=\"popular_today\">Популярно сьогодні</string>\n    <string name=\"popular_in_week\">Популярно цього тижня</string>\n    <string name=\"popular_in_month\">Популярно цього місяця</string>\n    <string name=\"popular_in_year\">Популярно цього року</string>\n    <string name=\"original_language\">Вихідна мова</string>\n    <string name=\"year\">Рік</string>\n    <string name=\"demographics\">Демографія</string>\n    <string name=\"demographic_shounen\">Шьонен</string>\n    <string name=\"demographic_shoujo\">Шьоджьо</string>\n    <string name=\"demographic_seinen\">Сейнен</string>\n    <string name=\"demographic_josei\">Джьосей</string>\n    <string name=\"years\">Роки</string>\n    <string name=\"any\">Будь-який</string>\n    <string name=\"filter_search_warning\">Це джерело не підтримує пошук з фільтрами. Ваші фільтри були очищені</string>\n    <string name=\"demographic_kodomo\">Кодомо</string>\n    <string name=\"content_type_one_shot\">Ваншот</string>\n    <string name=\"content_type_doujinshi\">Додзінсі</string>\n    <string name=\"content_type_image_set\">Набір зображень</string>\n    <string name=\"content_type_artist_cg\">Художник CG</string>\n    <string name=\"content_type_game_cg\">Ігрова CG</string>\n    <string name=\"debug\">Налагодження</string>\n    <string name=\"source_code\">Вихідний код</string>\n    <string name=\"user_manual\">Посібник користувача</string>\n    <string name=\"telegram_group\">Група у Telegram</string>\n    <string name=\"error_image_format\">Формат зображення, що не підтримується: %s</string>\n    <string name=\"error_not_image\">Невірний формат: очікувалося зображення, але отримано %s</string>\n    <string name=\"start_download\">Почати завантаження</string>\n    <string name=\"save_manga_confirm\">Зберегти вибрану мангу? Це може зайняти трафік та місце на диску</string>\n    <string name=\"save_manga\">Зберегти манґу</string>\n    <string name=\"genre\">Жанр</string>\n    <string name=\"download_added\">Завантаження додано</string>\n    <string name=\"more_options\">Більше опцій</string>\n    <string name=\"destination_directory\">Каталог призначення</string>\n    <string name=\"chapter_selection_hint\">Ви можете вибрати розділи для завантаження, утримуючи елемент у списку розділів.</string>\n    <string name=\"chapters_all\">Усі</string>\n    <string name=\"download_over_cellular\">Завантаження через мобільну мережу</string>\n    <string name=\"download_cellular_confirm\">Дозволити завантаження через мобільну мережу?</string>\n    <string name=\"dont_allow\">Не дозволяти</string>\n    <string name=\"allow_always\">Дозволити завжди</string>\n    <string name=\"allow_once\">Дозволити один раз</string>\n    <string name=\"ask_every_time\">Запитувати щоразу</string>\n    <string name=\"screen_orientation\">Орієнтація екрану</string>\n    <string name=\"portrait\">Портретна</string>\n    <string name=\"landscape\">Ландшафтна</string>\n    <string name=\"access_denied_403\">Доступ був відхилений (403)</string>\n    <string name=\"max_backups_count\">Максимальна кількість резервних копій</string>\n    <string name=\"delete_old_backups\">Видалити старі резервні копії</string>\n    <string name=\"delete_old_backups_summary\">Автоматично видаляти старі резервні копії, щоб звільнити місце для даних</string>\n    <string name=\"handle_links\">Обробка посилань</string>\n    <string name=\"email\">Електронна пошта</string>\n    <string name=\"captcha_required_message\">Це джерело вимагає рішення капчі, щоб продовжити</string>\n    <string name=\"author\">Автор</string>\n    <string name=\"rating\">Рейтинг</string>\n    <string name=\"source\">Джерело</string>\n    <string name=\"translation\">Переклад</string>\n    <string name=\"show_slider\">Показати слайдер</string>\n    <string name=\"incognito\">Інкогніто</string>\n    <string name=\"error_connection_reset\">Скидання з\\'єднання віддаленим хостом</string>\n    <string name=\"backup_tg_check\">Перевірте, чи працює API</string>\n    <string name=\"backup_tg_echo\">Тестове повідомлення</string>\n    <string name=\"backup_tg_id_not_set\">ID чату не встановлено</string>\n    <string name=\"telegram_chat_id\">ID чату Telegram</string>\n    <string name=\"open_telegram_bot\">Відкрийте Telegram-бота</string>\n    <string name=\"send_backups_telegram\">Надсилати резервні копії до Telegram</string>\n    <string name=\"test_connection\">Тестове з\\'єднання</string>\n    <string name=\"telegram_chat_id_summary\">Введіть ID чату, куди слід надсилати резервні копії</string>\n    <string name=\"open_telegram_bot_summary\">Натисніть, щоб відкрити чат з Kotatsu Backup Bot</string>\n    <string name=\"clear_database\">Очистити базу даних</string>\n    <string name=\"clear_database_summary\">Видалити інформацію про мангу, яка не використовується</string>\n    <string name=\"enable_all_sources\">Включити всі джерела манги</string>\n    <string name=\"enable_all_sources_summary\">Усі доступні джерела манги будуть включені назавжди</string>\n    <string name=\"all_sources_enabled\">Усі джерела включені</string>\n    <string name=\"reader_info_bar_transparent\">Прозора інформаційна панель читання</string>\n    <string name=\"backup_restored_background\">Резервну копію буде відновлено у фоновому режимі</string>\n    <string name=\"restoring_backup\">Відновлення резервної копії</string>\n    <string name=\"reader_controls_in_bottom_bar\">Елементи керування читанням на нижній панелі</string>\n    <string name=\"chapters_and_pages\">Розділи та сторінки</string>\n    <string name=\"pages_slider\">Повзунок перемикання сторінок</string>\n    <string name=\"screen_rotation_locked\">Поворот екрана було заблоковано</string>\n    <string name=\"screen_rotation_unlocked\">Поворот екрана було розблоковано</string>\n    <string name=\"badges_in_lists\">Піктограми у списках</string>\n    <string name=\"search_everywhere\">Пошук усюди</string>\n    <string name=\"simple\">Простий</string>\n    <string name=\"global_search\">Глобальний пошук</string>\n    <string name=\"disable_captcha_notifications\">Вимкнути сповіщення про CAPTCHA</string>\n    <string name=\"chapter_volume_number\">Том %1$s Розділ %2$s</string>\n    <string name=\"chapter_number\">Розділ %s</string>\n    <string name=\"unnamed_chapter\">Безіменний розділ</string>\n    <string name=\"search_disabled_sources\">Пошук за відключеними джерелами</string>\n    <string name=\"error_details\">Подробиці помилки</string>\n    <string name=\"book_effect\">Жовтуватий фон (синій фільтр)</string>\n    <string name=\"theme_name_totoro\">Тоторо</string>\n    <string name=\"theme_name_itsuka\">Іцука</string>\n    <string name=\"reader_multitask_summary\">Дозволяє тримати відкритими кілька читалок із різною манґою одночасно</string>\n    <string name=\"reader_multitask\">Відкривати читалку як окреме завдання</string>\n    <string name=\"share_backup\">Поділитись резервною копією</string>\n    <string name=\"creating_backup\">Створення резервної копії</string>\n    <string name=\"collapse_long_description\">Згорнути довгий опис</string>\n    <string name=\"adblock_summary\">Блокування реклами у вбудованому браузері (бета)</string>\n    <string name=\"adblock\">Блокувати рекламу у браузері</string>\n    <string name=\"expand\">Розгорнути</string>\n    <string name=\"collapse\">Згорнути</string>\n    <string name=\"changelog_summary\">Історія змін для нещодавно випущених версій</string>\n    <string name=\"changelog\">Журнал змін</string>\n    <string name=\"hide_from_main_screen\">Сховати з головного екрану</string>\n    <string name=\"additional_action_required\">Потрібні додаткові дії</string>\n    <string name=\"incognito_for_nsfw\">Режим інкогніто для NSFW-манґи</string>\n    <string name=\"incognito_mode_hint_nsfw\">Ця манґа може містити контент для дорослих. Бажаєте використовувати режим інкогніто?</string>\n    <string name=\"dont_ask_again\">Більше не питати</string>\n    <string name=\"page_switch_timer\">Сторінка змінюватиметься кожні ~%d секунд</string>\n    <string name=\"change_cover\">Змінити обкладинку</string>\n    <string name=\"pick_custom_file\">Вибрати файл користувача</string>\n    <string name=\"pick_manga_page\">Вибрати сторінку манґи</string>\n    <string name=\"use_default_cover\">Використовувати стандартну обкладинку</string>\n    <string name=\"manga_override_hint\">Ці зміни впливатимуть на те, як манга відображається у програмі</string>\n    <string name=\"error_non_file_uri\">Вибраний шлях не може бути використаний, оскільки він не позначає файл чи каталог</string>\n    <string name=\"tags_warnings_summary\">Виділяти жанри, які можуть бути неприйнятними для більшості користувачів</string>\n    <string name=\"tags_warnings\">Виділяти небезпечні жанри</string>\n    <string name=\"suggestions_disabled_sources_summary\">Відображати рекомендації з усіх джерел манги, включаючи вимкнені</string>\n    <string name=\"include_disabled_sources\">Увімкнути відключені джерела</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Доросла манґа не відображатиметься у рекомендаціях. Ця опція може не працювати з деякими джерелами</string>\n    <string name=\"no_write_permission_to_file\">Немає прав на запис до файлу</string>\n    <string name=\"clear_browser_data\">Очистити дані браузера</string>\n    <string name=\"link_to_manga_in_app\">Посилання на мангу у Kotatsu</string>\n    <string name=\"link_to_manga_on_s\">Посилання на мангу на %s</string>\n    <string name=\"error_disclaimer_report\">Ви можете надіслати звіт про помилку розробникам. Це допоможе нам виправити проблему.</string>\n    <string name=\"error_disclaimer_app_outdated\">Схоже, що ваша версія Kotatsu застаріла. Будь ласка, установіть останню версію, щоб отримати всі доступні виправлення.</string>\n    <string name=\"error_disclaimer_manga\">Спробуйте відкрити манґу в браузері, щоб переконатися, що вона доступна джерелом.</string>\n    <string name=\"handle_links_summary\">Обробка посилань на мангу з зовнішніх програм (наприклад, веб-браузера). Можливо, вам також доведеться ввімкнути цю функцію вручну в системних налаштуваннях програми</string>\n    <string name=\"disable_captcha_notifications_summary\">Ви не будете отримувати повідомлення про вирішення CAPTCHA для цього джерела, але це може призвести до порушення фонових операцій (перевірка нових розділів, отримання рекомендацій тощо)</string>\n    <string name=\"clear_browser_data_summary\">Очистити дані браузера, такі як кеш і файли cookie. Попередження: авторизація в джерелах манги може стати недійсною</string>\n    <string name=\"local_storage_cleanup\">Очищення локального сховища</string>\n    <string name=\"packup_creation_failed\">Не вдалося створити резервну копію</string>\n    <string name=\"main_screen\">Головний екран</string>\n    <string name=\"main_screen_fab\">Показати плаваючу кнопку «Продовжити»</string>\n    <string name=\"main_screen_fab_summary\">Дозволяє продовжити читання одним кліком. Ця кнопка не з\\'являється в режимі інкогніто або коли історія порожня</string>\n    <string name=\"error_corrupted_zip\">Пошкоджений архів ZIP (%s)</string>\n    <string name=\"discord_rpc\">Багатий на присутність у Discord</string>\n    <string name=\"discord_token\">Токен Discord</string>\n    <string name=\"discord_token_summary\">Введіть свій токен Discord, щоб увімкнути Rich Presence</string>\n    <string name=\"discord_token_description\">Введіть свій Discord токен або натисніть %s, щоб отримати його за допомогою браузера</string>\n    <string name=\"discord_token_hint\">Вставте тут свій токен Discord</string>\n    <string name=\"discord_rpc_summary\">Покажіть свій статус читання в Discord</string>\n    <string name=\"obtain\">Отримати</string>\n    <string name=\"discord_rpc_description\">Читання манги на Kotatsu - програма для читання манґи</string>\n    <string name=\"reading_s\">Читання %s</string>\n    <string name=\"read_on_s\">Читати на %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Не використовуйте RPC для контенту для дорослих</string>\n    <string name=\"invalid_token\">Недійсний токен: %s</string>\n    <string name=\"show_floating_control_button\">Показати плаваючу кнопку керування</string>\n    <string name=\"unavailable\">Недоступно</string>\n    <string name=\"manga_restricted_description\">Ця манга недоступна для читання в цьому джерелі. Спробуйте знайти її в інших джерелах або відкрити в браузері, щоб отримати додаткову інформацію</string>\n    <string name=\"no_chapters_in_manga\">Ця манга не містить жодних розділів</string>\n    <string name=\"chapters_load_failed\">Не вдалося завантажити список розділів</string>\n    <string name=\"telegram_integration\">Інтеграція з Telegram</string>\n    <string name=\"test_parser\">Джерело тестової манги</string>\n    <string name=\"pull_to_prev_chapter\">Відпустіть, щоб відкрити попередній розділ</string>\n    <string name=\"pull_to_next_chapter\">Відпустіть, щоб відкрити наступний розділ</string>\n    <string name=\"pull_top_no_prev\">Немає попереднього розділу</string>\n    <string name=\"pull_bottom_no_next\">Немає наступного розділу</string>\n    <string name=\"enable_pull_gesture_title\">Увімкнути жест потягування</string>\n    <string name=\"enable_pull_gesture_summary\">Використовуйте жест потягування для перемикання розділів у вебтуні</string>\n    <string name=\"two_page_scroll_sensitivity\">Чутливість прокручування на дві сторінки</string>\n    <string name=\"saved_filters\">Збережені фільтри</string>\n    <string name=\"enter_name\">Введіть ім\\'я</string>\n    <string name=\"reader_chapter_toast\">Показати спливаюче вікно зміни розділу</string>\n    <string name=\"reader_chapter_toast_summary\">Показувати спливаюче повідомлення з назвою розділу, коли її змінено</string>\n    <string name=\"rename\">Перейменувати</string>\n    <string name=\"save_filter\">Зберегти фільтр</string>\n    <string name=\"overwrite\">Перезаписати</string>\n    <string name=\"filter_overwrite_confirm\">Фільтр з назвою \\\"%s\\\" вже існує. Перезаписати його?</string>\n    <string name=\"storage_and_network\">Зберігання та мережа</string>\n    <string name=\"create_or_restore_backup\">Створення або відновлення резервної копії</string>\n    <string name=\"data_removal\">Вилучення даних</string>\n    <string name=\"privacy\">Конфіденційність</string>\n    <string name=\"source_broken_warning\">Це джерело манги позначено як непрацююче. Деякі функції можуть не працювати</string>\n    <string name=\"frequency_every_6_hours\">Кожні 6 годин</string>\n    <string name=\"download_default_directory\">Каталог за замовчуванням для завантаження манги</string>\n    <string name=\"private_app_directory_warning\">Цей каталог з усіма даними буде видалено, якщо ви видалите програму</string>\n    <string name=\"available_pattern\">Доступно %1$s</string>\n    <string name=\"auto_double_foldable\">Автоматичне двосторінкове розміщення на складному</string>\n    <string name=\"pinned_sources_only\">Тільки закріплені джерела</string>\n    <string name=\"hide_empty_sources\">Приховати порожні джерела</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v27/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<bool name=\"light_status_bar\">true</bool>\n\t<bool name=\"light_navigation_bar\">true</bool>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-v27/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<style name=\"Base.V27.Kotatsu\" parent=\"Base.Theme.Kotatsu\">\n\t\t<item name=\"android:windowLightNavigationBar\">@bool/light_navigation_bar</item>\n\t\t<item name=\"android:navigationBarColor\">@android:color/transparent</item>\n\t</style>\n\n\t<style name=\"Theme.Kotatsu\" parent=\"Base.V27.Kotatsu\" />\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v31/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<dimen name=\"appwidget_corner_radius_inner\">@android:dimen/system_app_widget_inner_radius</dimen>\n\t<dimen name=\"appwidget_corner_radius_background\">@android:dimen/system_app_widget_background_radius</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v31/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<style name=\"Base.V31.Kotatsu\" parent=\"Base.V27.Kotatsu\">\n\t\t<item name=\"android:windowSplashScreenAnimatedIcon\">@drawable/avd_splash</item>\n\t\t<item name=\"android:windowSplashScreenAnimationDuration\">@integer/splash_screen_duration</item>\n\t\t<item name=\"android:windowSplashScreenBackground\">@color/m3_sys_color_dynamic_light_surface</item>\n\t</style>\n\n\t<style name=\"Theme.Kotatsu\" parent=\"Base.V31.Kotatsu\" />\n\n\t<style name=\"Theme.Kotatsu.AppWidgetContainer\" parent=\"@android:style/Theme.DeviceDefault.DayNight\">\n\t\t<item name=\"android:colorBackground\">@color/m3_ref_palette_dynamic_secondary95</item>\n\t\t<item name=\"android:panelColorBackground\">@color/m3_ref_palette_dynamic_secondary90</item>\n\t\t<item name=\"colorTertiary\">@color/m3_ref_palette_dynamic_secondary70</item>\n\t</style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v33/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<bool name=\"com_samsung_android_icon_container_has_icon_container\">false</bool>\n\t<bool name=\"is_predictive_back_enabled\">true</bool>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-vi/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d Chương mới</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d chương</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d phút trước</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d giờ trước</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d ngày trước</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d mục</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d tháng trước</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d giờ</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d phút</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"text_feed_holder\">Các chương mới của truyện bạn đang đọc sẽ hiện thị ở đây</string>\n    <string name=\"local_storage\">Bộ nhớ cục bộ</string>\n    <string name=\"favourites\">Yêu thích</string>\n    <string name=\"network_error\">Lỗi mạng</string>\n    <string name=\"details\">Chi tiết</string>\n    <string name=\"chapters\">Chương</string>\n    <string name=\"list\">Danh sách</string>\n    <string name=\"settings\">Cài đặt</string>\n    <string name=\"remote_sources\">Nguồn truyện</string>\n    <string name=\"list_mode\">Chế độ hiển thị</string>\n    <string name=\"chapter_d_of_d\">Chương %1$d / %2$d</string>\n    <string name=\"close\">Đóng</string>\n    <string name=\"nothing_found\">Không tìm thấy kết quả</string>\n    <string name=\"history_is_empty\">Chưa có lịch sử</string>\n    <string name=\"you_have_not_favourites_yet\">Chưa có mục yêu thích nào</string>\n    <string name=\"add_to_favourites\">Thêm vào mục yêu thích</string>\n    <string name=\"add\">Thêm</string>\n    <string name=\"create_shortcut\">Tạo lối tắt</string>\n    <string name=\"share_s\">Chia sẻ %s</string>\n    <string name=\"search_manga\">Tìm kiếm manga</string>\n    <string name=\"manga_downloading_\">Đang tải xuống…</string>\n    <string name=\"by_name\">Tên</string>\n    <string name=\"updated\">Mới cập nhật</string>\n    <string name=\"newest\">Mới nhất</string>\n    <string name=\"by_rating\">Đánh giá</string>\n    <string name=\"filter\">Bộ lọc</string>\n    <string name=\"theme\">Chủ đề</string>\n    <string name=\"pages\">Danh sách trang</string>\n    <string name=\"clear\">Xoá</string>\n    <string name=\"remove\">Xoá</string>\n    <string name=\"save_page\">Lưu trang</string>\n    <string name=\"delete\">Xoá</string>\n    <string name=\"operation_not_supported\">Thao tác này không được hỗ trợ</string>\n    <string name=\"no_description\">Không có mô tả</string>\n    <string name=\"clear_pages_cache\">Dọn dẹp bộ nhớ đệm trang truyện</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">Tiêu chuẩn</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"read_mode\">Chế độ đọc</string>\n    <string name=\"grid_size\">Kích thước lưới</string>\n    <string name=\"search_on_s\">Tìm kiếm trên %s</string>\n    <string name=\"delete_manga\">Xoá truyện</string>\n    <string name=\"text_delete_local_manga\">Xoá \\\"%s\\\" vĩnh viễn khỏi thiết bị?</string>\n    <string name=\"reader_settings\">Cài đặt trình đọc</string>\n    <string name=\"switch_pages\">Chuyển trang</string>\n    <string name=\"clear_thumbs_cache\">Dọn dẹp bộ nhớ đệm ảnh bìa</string>\n    <string name=\"internal_storage\">Bộ nhớ trong</string>\n    <string name=\"external_storage\">Bộ nhớ ngoài</string>\n    <string name=\"domain\">Tên miền</string>\n    <string name=\"app_update_available\">Đã có phiên bản mới của ứng dụng</string>\n    <string name=\"open_in_browser\">Mở trong trình duyệt web</string>\n    <string name=\"notifications\">Thông báo</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">Đã bật %1$d trong số %2$d nguồn</string>\n    <string name=\"new_chapters\">Chương mới</string>\n    <string name=\"download\">Tải xuống</string>\n    <string name=\"notification_sound\">Âm thanh thông báo</string>\n    <string name=\"light_indicator\">Tín hiệu đèn LED thông báo</string>\n    <string name=\"vibration\">Rung</string>\n    <string name=\"favourites_categories\">Danh mục yêu thích</string>\n    <string name=\"remove_category\">Xoá</string>\n    <string name=\"text_local_holder_primary\">Lưu gì đó trước</string>\n    <string name=\"manga_shelf\">Tủ truyện</string>\n    <string name=\"pages_animation\">Hoạt ảnh chuyển trang</string>\n    <string name=\"manga_save_location\">Thư mục tải xuống</string>\n    <string name=\"not_available\">Không khả dụng</string>\n    <string name=\"cannot_find_available_storage\">Không có bộ nhớ khả dụng</string>\n    <string name=\"other_storage\">Bộ nhớ khác</string>\n    <string name=\"done\">Đã xong</string>\n    <string name=\"favourites_category_empty\">Danh mục trống</string>\n    <string name=\"read_later\">Đọc sau</string>\n    <string name=\"updates\">Cập nhật</string>\n    <string name=\"search_results\">Kết quả tìm kiếm</string>\n    <string name=\"size_s\">Kích thước: %s</string>\n    <string name=\"rotate_screen\">Xoay màn hình</string>\n    <string name=\"feed_will_update_soon\">Cập nhật danh sách sẽ bắt đầu sớm</string>\n    <string name=\"track_sources\">Kiểm tra tập / chương truyện mới</string>\n    <string name=\"dont_check\">Không kiểm tra</string>\n    <string name=\"enter_password\">Nhập mật khẩu</string>\n    <string name=\"protect_application_summary\">Hỏi mật khẩu khi khởi động Kotatsu</string>\n    <string name=\"repeat_password\">Nhập lại mật khẩu</string>\n    <string name=\"about\">Giới thiệu</string>\n    <string name=\"app_version\">Phiên bản %s</string>\n    <string name=\"check_for_updates\">Chạm để kiểm tra bản cập nhật mới</string>\n    <string name=\"no_update_available\">Không có bản cập nhật</string>\n    <string name=\"right_to_left\">Phải sang trái</string>\n    <string name=\"create_category\">Danh mục mới</string>\n    <string name=\"scale_mode\">Kiểu tỉ lệ</string>\n    <string name=\"zoom_mode_fit_center\">Vừa màn hình</string>\n    <string name=\"zoom_mode_fit_height\">Vừa chiều dọc</string>\n    <string name=\"zoom_mode_keep_start\">Giữ ở đầu</string>\n    <string name=\"black_dark_theme\">Nền đen</string>\n    <string name=\"backup_restore\">Sao lưu và khôi phục</string>\n    <string name=\"preparing_\">Đang chuẩn bị…</string>\n    <string name=\"backup_information\">Bạn có thể tạo bản sao lưu lịch sử, danh mục yêu thích và khôi phục nó sau này</string>\n    <string name=\"just_now\">Mới đây</string>\n    <string name=\"yesterday\">Hôm qua</string>\n    <string name=\"reader_mode_hint\">Cài đặt được chọn sẽ chỉ lưu lại cho truyện này</string>\n    <string name=\"silent\">Im lặng</string>\n    <string name=\"captcha_required\">Yêu cầu xác thực CAPTCHA</string>\n    <string name=\"captcha_solve\">Xác thực</string>\n    <string name=\"clear_cookies\">Xoá cookies</string>\n    <string name=\"check_for_new_chapters\">Kiểm tra các chương mới</string>\n    <string name=\"sign_in\">Đăng nhập</string>\n    <string name=\"auth_required\">Đăng nhập để xem nội dung này</string>\n    <string name=\"default_s\">Mặc định: %s</string>\n    <string name=\"next\">Kế tiếp</string>\n    <string name=\"protect_application_subtitle\">Hãy đặt mật khẩu để mở ứng dụng</string>\n    <string name=\"confirm\">Xác nhận</string>\n    <string name=\"password_length_hint\">Mật khẩu phải có 4 ký tự hoặc hơn</string>\n    <string name=\"text_clear_search_history_prompt\">Xoá vĩnh viễn tất cả lịch sử tìm kiếm gần đây\\?</string>\n    <string name=\"read_more\">Đọc thêm</string>\n    <string name=\"queued\">Trong hàng chờ</string>\n    <string name=\"chapter_is_missing\">Chương bị mất</string>\n    <string name=\"about_app_translation_summary\">Dịch ứng dụng này</string>\n    <string name=\"genres\">Thể loại</string>\n    <string name=\"state_finished\">Đã hoàn thành</string>\n    <string name=\"screenshots_policy\">Chính sách chụp ảnh màn hình</string>\n    <string name=\"screenshots_block_all\">Luôn luôn chặn</string>\n    <string name=\"suggestions_summary\">Gợi ý truyện dựa trên sở thích của bạn</string>\n    <string name=\"exclude_nsfw_from_suggestions\">Không đề xuất truyện NSFW</string>\n    <string name=\"enabled\">Đã bật</string>\n    <string name=\"preload_pages\">Tải trước trang</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">Nhiều ngôn ngữ</string>\n    <string name=\"search_chapters\">Tìm kiếm chương</string>\n    <string name=\"chapters_empty\">Không có chương nào trong truyện này</string>\n    <string name=\"appearance\">Giao diện</string>\n    <string name=\"suggestions_updating\">Đang cập nhật gợi ý</string>\n    <string name=\"suggestions_excluded_genres\">Lọc bỏ những thể loại</string>\n    <string name=\"suggestions_excluded_genres_summary\">Liệt kê những thể loại mà bạn không muốn thấy trong phần gợi ý</string>\n    <string name=\"back\">Quay lại</string>\n    <string name=\"download_slowdown_summary\">Giúp tránh việc ip của bạn bị chặn</string>\n    <string name=\"chapters_will_removed_background\">Các chương sẽ bị xoá dưới nền</string>\n    <string name=\"sync_title\">Đồng bộ dữ liệu của bạn</string>\n    <string name=\"email_enter_hint\">Nhập email của bạn để tiếp tục</string>\n    <string name=\"new_sources_text\">Đã có các nguồn truyện mới</string>\n    <string name=\"check_new_chapters_title\">Kiểm tra và thông báo về các chương mới</string>\n    <string name=\"show_notification_new_chapters_off\">Bạn sẽ không nhận thông báo nhưng những chương mới sẽ được đánh đấu trong danh sách</string>\n    <string name=\"notifications_enable\">Bật thông báo</string>\n    <string name=\"logout\">Đăng xuất</string>\n    <string name=\"bookmarks\">Đánh dấu trang</string>\n    <string name=\"bookmark_added\">Đã thêm đánh dấu trang</string>\n    <string name=\"undo\">Hoàn tác</string>\n    <string name=\"removed_from_history\">Đã xoá khỏi lịch sử</string>\n    <string name=\"dns_over_https\">DNS trên HTTPS</string>\n    <string name=\"send\">Gửi</string>\n    <string name=\"status_reading\">Đang đọc</string>\n    <string name=\"appwidget_recent_description\">Truyện bạn đã đọc gần đây</string>\n    <string name=\"report\">Báo cáo</string>\n    <string name=\"show_reading_indicators\">Hiển thị thanh tiến độ đọc</string>\n    <string name=\"no_bookmarks_yet\">Chưa có đánh dấu nào</string>\n    <string name=\"categories_delete_confirm\">Bạn có chắc muốn xoá danh mục yêu thích đã chọn không? \\nToàn bộ truyện trong mục sẽ bị mất và không thể khôi phục.</string>\n    <string name=\"bookmarks_removed\">Đã xóa dấu trang</string>\n    <string name=\"no_manga_sources\">Không có nguồn truyện nào</string>\n    <string name=\"exit_confirmation\">Xác nhận thoát</string>\n    <string name=\"reorder\">Sắp xếp lại</string>\n    <string name=\"explore\">Khám phá</string>\n    <string name=\"empty\">Trống</string>\n    <string name=\"importing_manga\">Nhập truyện</string>\n    <string name=\"history_shortcuts_summary\">Hiển thị truyện đọc gần đây bằng cách ấn giữ biểu tượng ứng dụng</string>\n    <string name=\"color_correction\">Điều chỉnh màu</string>\n    <string name=\"brightness\">Độ sáng</string>\n    <string name=\"discard\">Huỷ bỏ</string>\n    <string name=\"network_unavailable\">Không có mạng</string>\n    <string name=\"network_unavailable_hint\">Bật wifi hoặc dữ liệu di động để đọc truyện trực tuyến</string>\n    <string name=\"prefetch_content\">Tải trước nội dung</string>\n    <string name=\"mark_as_current\">Đánh dấu là hiện tại đã đọc xong</string>\n    <string name=\"enable_logging_summary\">Lưu một số thông tin cho mục đích gỡ lỗi. Đừng bật nó nếu bạn không biết mình đang làm gì</string>\n    <string name=\"show_suspicious_content\">Hiển thị nội dung khả nghi</string>\n    <string name=\"theme_name_dynamic\">Động</string>\n    <string name=\"error_occurred\">Đã xảy ra lỗi</string>\n    <string name=\"history\">Lịch sử</string>\n    <string name=\"grid\">Dạng lưới</string>\n    <string name=\"detailed_list\">Danh sách chi tiết</string>\n    <string name=\"computing_\">Đang tính toán…</string>\n    <string name=\"loading_\">Đang tải…</string>\n    <string name=\"try_again\">Thử lại</string>\n    <string name=\"clear_history\">Xoá lịch sử</string>\n    <string name=\"read\">Đọc</string>\n    <string name=\"share\">Chia sẻ</string>\n    <string name=\"download_complete\">Đã tải xuống</string>\n    <string name=\"save\">Lưu</string>\n    <string name=\"add_new_category\">Danh mục mới</string>\n    <string name=\"search\">Tìm kiếm</string>\n    <string name=\"processing_\">Đang xử lý…</string>\n    <string name=\"downloads\">Danh sách tải xuống</string>\n    <string name=\"popular\">Phổ biến</string>\n    <string name=\"sort_order\">Sắp xếp theo</string>\n    <string name=\"dark\">Tối</string>\n    <string name=\"light\">Sáng</string>\n    <string name=\"follow_system\">Theo hệ thống</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" đã bị xoá khỏi bộ nhớ cục bộ</string>\n    <string name=\"share_image\">Chia sẻ hình ảnh</string>\n    <string name=\"page_saved\">Đã lưu trang này</string>\n    <string name=\"text_file_not_supported\">Vui lòng chọn tập tin ZIP hoặc CBZ.</string>\n    <string name=\"error\">Lỗi</string>\n    <string name=\"_continue\">Tiếp tục</string>\n    <string name=\"recent_manga\">Gần đây</string>\n    <string name=\"screenshots_allow\">Cho phép</string>\n    <string name=\"suggestions_info\">Tất cả dữ liệu chỉ được phân tích cục bộ trên thiết bị này và không bao giờ gửi đi nơi nào khác.</string>\n    <string name=\"all_favourites\">Tất cả danh mục yêu thích</string>\n    <string name=\"new_version_s\">Phiên bản mới: %s</string>\n    <string name=\"clear_updates_feed\">Xoá danh sách cập nhật</string>\n    <string name=\"updates_feed_cleared\">Đã xoá</string>\n    <string name=\"update\">Cập nhật</string>\n    <string name=\"wrong_password\">Sai mật khẩu</string>\n    <string name=\"protect_application\">Bảo vệ ứng dụng</string>\n    <string name=\"passwords_mismatch\">Mật khẩu không trùng</string>\n    <string name=\"zoom_mode_fit_width\">Vừa chiều ngang</string>\n    <string name=\"exclude_nsfw_from_history\">Loại bỏ truyện NSFW khỏi lịch sử</string>\n    <string name=\"show_pages_numbers\">Đánh số trang</string>\n    <string name=\"data_restored_success\">Tất cả dữ liệu đã được khôi phục</string>\n    <string name=\"black_dark_theme_summary\">Sử dụng ít năng lượng hơn trên màn hình AMOLED</string>\n    <string name=\"file_not_found\">Không tìm thấy tài liệu</string>\n    <string name=\"create_backup\">Tạo bản sao lưu</string>\n    <string name=\"cookies_cleared\">Đã xoá tất cả cookies</string>\n    <string name=\"restore_backup\">Khôi phục từ bản sao lưu</string>\n    <string name=\"data_restored\">Đã khôi phục</string>\n    <string name=\"data_restored_with_errors\">Dữ liệu đã được khôi phục, nhưng có một số lỗi</string>\n    <string name=\"text_clear_updates_feed_prompt\">Xoá vĩnh viễn tất cả lịch sử cập nhật\\?</string>\n    <string name=\"long_ago\">Từ lâu</string>\n    <string name=\"group\">Nhóm</string>\n    <string name=\"today\">Hôm nay</string>\n    <string name=\"tap_to_try_again\">Nhấn để thử lại</string>\n    <string name=\"clear_feed\">Xoá danh sách</string>\n    <string name=\"reverse\">Đảo ngược</string>\n    <string name=\"backup_saved\">Đã lưu bản sao lưu</string>\n    <string name=\"welcome\">Chào mừng</string>\n    <string name=\"about_app_translation\">Dịch thuật</string>\n    <string name=\"tracker_warning\">Một số thiết bị có hành vi hệ thống khác nhau, có thể phá hỏng các tác vụ chạy ngầm.</string>\n    <string name=\"state_ongoing\">Đang tiến hành</string>\n    <string name=\"system_default\">Mặc định</string>\n    <string name=\"text_clear_cookies_prompt\">Bạn sẽ bị đăng xuất khỏi tất cả các nguồn</string>\n    <string name=\"clear_search_history\">Xoá lịch sử tìm kiếm</string>\n    <string name=\"search_history_cleared\">Đã xoá</string>\n    <string name=\"text_empty_holder_primary\">Không có gì ở đây…</string>\n    <string name=\"notifications_settings\">Cài đặt thông báo</string>\n    <string name=\"text_search_holder_secondary\">Hãy thử lại với từ khoá khác.</string>\n    <string name=\"text_history_holder_primary\">Những gì bạn đã đọc sẽ được hiển thị ở đây ツ</string>\n    <string name=\"text_local_holder_secondary\">Tải xuống từ nguồn online hoặc nhập từ tập tin.</string>\n    <string name=\"text_suggestion_holder\">Bắt đầu đọc truyện và bạn sẽ nhận được những gợi ý cá nhân hoá</string>\n    <string name=\"detect_reader_mode\">Tự động phát hiện chế độ đọc</string>\n    <string name=\"never\">Không bao giờ</string>\n    <string name=\"hide\">Ẩn</string>\n    <string name=\"sync\">Đồng bộ hoá</string>\n    <string name=\"disabled\">Đã tắt</string>\n    <string name=\"onboard_text\">Chọn những ngôn ngữ mà bạn muốn đọc. Bạn có thể thay đổi nó sau trong phần cài đặt.</string>\n    <string name=\"reset_filter\">Đặt lại bộ lọc</string>\n    <string name=\"only_using_wifi\">Chỉ sử dụng Wi-Fi</string>\n    <string name=\"always\">Luôn luôn</string>\n    <string name=\"suggestions\">Gợi ý</string>\n    <string name=\"suggestions_enable\">Bật gợi ý truyện</string>\n    <string name=\"edit\">Chỉnh sửa</string>\n    <string name=\"screenshots_block_nsfw\">Chặn trên các truyện NSFW</string>\n    <string name=\"name\">Tên</string>\n    <string name=\"edit_category\">Chỉnh sửa danh sách</string>\n    <string name=\"show_reading_indicators_summary\">Hiện phần trăm đã đọc trong lịch sử và danh mục yêu thích</string>\n    <string name=\"empty_favourite_categories\">Không có danh mục yêu thích nào</string>\n    <string name=\"bookmark_remove\">Xoá đánh dấu trang</string>\n    <string name=\"bookmark_removed\">Đánh dấu đã được xoá</string>\n    <string name=\"saved_manga\">Truyện đã lưu</string>\n    <string name=\"bookmark_add\">Thêm đánh dấu trang</string>\n    <string name=\"available\">Khả dụng</string>\n    <string name=\"detect_reader_mode_summary\">Tự động phát hiện nếu truyện là webtoon</string>\n    <string name=\"other_cache\">Bộ nhớ đệm khác</string>\n    <string name=\"storage_usage\">Bộ nhớ sử dụng</string>\n    <string name=\"default_mode\">Chế độ mặc định</string>\n    <string name=\"canceled\">Đã huỷ</string>\n    <string name=\"account_already_exists\">Tài khoản đã tồn tại</string>\n    <string name=\"disable_battery_optimization\">Tắt tối ưu pin</string>\n    <string name=\"pages_cache\">Bộ nhớ đệm trang truyện</string>\n    <string name=\"server_error\">Lỗi server (%1$d). Vui lòng thử lại sau</string>\n    <string name=\"exit_confirmation_summary\">Nhấn quay lại hai lần để thoát ứng dụng</string>\n    <string name=\"show_notification_new_chapters_on\">Bạn sẽ nhận được thông báo cập nhật của những truyện mà bạn đang đọc</string>\n    <string name=\"tracking\">Theo dõi</string>\n    <string name=\"exclude_nsfw_from_history_summary\">Truyện được đánh dấu là NSFW sẽ không bao giờ được thêm vào lịch sử và tiến độ đọc của bạn sẽ không được lưu lại</string>\n    <string name=\"show_all\">Hiện tất cả</string>\n    <string name=\"confirm_exit\">Nhấn back lần nữa để thoát</string>\n    <string name=\"clear_all_history\">Xoá toàn bộ lịch sử</string>\n    <string name=\"invalid_domain_message\">Tên miền không hợp lệ</string>\n    <string name=\"select_range\">Chọn phạm vi</string>\n    <string name=\"last_2_hours\">2 giờ qua</string>\n    <string name=\"history_cleared\">Lịch sử đã được xoá</string>\n    <string name=\"no_bookmarks_summary\">Bạn có thể tạo đánh dấu khi đang đọc truyện</string>\n    <string name=\"no_manga_sources_text\">Bật các nguồn truyện để đọc truyện trực tuyến</string>\n    <string name=\"random\">Ngẫu nhiên</string>\n    <string name=\"status_re_reading\">Đọc lại</string>\n    <string name=\"no_chapters\">Không có chương nào</string>\n    <string name=\"crash_text\">Có gì đó không đúng. Vui lòng gửi một báo cáo lỗi cho các nhà phát triển để giúp chúng tôi sửa nó.</string>\n    <string name=\"status_on_hold\">Tạm ngưng</string>\n    <string name=\"status_dropped\">Đã ngưng</string>\n    <string name=\"appwidget_shelf_description\">Manga từ các mục yêu thích của bạn</string>\n    <string name=\"disable_all\">Tắt tất cả</string>\n    <string name=\"status_completed\">Đã hoàn thành</string>\n    <string name=\"clear_cookies_summary\">Có thể giúp ích trong trường hợp có vấn đề. Mọi uỷ quyền sẽ bị vô hiệu hoá</string>\n    <string name=\"manage\">Quản lý</string>\n    <string name=\"use_fingerprint\">Sử dụng vân tay / sinh trắc học (nếu có)</string>\n    <string name=\"data_deletion\">Xoá dữ liệu</string>\n    <string name=\"removed_from_favourites\">Đã xoá khỏi danh mục yêu thích</string>\n    <string name=\"error_no_space_left\">Không đủ dung lượng lưu trữ trên thiết bị</string>\n    <string name=\"options\">Tuỳ chọn</string>\n    <string name=\"not_found_404\">Nội dung không khả dụng hoặc đã bị loại bỏ</string>\n    <string name=\"incognito_mode\">Chế độ ẩn danh</string>\n    <string name=\"automatic_scroll\">Tự động cuộn</string>\n    <string name=\"import_will_start_soon\">Quá trình nhập sẽ bắt đầu sớm</string>\n    <string name=\"reader_info_bar\">Hiện thanh thông tin truyện trong trình đọc</string>\n    <string name=\"folder_with_images\">Từ thư mục chứa hình ảnh / dữ liệu truyện</string>\n    <string name=\"import_completed\">Nhập hoàn tất</string>\n    <string name=\"import_completed_hint\">Bạn có thể xoá file gốc khỏi bộ nhớ để tiết kiệm dung lượng</string>\n    <string name=\"reset\">Đặt lại</string>\n    <string name=\"feed\">Bảng tin</string>\n    <string name=\"reader_control_ltr_summary\">Chạm vào bên phải màn hình hoặc nhấn nút mũi tên bên phải để sang trang kế tiếp. Điều này phụ thuộc vào thiết bị / giả lập bạn đang dùng (Không điều chỉnh hướng với chế độ đọc)</string>\n    <string name=\"history_shortcuts\">Hiển thị lối tắt truyện đọc gần đây</string>\n    <string name=\"contrast\">Tương phản</string>\n    <string name=\"reader_slider\">Hiện thanh trượt chuyển trang</string>\n    <string name=\"webtoon_zoom\">Phóng to truyện Webtoon</string>\n    <string name=\"show_in_grid_view\">Hiển thị dưới dạng lưới</string>\n    <string name=\"share_logs\">Chia sẻ logs</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"enable_logging\">Bật log</string>\n    <string name=\"color_theme\">Bảng màu</string>\n    <string name=\"nothing_here\">Không có gì ở đây cả</string>\n    <string name=\"language\">Ngôn ngữ</string>\n    <string name=\"compact\">Nhỏ gọn</string>\n    <string name=\"services\">Dịch vụ</string>\n    <string name=\"allow_unstable_updates\">Cho phép các bản cập nhật không ổn định</string>\n    <string name=\"_import\">Nhập</string>\n    <string name=\"text_unsaved_changes_prompt\">Lưu hoặc loại bỏ những thay đổi chưa được lưu?</string>\n    <string name=\"download_started\">Tải về đã bắt đầu</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"got_it\">Đã hiểu</string>\n    <string name=\"find_similar\">Tìm truyện giống nhau</string>\n    <string name=\"sync_auth_hint\">Đăng nhập vào tài khoản hoặc tạo một tài khoản mới</string>\n    <string name=\"sync_settings\">Cài đặt đồng bộ hoá</string>\n    <string name=\"speed\">Tốc độ</string>\n    <string name=\"pause\">Tạm dừng</string>\n    <string name=\"suggestion_manga\">Đề xuất: %s</string>\n    <string name=\"suggestions_notifications_summary\">Nhận thông báo về manga được đề xuất</string>\n    <string name=\"enable\">Kích hoạt</string>\n    <string name=\"cancel_all_downloads_confirm\">Tất cả tải xuống đang hoạt động sẽ bị huỷ bỏ, dữ liệu chưa hoàn tất tải xuống sẽ bị mất</string>\n    <string name=\"downloads_paused\">Đã tạm dừng tải xuống</string>\n    <string name=\"text_downloads_list_holder\">Bạn chưa tải xuống bộ truyện nào</string>\n    <string name=\"downloads_cancelled\">Đã huỷ tải xuống</string>\n    <string name=\"suggestions_enable_prompt\">Bạn có muốn nhận manga đề xuất đã cá nhân hóa không?</string>\n    <string name=\"port\">Cổng</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"downloaded\">Đã tải xuống</string>\n    <string name=\"password\">Mật khẩu</string>\n    <string name=\"username\">Tên người dùng</string>\n    <string name=\"invalid_value_message\">Giá trị không hợp lệ</string>\n    <string name=\"webtoon_zoom_summary\">Cho phép cử chỉ phóng to ở chế độ Webtoon</string>\n    <string name=\"data_and_privacy\">Dữ liệu và quyền riêng tư</string>\n    <string name=\"show_pages_numbers_summary\">Hiển thị số trang ở góc bên dưới</string>\n    <string name=\"clear_source_cookies_summary\">Xoá cookies chỉ cho tên miền này. Trong hầu hết mọi trường hợp sẽ làm mất hiệu lực xác thực</string>\n    <string name=\"ignore_ssl_errors\">Bỏ qua lỗi SSL</string>\n    <string name=\"sources_reorder_tip\">Chạm và giữ các nguồn truyện để sắp xếp chúng</string>\n    <string name=\"settings_apply_restart_required\">Vui lòng khởi động lại ứng dụng để áp dụng những thay đổi trên</string>\n    <string name=\"server_address\">Địa chỉ server</string>\n    <string name=\"comics_archive_import_description\">Bạn có thể chọn một hoặc nhiều file .cbz hoặc .zip, mỗi file sẽ được nhận dạng như một manga riêng biệt.</string>\n    <string name=\"cancel_all\">Huỷ bỏ tất cả</string>\n    <string name=\"downloads_wifi_only\">Chỉ tải xuống qua Wi-Fi</string>\n    <string name=\"downloads_wifi_only_summary\">Ngừng tải xuống khi chuyển qua mạng dữ liệu</string>\n    <string name=\"remove_completed_downloads_confirm\">Lịch sử tải xuống của bạn sẽ bị xoá vĩnh viễn. Các tệp đã tải về máy sẽ không bị xóa</string>\n    <string name=\"address\">Địa chỉ</string>\n    <string name=\"invert_colors\">Đảo màu</string>\n    <string name=\"invalid_port_number\">Cổng không hợp lệ</string>\n    <string name=\"download_option_manual_selection\">Chọn thủ công các chương</string>\n    <string name=\"download_option_all_unread\">Tất cả các chương chưa đọc</string>\n    <string name=\"resume\">Tiếp tục</string>\n    <string name=\"clear_new_chapters_counters\">Xoá thông tin về chương mới</string>\n    <string name=\"source_disabled\">Đã vô hiệu hoá nguồn truyện</string>\n    <string name=\"sync_host_description\">Bạn có thể dùng một máy chủ đồng bộ hoá của bạn (tự Host) hoặc máy chủ đồng bộ hoá mặc định. Đừng thay đổi địa chủ máy chủ nếu bạn không biết mình đang làm gì.</string>\n    <string name=\"downloads_resumed\">Tải xuống đã được tiếp tục</string>\n    <string name=\"downloads_removed\">Đã xoá tải xuống</string>\n    <string name=\"more\">Thêm</string>\n    <string name=\"no_thanks\">Không, cảm ơn</string>\n    <string name=\"download_option_all_chapters\">Số chương đã được dịch %s</string>\n    <string name=\"auth_not_supported_by\">Đăng nhập trên %s không được hỗ trợ</string>\n    <string name=\"logged_in_as\">Đang đăng nhập với tư cách là %s</string>\n    <string name=\"removal_completed\">Xoá hoàn thành</string>\n    <string name=\"download_slowdown\">Làm chậm tải xuống</string>\n    <string name=\"local_manga_processing\">Xử lý các truyện được lưu</string>\n    <string name=\"text_delete_local_manga_batch\">Xoá vĩnh viễn những thứ được chọn khỏi thiết bị\\?</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"disable_battery_optimization_summary\">Giúp kiểm tra chương mới trong khi chạy dưới nền dễ dàng hơn</string>\n    <string name=\"show_on_shelf\">Hiển thị trên kệ sách</string>\n    <string name=\"mirror_switching\">Tự động chuyển sang dự phòng</string>\n    <string name=\"mirror_switching_summary\">Tự động chuyển sang tên miền dự phòng của nguồn đọc (nếu có) khi gặp lỗi</string>\n    <string name=\"paused\">Đã tạm dừng</string>\n    <string name=\"images_proxy_title\">Proxy tối ưu hình ảnh</string>\n    <string name=\"images_procy_description\">Sử dụng dịch vụ wsrv.nl để giảm dung lượng ảnh và tăng tốc quá trình tải ảnh nếu khả thi</string>\n    <string name=\"restore_summary\">Khôi phục từ bản sao lưu đã được tạo trước đó</string>\n    <string name=\"reader_info_bar_summary\">Hiển thị thời gian hiện tại và tiến trình đọc ở góc bên trên màn hình</string>\n    <string name=\"download_option_whole_manga\">Toàn bộ truyện</string>\n    <string name=\"download_option_all_unread_b\">Tất cả các chương chưa đọc (%s)</string>\n    <string name=\"no_access_to_file\">Bạn không có quyền truy cập vào tập tin hoặc thư mục này</string>\n    <string name=\"local_manga_directories\">Thư mục truyện tên thiết bị</string>\n    <string name=\"pick_custom_directory\">Chọn thư mục tuỳ chỉnh</string>\n    <string name=\"reader_info_pattern\">Chương %1$d/%2$d - Trang %3$d/%4$d</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"web_view_unavailable\">WebView hiện không có sẵn: Kiểm tra xem ứng dụng WebView được cài chưa</string>\n    <string name=\"manga_error_description_pattern\">Chi tiết lỗi: &lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Hãy thử &lt;a href=%2$s&gt;mở manga trong trình duyệt web&lt;/a&gt; để đảm bảo rằng manga này có trong nguồn&lt;br&gt;2. Hãy chắc chắn rằng bạn đang dùng &lt;a href=kotatsu://about&gt;bản mới nhất của Kotatsu&lt;/a&gt;&lt;br&gt;3. Nếu đã đáp ứng các yêu cầu trên, gửi báo cáo lỗi lên nhà phát triển.</string>\n    <string name=\"text_history_holder_secondary\">Tìm truyện để đọc trong phần «Khám phá»</string>\n    <string name=\"this_month\">Tháng này</string>\n    <string name=\"tracker_wifi_only_summary\">Không kiểm tra các chương mới bằng kết nối mạng có đo lường</string>\n    <string name=\"user_agent\">Tùy chỉnh User-Agent</string>\n    <string name=\"data_not_restored_text\">Đảm bảo bạn đã chọn đúng tệp sao lưu</string>\n    <string name=\"auth_complete\">Được ủy quyền</string>\n    <string name=\"data_not_restored\">Dữ liệu chưa được khôi phục</string>\n    <string name=\"manage_categories\">Quản lý danh mục</string>\n    <string name=\"scrobbling_empty_hint\">Để theo dõi tiến trình đọc, chọn Danh sách → Theo dõi trên màn hình chi tiết manga.</string>\n    <string name=\"color_light\">Sáng</string>\n    <string name=\"color_dark\">Tối</string>\n    <string name=\"allow_unstable_updates_summary\">Nhận thông báo về các bản dựng không ổn định</string>\n    <string name=\"related_manga\">Truyện tranh liên quan</string>\n    <string name=\"suggestions_wifi_only_summary\">Không cập nhật đề xuất bằng kết nối mạng có đo lường</string>\n    <string name=\"comics_archive\">Từ tệp đã nén dữ liệu truyện</string>\n    <string name=\"folder_with_images_import_description\">Bạn có thể chọn một thư mục để làm kho lưu trữ / hình ảnh. Mỗi kho lưu trữ (hoặc thư mục con) sẽ được coi là một chương.</string>\n    <string name=\"voice_search\">Tìm kiếm bằng giọng nói</string>\n    <string name=\"reader_control_ltr\">Điều khiển trình đọc tiện dụng</string>\n    <string name=\"color_white\">Trắng</string>\n    <string name=\"status_planned\">Đã lên kế hoạch</string>\n    <string name=\"color_black\">Đen</string>\n    <string name=\"volume_\">Tập %d</string>\n    <string name=\"volume_unknown\">Không rõ tập</string>\n    <string name=\"remove_from_history\">Xoá khỏi lịch sử</string>\n    <string name=\"restore\">Khôi phục</string>\n    <string name=\"incognito_mode_hint\">Tiến trình đọc của bản sẽ không được lưu</string>\n    <string name=\"rating_adult\">Người lớn</string>\n    <string name=\"rating_suggestive\">Khiêu gợi</string>\n    <string name=\"minutes_short\">%d phút</string>\n    <string name=\"hours_short\">%d giờ</string>\n    <string name=\"hours_minutes_short\">%1$d giờ %2$d phút</string>\n    <string name=\"vertical\">Hướng dọc</string>\n    <string name=\"chapters_grid_view\">Chế độ xem lưới</string>\n    <string name=\"remove_completed\">Xoá hoàn tất</string>\n    <string name=\"search_hint\">Nhập tên manga, thể loại hoặc tên nguồn</string>\n    <string name=\"clear_network_cache\">Xoá cache (Cache của mạng)</string>\n    <string name=\"type\">Thể loại</string>\n    <string name=\"suggest_new_sources\">Gợi ý nguồn mới sau khi cập nhật ứng dụng</string>\n    <string name=\"manga_list\">Danh sách manga</string>\n    <string name=\"list_options\">Tuỳ chọn danh sách</string>\n    <string name=\"suggest_new_sources_summary\">Hiện cửa sổ bật các nguồn mới sau khi cập nhật ứng dụng</string>\n    <string name=\"by_relevance\">Liên quan</string>\n    <string name=\"periodic_backups\">Sao lưu định kì</string>\n    <string name=\"online_variant\">Xem chi tiết với nguồn</string>\n    <string name=\"frequency_once_per_week\">Một lần mỗi tuần</string>\n    <string name=\"frequency_twice_per_month\">Hai lần mỗi tháng</string>\n    <string name=\"categories\">Thể loại</string>\n    <string name=\"catalog\">Danh mục</string>\n    <string name=\"lock_screen_rotation\">Khoá hướng màn hình</string>\n    <string name=\"content_type_manga\">Manga</string>\n    <string name=\"content_type_hentai\">Hentai</string>\n    <string name=\"content_type_comics\">Comics</string>\n    <string name=\"disable_nsfw_summary\">Tắt các nguồn NSFW và ẩn các manga người lớn từ danh sách (nếu khả thi)</string>\n    <string name=\"reader_optimize\">Giảm tiêu tốn bộ nhớ (beta)</string>\n    <string name=\"reader_optimize_summary\">Giảm chất lượng các trang chưa hiển thị để dùng ít bộ nhớ hơn</string>\n    <string name=\"manage_sources\">Nguồn manga</string>\n    <string name=\"available_d\">Hiện đang có sẵn: %1$d</string>\n    <string name=\"genres_search_hint\">Nhập tên thể loại</string>\n    <string name=\"backup_date_\">Ngày sao lưu: %s</string>\n    <string name=\"color_correction_apply_text\">Cài đặt có thể được áp dụng cho toàn bộ nguồn hoặc chỉ nguồn manga này. Nếu áp dụng cho toàn bộ, các nguồn đã cài đặt riêng không bị ảnh hưởng.</string>\n    <string name=\"error_filter_states_genre_not_supported\">Lọc với thể loại và trạng thái không được hỗ trợ bởi nguồn này</string>\n    <string name=\"reader_fullscreen_summary\">Ẩn thanh thông báo và nút điều hướng</string>\n    <string name=\"switch_pages_volume_buttons_summary\">Dùng nút âm lượng để chuyển trang</string>\n    <string name=\"default_webtoon_zoom_out\">Thu nhỏ mặc định webtoon</string>\n    <string name=\"show_labels_in_navbar\">Hiển thị tên trong thanh điều hướng</string>\n    <string name=\"pages_saving\">Lưu trang</string>\n    <string name=\"location\">Địa chỉ</string>\n    <string name=\"automatic\">Tự động</string>\n    <string name=\"suggestions_unavailable_text\">Tính năng gợi ý đã tắt</string>\n    <string name=\"clear_stats\">Xoá thống kê</string>\n    <string name=\"stats_cleared\">Đã xoá thống kê</string>\n    <string name=\"clear_stats_confirm\">Bạn có muốn xoá hết thống kê đọc không? Hành động này không thể hoàn tác</string>\n    <string name=\"less_than_minute\">Ít hơn một phút trước</string>\n    <string name=\"month\">Tháng</string>\n    <string name=\"all_time\">Toàn thời gian</string>\n    <string name=\"three_months\">Ba tháng</string>\n    <string name=\"week\">Tuần</string>\n    <string name=\"day\">Ngày</string>\n    <string name=\"alternatives\">Thay thế</string>\n    <string name=\"empty_stats_text\">Không có thống kê cho khoảng thời gian đã chọn</string>\n    <string name=\"migrate_confirmation\">Manga \\\"%1$s\\\" từ \\\"%2$s\\\" sẽ bị thay thế bởi \\\"%3$s\\\" từ \\\"%4$s\\\" trong lịch sử và yêu tích (nếu có)</string>\n    <string name=\"manga_migration\">Sáp nhập manga</string>\n    <string name=\"migrate\">Sáp nhập</string>\n    <string name=\"delete_read_chapters\">Xoá các chương đã đọc</string>\n    <string name=\"no_chapters_deleted\">Không có chương nào bị xoá</string>\n    <string name=\"migration_completed\">Sáp nhập thành công</string>\n    <string name=\"delete_read_chapters_prompt\">Hành động này sẽ xoá tất cả các chương đã đọc trong bộ nhớ trong. Bạn có thể tải lại sau, nhưng những chương đã xoá có thể mất vĩnh viễn.</string>\n    <string name=\"email_password_enter_hint\">Nhập email và mật khẩu để tiếp tục</string>\n    <string name=\"unknown\">Không rõ</string>\n    <string name=\"show\">Xem</string>\n    <string name=\"captcha_required_summary\">%s yêu cầu giải captcha để hoạt động bình thường</string>\n    <string name=\"in_progress\">Đang tiến hành</string>\n    <string name=\"disable_nsfw\">Vô hiệu hóa đối với NSFW</string>\n    <string name=\"advanced\">Nâng cao</string>\n    <string name=\"error_corrupted_file\">Dữ liệu trả lại không hợp lệ hoặc file đã bị hỏng</string>\n    <string name=\"related_manga_summary\">Hiển thị danh sách manga có liên quan. Đôi khi có thể không có hoặc không liên quan</string>\n    <string name=\"too_many_requests_message\">Yêu cầu quá nhiều. Hãy thoát ra và vào lại ¯\\\\_(ツ)_/¯</string>\n    <string name=\"on_device\">Trên máy</string>\n    <string name=\"main_screen_sections\">Phần màn hình chính</string>\n    <string name=\"directories\">Đường dẫn</string>\n    <string name=\"items_limit_exceeded\">Không thể thêm mục nữa</string>\n    <string name=\"zoom_out\">Thu nhỏ</string>\n    <string name=\"zoom_in\">Phóng to</string>\n    <string name=\"to_top\">Lên đầu</string>\n    <string name=\"moved_to_top\">Chuyển lên đầu</string>\n    <string name=\"reader_zoom_buttons\">Hiển thị nút thu phóng</string>\n    <string name=\"reader_zoom_buttons_summary\">Hiển thị nút thu phóng ở góc dưới bên phải màn hình</string>\n    <string name=\"keep_screen_on_summary\">Không tắt màn hình khi bạn đang đọc manga</string>\n    <string name=\"keep_screen_on\">Giữ màn hình luôn sáng</string>\n    <string name=\"state_abandoned\">Đã huỷ</string>\n    <string name=\"enhanced_colors_summary\">Giảm hiệu ứng dải, nhưng có thể ảnh hưởng đến hiệu suất</string>\n    <string name=\"enhanced_colors\">Chế độ màu 32-bit</string>\n    <string name=\"frequency_every_day\">Hàng ngày</string>\n    <string name=\"backup_frequency\">Tần suất tạo bản sao lưu</string>\n    <string name=\"frequency_every_2_days\">2 ngày</string>\n    <string name=\"backups_output_directory\">Sao lưu thư mục xuất</string>\n    <string name=\"periodic_backups_enable\">Bật sao lưu định kì</string>\n    <string name=\"last_successful_backup\">Lần sao lưu thành công gần nhất: %s</string>\n    <string name=\"source_enabled\">Nguồn đã bật</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"content_type_other\">Khác</string>\n    <string name=\"sources_catalog\">Danh mục nguồn</string>\n    <string name=\"no_manga_sources_catalog_text\">Không còn nguồn nào cả hoặc đã bật hết các nguồn.\n\\nHãy chờ bản cập nhật sau</string>\n    <string name=\"state_paused\">Tạm dừng</string>\n    <string name=\"error_multiple_genres_not_supported\">Lọc với nhiều thể loại không được hỗ trợ trên nguồn truyện này</string>\n    <string name=\"error_multiple_states_not_supported\">Lọc với nhiều trạng thái không được hỗ trợ bởi nguồn này</string>\n    <string name=\"grayscale\">Thang đo xám (Grayscale)</string>\n    <string name=\"error_search_not_supported\">Tìm kiếm không được hỗ trợ bởi nguồn này</string>\n    <string name=\"globally\">Toàn bộ</string>\n    <string name=\"this_manga\">Chỉ manga này</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">Có thể giúp bắt đầu tải xuống nếu bạn có vấn đề về tải xuống</string>\n    <string name=\"sync_auth\">Đăng nhập vào tài khoản đồng bộ</string>\n    <string name=\"by_name_reverse\">Theo tên (ngược)</string>\n    <string name=\"content_rating\">Đánh giá</string>\n    <string name=\"state_upcoming\">Sắp ra mắt</string>\n    <string name=\"rating_safe\">An toàn</string>\n    <string name=\"default_tab\">Tab mặc định</string>\n    <string name=\"mark_as_completed\">Đánh dấu đã hoàn thành</string>\n    <string name=\"mark_as_completed_prompt\">Đánh dấu manga đã chọn là manga đã đọc xong? \\n \\nCảnh báo: Tiến trình đọc hiện tại của bạn sẽ bị xoá và sẽ được đánh dấu là đã hoàn thành.</string>\n    <string name=\"next_chapter\">Chương tiếp theo</string>\n    <string name=\"prev_page\">Trang trước</string>\n    <string name=\"next_page\">Trang tiếp theo</string>\n    <string name=\"reader_actions\">Thay đổi cử chỉ của người dùng trên giao diện đọc</string>\n    <string name=\"reader_actions_summary\">Chỉnh sửa các hành động / cử chỉ cho các phần chạm của màn hình đọc</string>\n    <string name=\"switch_pages_volume_buttons\">Bật nút âm lượng</string>\n    <string name=\"tap_action\">Chạm</string>\n    <string name=\"long_tap_action\">Nhấn giữ</string>\n    <string name=\"none\">Để trống</string>\n    <string name=\"config_reset_confirm\">Đặt lại về mặc định? Tiếp tục sẽ không thể phục hồi cài đặt trước đó.</string>\n    <string name=\"use_two_pages_landscape\">Dùng bố cục hai trang cho định dạng màn hình ngang (beta)</string>\n    <string name=\"reading_stats\">Thống kê đọc</string>\n    <string name=\"statistics\">Thống kê</string>\n    <string name=\"pages_read_s\">Số trang đã đọc: %s</string>\n    <string name=\"download_option_first_n_chapters\">Từ đầu đến %s</string>\n    <string name=\"description\">Mô tả</string>\n    <string name=\"background\">Nền</string>\n    <string name=\"show_pages_thumbs\">Hiển thị thumbnail trang</string>\n    <string name=\"show_pages_thumbs_summary\">Bật \\\"Trang\\\" trong màn hình chi tiết</string>\n    <string name=\"category_hidden_done\">Danh mục này đã bị ẩn và có thể vào được qua Menu → Quản lý danh mục</string>\n    <string name=\"manual\">Bằng tay</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"preferred_download_format\">Định dạng muốn tải xuống</string>\n    <string name=\"single_cbz_file\">Một file CBZ</string>\n    <string name=\"multiple_cbz_files\">Nhiều file CBZ</string>\n    <string name=\"languages\">Ngôn ngữ</string>\n    <string name=\"toggle_ui\">Hiển thị/Ẩn UI</string>\n    <string name=\"prev_chapter\">Chương trước</string>\n    <string name=\"show_menu\">Hiển thị Menu</string>\n    <string name=\"webtoon_gaps\">Khoảng cách trong chế độ webtoon</string>\n    <string name=\"check_for_new_chapters_disabled\">Kiểm tra chương mới thêm đã tắt</string>\n    <string name=\"network\">Mạng</string>\n    <string name=\"progress\">Tiến trình</string>\n    <string name=\"order_added\">Đã thêm</string>\n    <string name=\"frequency_once_per_month\">Một lần mỗi tháng</string>\n    <string name=\"no_manga_sources_found\">Không tìm được yêu cầu của bạn từ bất kì nguồn manga nào</string>\n    <string name=\"skip\">Bỏ qua</string>\n    <string name=\"apply\">Áp dụng</string>\n    <string name=\"genres_exclude\">Ngoại trừ các thể loại</string>\n    <string name=\"last_read\">Gần đây</string>\n    <string name=\"fullscreen_mode\">Chế độ toàn màn hình</string>\n    <string name=\"reading_time_estimation\">Hiển thị thời gian đọc ước tính</string>\n    <string name=\"reading_time_estimation_summary\">Thời gian đọc ước tính có thể không chính xác</string>\n    <string name=\"ask_for_dest_dir_every_time\">Hỏi địa chỉ mỗi lần lưu</string>\n    <string name=\"other_manga\">Manga khác</string>\n    <string name=\"chapters_deleted_pattern\">Đã xoá %1$s, làm sạch %2$s</string>\n    <string name=\"delete_read_chapters_auto\">Xoá các chương đã đọc tự động</string>\n    <string name=\"runs_on_app_start\">Chạy khi ứng dụng được khởi chạy</string>\n    <string name=\"split_by_translations\">Tách theo bản dịch</string>\n    <string name=\"split_by_translations_summary\">Hiển thị chương với các bản dịch tách riêng ra, không gộp lại thành một danh sách</string>\n    <string name=\"order_oldest\">Cũ nhất</string>\n    <string name=\"unread\">Chưa đọc</string>\n    <string name=\"unsupported_backup_message\">Vui lòng chọn một tệp sao lưu Kotatsu hợp lệ</string>\n    <string name=\"remaining_time_pattern\">Còn %1$s %2$s</string>\n    <string name=\"fix\">Sửa lỗi</string>\n    <string name=\"missing_storage_permission\">Không có quyền để truy cập manga trên bộ nhớ ngoài</string>\n    <string name=\"webtoon_gaps_summary\">Hiển thị khoảng cách dọc giữa các trang trong chế độ webtoon</string>\n    <string name=\"authorization_optional\">Xác thực (tuỳ chọn)</string>\n    <string name=\"download_option_next_unread_n_chapters\">Từ chưa đọc đến %s</string>\n    <string name=\"welcome_text\">Vui lòng chọn nguồn mà bạn muốn bật. Có thể thay đổi lại sau trong cài đặt</string>\n    <string name=\"default_page_save_dir\">Địa chỉ lưu trang mặc định</string>\n    <string name=\"delete_read_chapters_summary\">Xoá các chương mà bạn đã đọc khỏi bộ nhớ trong để giải phóng bộ nhớ</string>\n    <string name=\"long_ago_read\">Đã đọc từ rất lâu trước</string>\n    <string name=\"last_used\">Đã đọc gần đây</string>\n    <string name=\"show_updated\">Hiển thị cập nhật</string>\n    <string name=\"downloads_settings_info\">Bật làm chậm tải xuống cho nguồn manga trong cài đặt nguồn của nó nếu bạn bị chặn bởi server (server-side blocking)</string>\n    <string name=\"state\">Tình trạng</string>\n    <string name=\"error_filter_locale_genre_not_supported\">Lọc với thể loại và ngôn ngữ không được hỗ trợ bởi nguồn này</string>\n    <string name=\"error_no_data_received\">Không có dữ liệu nào nhận được từ server</string>\n    <string name=\"enable_source\">Bật nguồn</string>\n    <string name=\"unsupported_source\">Nguồn manga này không được hỗ trợ</string>\n    <string name=\"less_frequently\">Ít thường xuyên</string>\n    <string name=\"more_frequently\">Thường xuyên</string>\n    <string name=\"frequency_of_check\">Tần xuất kiểm tra</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui\">Ghim UI điều hướng</string>\n    <string name=\"pin_navigation_ui_summary\">Không ẩn thanh điều hướng và chế độ xem tìm kiếm khi cuộn</string>\n    <string name=\"search_suggestions\">Gợi ý tìm kiếm</string>\n    <string name=\"recent_queries\">Các truy vấn gần đây</string>\n    <string name=\"suggested_queries\">Truy vấn được đề xuất</string>\n    <string name=\"authors\">Tác giả</string>\n    <string name=\"blocked_by_server_message\">Bạn bị chặn bởi máy chủ. Hãy thử sử dụng kết nối mạng khác (VPN, Proxy, v.v.)</string>\n    <string name=\"_new\">Mới</string>\n    <string name=\"all_languages\">Tất cả các ngôn ngữ</string>\n    <string name=\"screenshots_block_incognito\">Chặn khi sử dụng chế độ ẩn danh</string>\n    <string name=\"image_server\">Chất lượng hình ảnh</string>\n    <string name=\"crop_pages\">Cắt ảnh trang</string>\n    <string name=\"pin\">Ghim</string>\n    <string name=\"unpin\">Xóa ghim</string>\n    <string name=\"source_pinned\">Nguồn truyện đã được ghim</string>\n    <string name=\"source_unpinned\">Đã xóa ghim nguồn truyện này</string>\n    <string name=\"sources_unpinned\">Đã xóa ghim các nguồn truyện này</string>\n    <string name=\"sources_pinned\">Đã ghim các nguồn truyện này</string>\n    <string name=\"recent_sources\">Các nguồn truyện trước đó</string>\n    <string name=\"disable_connectivity_check\">Vô hiệu hóa tự động kiểm tra kết nối mạng</string>\n    <string name=\"disable_nsfw_notifications\">Vô hiệu hóa thông báo NSFW</string>\n    <string name=\"disable_nsfw_notifications_summary\">Ẩn các thông báo liên quan đến cập nhật thông tin về những bộ truyện dành cho người lớn (NSFW)</string>\n    <string name=\"tracker_debug_info\">Kiểm tra các bản ghi về chương mới</string>\n    <string name=\"disable_connectivity_check_summary\">Bỏ qua kiểm tra kết nối mạng của bạn trong ứng dụng nếu bạn gặp vấn đề với nó (Ví dụ như bạn đã kết nối mạng nhưng ứng dụng nói \\\"Bạn đang ngoại tuyến\\\",...)</string>\n    <string name=\"ignore_ssl_errors_summary\">Bạn có thể vô hiệu hóa xác thực chứng chỉ SSL nếu bạn gặp phải sự cố liên quan đến SSL khi truy cập vào nguồn truyện. Điều này có thể ảnh hưởng đến việc bảo mật kết nối của bạn. Bắt buộc phải khởi động lại ứng dụng nếu bạn thay đổi thiết đặt này.</string>\n    <string name=\"disable\">Vô hiệu hóa</string>\n    <string name=\"sources_disabled\">Nguồn đã bị vô hiệu hóa</string>\n    <string name=\"tracker_debug_info_summary\">Gỡ lỗi các thông tin về tính năng kiểm tra chương mới trong nền</string>\n    <string name=\"percent_read\">Tiến trình đọc</string>\n    <string name=\"percent_left\">Tiến trình đọc còn lại</string>\n    <string name=\"chapters_read\">Chương đã đọc</string>\n    <string name=\"chapters_left\">Chương còn lại</string>\n    <string name=\"external_source\">Nguồn / Plugin bên ngoài</string>\n    <string name=\"plugin_incompatible\">Plugin bị lỗi hoặc không phù hợp. Hãy đảm bảo rằng bạn đang sử dụng phiên bản mới nhất của Kotatsu hoặc của Plugin đó</string>\n    <string name=\"text_empty_holder_secondary_filtered\">Không có bộ truyện nào phù hợp với bộ lọc của bạn ở đây ¯\\\\_(ツ)_/¯</string>\n    <string name=\"connection_ok\">Kết nối rất OK ¯\\\\_(ツ)_/¯</string>\n    <string name=\"invalid_proxy_configuration\">Cài đặt Proxy này của bạn không hợp lệ</string>\n    <string name=\"show_quick_filters\">Hiện bộ lọc nhanh</string>\n    <string name=\"show_quick_filters_summary\">Cung cấp khả năng lọc danh sách Manga dựa vào dữ liệu cụ thể</string>\n    <string name=\"invalid_server_address_message\">Địa chỉ máy chủ này không hợp lệ</string>\n    <string name=\"sfw\">Truyện SFW</string>\n    <string name=\"too_many_requests_message_retry\">Quá nhiều yêu cầu được gửi đi, vui lòng thử lại sau %s</string>\n    <string name=\"seconds_short\">%d giây</string>\n    <string name=\"minutes_seconds_short\">%1$d phút %2$d giây</string>\n    <string name=\"skip_all\">Bỏ qua tất cả</string>\n    <string name=\"retry\">Thử lại</string>\n    <string name=\"stuck\">Kẹt đứng</string>\n    <string name=\"not_in_favorites\">Không có ở trong mục yêu thích của bạn</string>\n    <string name=\"unpopular\">Không quá nổi bật</string>\n    <string name=\"low_rating\">Được đánh giá thấp</string>\n    <string name=\"sort_order_asc\">Tăng dần</string>\n    <string name=\"sort_order_desc\">Giảm dần</string>\n    <string name=\"by_date\">Theo ngày</string>\n    <string name=\"popularity\">Theo mức độ phổ biến</string>\n    <string name=\"updated_long_ago\">Đã được cập nhật từ trước đó</string>\n    <string name=\"scrobbler_auth_required\">Đăng nhập vào %s để tiếp tục</string>\n    <string name=\"unstable_feature\">Tính năng không ổn định</string>\n    <string name=\"scrobbler_auth_intro\">Đăng nhập để thiết đặt với %s. Điều này sẽ cho phép bạn theo dõi tiến trình và trạng thái đọc manga của mình</string>\n    <string name=\"unstable_feature_summary\">Tính năng này đang được thử nghiệm. Hãy chắc chắn rằng bạn đã tạo bản sao lưu để tránh việc mất dữ liệu oan</string>\n    <string name=\"download_new_chapters\">Tải khi có chương mới</string>\n    <string name=\"downloads_background\">Tải trong nền</string>\n    <string name=\"manga_with_downloaded_chapters\">Truyện đã tải sẵn chương</string>\n    <string name=\"fixing_manga\">Đang sửa manga</string>\n    <string name=\"fixed\">Đã sửa thành công</string>\n    <string name=\"no_alternatives_found\">Không tìm thấy lựa chọn thay thế cho \\\"%s\\\"</string>\n    <string name=\"manga_fix_prompt\">Tính năng này sẽ giúp bạn tìm và thay thế nguồn đọc cho bộ manga mà bạn chọn. Tính năng này sẽ tốn thời gian để xử lí và sẽ được xử lí ngầm (trong nền)</string>\n    <string name=\"manga_replaced\">Manga \\\"%1$s\\\" (%2$s) đã được thay thế bằng \\\"%3$s\\\" (%4$s)</string>\n    <string name=\"no_fix_required\">Không cần sửa đối với \\\"%s\\\"</string>\n    <string name=\"recently_added\">Đã thêm gần đây</string>\n    <string name=\"content_type_novel\">Tiểu thuyết</string>\n    <string name=\"content_type_manhua\">Manhua</string>\n    <string name=\"content_type_manhwa\">Manhwa</string>\n    <string name=\"added_long_ago\">Đã được thêm từ trước</string>\n    <string name=\"popular_in_hour\">Nổi bật trong giờ qua</string>\n    <string name=\"popular_in_week\">Nổi bật trong tuần</string>\n    <string name=\"popular_today\">Hiện đang nổi</string>\n    <string name=\"popular_in_year\">Nổi bật trong năm</string>\n    <string name=\"popular_in_month\">Nổi bật trong tháng</string>\n    <string name=\"original_language\">Ngôn ngữ gốc</string>\n    <string name=\"year\">Năm</string>\n    <string name=\"demographics\">Thể loại đối tượng</string>\n    <string name=\"demographic_shounen\">Shounen</string>\n    <string name=\"demographic_shoujo\">Shoujo</string>\n    <string name=\"demographic_seinen\">Seinen</string>\n    <string name=\"demographic_josei\">Josei</string>\n    <string name=\"years\">Năm</string>\n    <string name=\"any\">Bất kì</string>\n    <string name=\"filter_search_warning\">Bộ lọc của bạn đã bị xóa do nguồn đọc này không hỗ trợ cho việc tìm kiếm bằng bộ lọc</string>\n    <string name=\"demographic_kodomo\">Dành cho trẻ em</string>\n    <string name=\"content_type_one_shot\">One shot</string>\n    <string name=\"content_type_image_set\">Đặt hình ảnh</string>\n    <string name=\"content_type_game_cg\">Game CG</string>\n    <string name=\"content_type_doujinshi\">Doujinshi</string>\n    <string name=\"content_type_artist_cg\">Họa sĩ CG</string>\n    <string name=\"source_code\">Mã nguồn</string>\n    <string name=\"user_manual\">Hướng dẫn sử dụng</string>\n    <string name=\"telegram_group\">Nhóm Telegram</string>\n    <string name=\"debug\">Gỡ lỗi</string>\n    <string name=\"error_image_format\">Định dạng hình ảnh không được hỗ trợ: %s</string>\n    <string name=\"save_manga\">Lưu manga</string>\n    <string name=\"start_download\">Bắt đầu tải về</string>\n    <string name=\"save_manga_confirm\">Lưu manga đã chọn? Điều này sẽ tiêu tốn bộ nhớ của máy và dữ liệu mạng của bạn</string>\n    <string name=\"genre\">Thể loại</string>\n    <string name=\"download_added\">Đã thêm vào mục tải xuống</string>\n    <string name=\"more_options\">Cài đặt nâng cao</string>\n    <string name=\"destination_directory\">Thư mục đích</string>\n    <string name=\"chapter_selection_hint\">Bạn có thể chọn tải về nhiều chương cùng lúc bằng cách ấn giữ các mục trong danh sách chương.</string>\n    <string name=\"chapters_all\">Tất cả</string>\n    <string name=\"download_over_cellular\">Đang tải xuống bằng mạng dữ liệu di động</string>\n    <string name=\"dont_allow\">Không cho phép</string>\n    <string name=\"allow_always\">Luôn cho phép</string>\n    <string name=\"download_cellular_confirm\">Cho phép tải xuống bằng mạng dữ liệu di động?</string>\n    <string name=\"allow_once\">Chỉ cho phép lần này</string>\n    <string name=\"ask_every_time\">Luôn hỏi</string>\n    <string name=\"portrait\">Dọc</string>\n    <string name=\"landscape\">Ngang</string>\n    <string name=\"screen_orientation\">Xoay hướng màn hình</string>\n    <string name=\"plugin_incompatible_with_cause\">Lỗi Plugin: %s\\nHãy đảm bảo rằng bạn đang sử dụng phiên bản mới nhất của Kotatsu / Đã cập nhật Plugin đó</string>\n    <string name=\"error_not_image\">Định dạng không hợp lệ: Hình ảnh nhận được là %s</string>\n    <string name=\"max_backups_count\">Tối đa số lượng các bản sao lưu</string>\n    <string name=\"access_denied_403\">Truy cập bị từ chối (Lỗi 403)</string>\n    <string name=\"pages_saved\">Đã lưu trang</string>\n    <string name=\"delete_old_backups\">Xóa bản sao lưu cũ</string>\n    <string name=\"delete_old_backups_summary\">Tự động xóa các bản sao lưu cũ để tiết kiệm bộ nhớ của thiết bị</string>\n    <string name=\"handle_links\">Xử lý liên kết (Link)</string>\n    <string name=\"handle_links_summary\">Xử lý các liên kết (Link) truyện từ các ứng dụng bên ngoài (Ví dụ: Chrome,...). Có thể bạn cũng cần phải bật thủ công trong cài đặt hệ thống</string>\n    <string name=\"email\">E-mail</string>\n    <string name=\"captcha_required_message\">Nguồn đang yêu cầu xác thực CAPTCHA để có thể tiếp tục sử dụng</string>\n    <string name=\"author\">Tác giả</string>\n    <string name=\"rating\">Đánh giá</string>\n    <string name=\"source\">Nguồn</string>\n    <string name=\"translation\">Bản dịch</string>\n    <string name=\"incognito\">Ẩn danh</string>\n    <string name=\"show_slider\">Hiện thanh trượt</string>\n    <string name=\"backup_tg_echo\">Tình trạng: 200 OK</string>\n    <string name=\"backup_tg_check\">Kiểm tra API</string>\n    <string name=\"backup_tg_id_not_set\">Bạn chưa thiết lập Chat ID của Telegram</string>\n    <string name=\"telegram_chat_id\">Chat ID của Telegram</string>\n    <string name=\"open_telegram_bot\">Mở Bot Telegram</string>\n    <string name=\"error_connection_reset\">Kết nối đã được đặt lại bởi máy chủ</string>\n    <string name=\"telegram_chat_id_summary\">Nhập Chat ID từ Telegram của bạn, nơi mà Bot sẽ gửi bản sao lưu dữ liệu của ứng dụng cho bạn</string>\n    <string name=\"test_connection\">Kiểm tra kết nối</string>\n    <string name=\"send_backups_telegram\">Gửi bản sao lưu dữ liệu vào Telegram</string>\n    <string name=\"open_telegram_bot_summary\">Ấn vào đây để mở đoạn Chat với Kotatsu Backup Bot (cần cài đặt Telegram)</string>\n    <string name=\"clear_database\">Xóa database</string>\n    <string name=\"clear_database_summary\">Xoá thông tin dữ liệu về manga chưa từng được sử dụng (cache)</string>\n    <string name=\"all_sources_enabled\">Toàn bộ nguồn đọc đã được bật</string>\n    <string name=\"enable_all_sources_summary\">Tất cả nguồn đọc / nguồn truyện mà App có sẵn sẽ được bật vĩnh viễn</string>\n    <string name=\"enable_all_sources\">Bật toàn bộ nguồn đọc có sẵn</string>\n    <string name=\"reader_info_bar_transparent\">Làm trong suốt thanh trạng thái ở giao diện đọc truyện</string>\n    <string name=\"restoring_backup\">Đang khôi phục sao lưu</string>\n    <string name=\"chapters_and_pages\">Duyệt chương và trang đọc</string>\n    <string name=\"pages_slider\">Thanh trượt chuyển trang</string>\n    <string name=\"screen_rotation_locked\">Xoay màn hình đã được khóa lại</string>\n    <string name=\"screen_rotation_unlocked\">Hướng xoay màn hình đã được mở khóa</string>\n    <string name=\"backup_restored_background\">Quá trình khôi phục bản sao lưu này sẽ được thực hiện trong tiến trình nền (chạy ngầm)</string>\n    <string name=\"reader_controls_in_bottom_bar\">Tùy chỉnh nút điều khiển ở thanh công cụ phía dưới giao diện đọc</string>\n    <string name=\"badges_in_lists\">Hiển thị biểu tượng trong thư viện</string>\n    <string name=\"simple\">Đơn giản</string>\n    <string name=\"search_everywhere\">Tìm kiếm ở bất cứ đâu</string>\n    <string name=\"global_search\">Tìm kiếm chung</string>\n    <string name=\"disable_captcha_notifications\">Tắt thông báo về Captcha</string>\n    <string name=\"disable_captcha_notifications_summary\">Bạn sẽ không nhận được thông báo về việc giải CAPTCHA cho nguồn này nhưng điều này cũng có thể dẫn đến việc phá vỡ quá trình hoạt động nền đối với nguồn này (như kiểm tra chương / tập truyện mới, đưa ra gợi ý,...)</string>\n    <string name=\"error_details\">Chi tiết lỗi</string>\n    <string name=\"error_disclaimer_report\">Bạn có thể báo cáo lỗi cho các nhà phát triển của Kotatsu. Nó sẽ rất hữu ích để các nhà phát triển có thể tìm ra và xử lý lỗi đó ở các phiên bản kế tiếp.</string>\n    <string name=\"error_disclaimer_app_outdated\">Có vẻ như bạn đang sử dụng một phiên bản \\\"siêu cấp cổ đại\\\" của Kotatsu. Hãy cập nhật / cài đặt phiên bản mới nhất để nhận được các bản vá lỗi cho Kotatsu.</string>\n    <string name=\"error_disclaimer_manga\">Hãy đảm bảo rằng truyện này vẫn xuất hiện ở nguồn đọc đó bằng cách tìm và mở nó trên trình duyệt Web của bạn ¯\\\\_(ツ)_/¯.</string>\n    <string name=\"search_disabled_sources\">Tìm kiếm với những nguồn đang bị vô hiệu hóa</string>\n    <string name=\"chapter_volume_number\">Vol %1$s Chương %2$s</string>\n    <string name=\"chapter_number\">Chương %s</string>\n    <string name=\"unnamed_chapter\">Chương không có tên</string>\n    <string name=\"link_to_manga_in_app\">Liên kết manga trên Kotatsu</string>\n    <string name=\"link_to_manga_on_s\">Liên kết manga với nguồn %s</string>\n    <string name=\"clear_browser_data\">Xóa dữ liệu của trình duyệt</string>\n    <string name=\"clear_browser_data_summary\">Điều này sẽ xóa những dữ liệu như cache và cookies. Lưu ý: Xác thực (đăng nhập) đối với nguồn đọc có thể sẽ không còn hợp lệ nữa</string>\n    <string name=\"no_write_permission_to_file\">Không có quyền để ghi một tệp tin mới</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">Gợi ý về manga người lớn sẽ được ẩn đi. Tùy chọn này có thể không hoạt động chuẩn với một số nguồn</string>\n    <string name=\"include_disabled_sources\">Bao gồm các nguồn đã bị vô hiệu hoá</string>\n    <string name=\"suggestions_disabled_sources_summary\">Hiện gợi ý từ tất cả các nguồn đọc, bao gồm cả những nguồn đã bị vô hiệu hoá</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"tags_warnings\">Highlight những thể loại không an toàn</string>\n    <string name=\"tags_warnings_summary\">Highlight những thể loại mà nó không thực sự phù hợp với hầu hết người đọc (mọi lứa tuổi)</string>\n    <string name=\"manga_override_hint\">Những thay đổi này sẽ ảnh hưởng đến cách bạn trang trí / trình bày truyện trong ứng dụng</string>\n    <string name=\"pick_manga_page\">Chọn trang có trong manga làm ảnh bìa của truyện</string>\n    <string name=\"change_cover\">Sửa ảnh bìa truyện</string>\n    <string name=\"use_default_cover\">Sử dụng ảnh bìa truyện mặc định</string>\n    <string name=\"pick_custom_file\">Chọn ảnh từ máy làm ảnh bìa của truyện</string>\n    <string name=\"error_non_file_uri\">Không thể sử dụng đường dẫn này vì nó không tồn tại hoặc không phải là 1 tệp / đường dẫn xác định</string>\n    <string name=\"page_switch_timer\">Trang truyện sẽ được chuyển sau mỗi %d giây (ước tính)</string>\n    <string name=\"dont_ask_again\">Không hỏi lại lần nữa</string>\n    <string name=\"incognito_for_nsfw\">Bật chế độ ẩn danh đối với truyện tranh người lớn</string>\n    <string name=\"incognito_mode_hint_nsfw\">Truyện này có thể có chứa nội dung dành cho người lớn. Bạn có muốn sử dụng chế độ ẩn danh không?</string>\n    <string name=\"theme_name_expressive\">Expressive (Thử nghiệm)</string>\n    <string name=\"additional_action_required\">Bắt buộc cần có hành động bổ sung</string>\n    <string name=\"hide_from_main_screen\">Ẩn khỏi giao diện màn hình chính</string>\n    <string name=\"changelog\">Ghi chú cập nhật</string>\n    <string name=\"changelog_summary\">Xem lại các bản ghi về các tính năng đã được sửa đổi / thêm vào trong các phiên bản trước đây</string>\n    <string name=\"expand\">Mở rộng</string>\n    <string name=\"collapse\">Thu gọn</string>\n    <string name=\"adblock\">Chặn quảng cáo trong trình duyệt (Webview)</string>\n    <string name=\"adblock_summary\">Chặn những quảng cáo xuất hiện trong trình duyệt Webview của hệ thống khi duyệt (Beta)</string>\n    <string name=\"collapse_long_description\">Thu gọn đoạn mô tả dài</string>\n    <string name=\"creating_backup\">Sao lưu dữ liệu</string>\n    <string name=\"share_backup\">Chia sẻ dữ liệu đã sao lưu</string>\n    <string name=\"reader_multitask\">Mở trình đọc sang 1 tác vụ riêng biệt</string>\n    <string name=\"reader_multitask_summary\">Tách trình đọc thành 1 tác vụ riêng biệt sẽ cho bạn khả năng mở và đọc nhiều truyện cùng một lúc (tốt hơn với máy tính bảng, Chrome OS và các giả lập Android cho phép mở đa cửa sổ trên máy tính như WSA,...)</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"reader_navigation_inverted\">Đảo ngược phím điều khiển hướng đọc</string>\n    <string name=\"reader_navigation_inverted_summary\">Đảo ngược hướng của các phím điều khiển để đọc truyện như nút tăng / giảm âm lượng, D-pad (lên / xuống / trái / phải)</string>\n    <string name=\"book_effect\">Làm dịu ánh màu xanh của ảnh (chuyển sang màu vàng)</string>\n    <string name=\"local_storage_cleanup\">Dọn dẹp bộ nhớ lưu trữ cục bộ</string>\n    <string name=\"packup_creation_failed\">Tạo bản sao lưu dữ liệu thất bại</string>\n    <string name=\"main_screen\">Màn hình chính</string>\n    <string name=\"main_screen_fab\">Hiện nút \\\"Tiếp tục\\\" nổi ở màn hình chính</string>\n    <string name=\"main_screen_fab_summary\">Nút này cho phép bạn đọc tiếp truyện gần nhất trong lịch sử đọc chỉ với 1 chạm. Nút này sẽ không xuất hiện nếu bạn đang dùng chế độ ẩn danh hoặc khi lịch sử đọc rỗng</string>\n    <string name=\"error_corrupted_zip\">Tệp lưu trữ ZIP đã bị hỏng (%s)</string>\n    <string name=\"discord_rpc\">Tích hợp Discord Rich Presence (Discord RPC)</string>\n    <string name=\"discord_token\">Token của Discord</string>\n    <string name=\"discord_token_summary\">Nhập Token vào để kích hoạt Discord Rich Presence</string>\n    <string name=\"discord_token_description\">Nhập Token do Discord gán hoặc %s tại đây để Kotatsu tự động trích xuất Token của bạn từ trình duyệt</string>\n    <string name=\"discord_token_hint\">Dán Token của bạn tại đây</string>\n    <string name=\"discord_rpc_summary\">Hiển thị tiến trình đọc truyện của bạn trên Discord</string>\n    <string name=\"obtain\">Đã nhận được</string>\n    <string name=\"discord_rpc_description\">Đang đọc trên Kotatsu - Ứng dụng đọc truyện mã nguồn mở miễn phí</string>\n    <string name=\"reading_s\">Hiện đang đọc: %s</string>\n    <string name=\"read_on_s\">Đọc trên %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">Không tự động kích hoạt Discord Rich Presence đối với truyện người lớn</string>\n    <string name=\"invalid_token\">Token không hợp lệ: %s</string>\n    <string name=\"show_floating_control_button\">Hiển thị nút điều khiển nổi</string>\n    <string name=\"unavailable\">Không có sẵn</string>\n    <string name=\"manga_restricted_description\">Truyện này không có sẵn trên nguồn này. Hãy thử tìm kiếm truyện này với nguồn đọc khác hoặc mở nó trong trình duyệt để biết thêm chi tiết</string>\n    <string name=\"no_chapters_in_manga\">Truyện này không có sẵn chương nào cả ツ</string>\n    <string name=\"chapters_load_failed\">Không thể tải danh sách chương hiện có</string>\n    <string name=\"telegram_integration\">Tích hợp Telegram</string>\n    <string name=\"test_parser\">Nguồn manga kiểm thử</string>\n    <string name=\"pull_to_prev_chapter\">Thả tay ra để mở chương trước đó</string>\n    <string name=\"pull_to_next_chapter\">Thả tay ra để mở chương tiếp theo</string>\n    <string name=\"pull_top_no_prev\">Không còn chương nào ở trước đó</string>\n    <string name=\"pull_bottom_no_next\">Không còn chương nào để xem tiếp</string>\n    <string name=\"enable_pull_gesture_title\">Bật cử chỉ kéo - thả</string>\n    <string name=\"enable_pull_gesture_summary\">Sử dụng cử chỉ tay kéo - thả để chuyển chương trong chế độ đọc Webtoon</string>\n    <string name=\"saved_filters\">Lưu bộ lọc tìm kiếm</string>\n    <string name=\"enter_name\">Hãy điền tên</string>\n    <string name=\"two_page_scroll_sensitivity\">Độ nhạy khi cuộn hai trang</string>\n    <string name=\"reader_chapter_toast\">Hiện thông báo chuyển chương</string>\n    <string name=\"reader_chapter_toast_summary\">Hiển thị một bong bóng nổi nho nhỏ ở phía dưới màn hình cùng với tên của chương truyện khi bạn đọc sang một chương truyện mới</string>\n    <string name=\"rename\">Đặt tên</string>\n    <string name=\"save_filter\">Lưu bộ lọc</string>\n    <string name=\"overwrite\">Đè lên</string>\n    <string name=\"filter_overwrite_confirm\">Một bộ lộc có tên là \\\"%s\\\" đã tồn tại trước đó. Bạn có muốn ghi đè nó lên không?</string>\n    <string name=\"storage_and_network\">Lưu trữ và mạng</string>\n    <string name=\"create_or_restore_backup\">Tạo hoặc khôi phục từ một bản sao lưu</string>\n    <string name=\"data_removal\">Xoá dữ liệu</string>\n    <string name=\"privacy\">Quyền riêng tư</string>\n    <string name=\"source_broken_warning\">Nguồn đọc này đã được gắn là \\\"Không còn sử dụng được\\\". Một vài tính năng có thể sẽ không hoạt động</string>\n    <string name=\"frequency_every_6_hours\">6 tiếng một lần</string>\n    <string name=\"download_default_directory\">Đường dẫn mặc định để tải truyện về thiết bị</string>\n    <string name=\"private_app_directory_warning\">Đường dẫn này với tất cả dữ liệu của ứng dụng sẽ bị xóa nếu bạn gỡ ứng dụng Kotatsu</string>\n    <string name=\"available_pattern\">Hiện có sẵn %1$s</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w600dp-land/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<bool name=\"is_tablet\">true</bool>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-w600dp-land/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<dimen name=\"grid_spacing\">6dp</dimen>\n\t<dimen name=\"grid_spacing_outer\">2dp</dimen>\n\t<dimen name=\"grid_spacing_outer_double\">4dp</dimen>\n\t<dimen name=\"preferred_grid_width\">140dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w600dp-land/integers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<integer name=\"explore_buttons_columns\">4</integer>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d 个项目</item>\n    </plurals>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d 个新章节</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d 个章节</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d 分钟前</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d 小时前</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d 天前</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d 个月前</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d 个小时</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d 分钟</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"settings\">设置</string>\n    <string name=\"local_storage\">本地</string>\n    <string name=\"favourites\">收藏</string>\n    <string name=\"history\">历史</string>\n    <string name=\"error_occurred\">出错了</string>\n    <string name=\"network_error\">网络错误</string>\n    <string name=\"chapters\">章节</string>\n    <string name=\"list\">列表</string>\n    <string name=\"data_restored_with_errors\">数据已恢复，但出现了一些错误</string>\n    <string name=\"processing_\">正在处理…</string>\n    <string name=\"newest\">最新</string>\n    <string name=\"by_rating\">评分（降序）</string>\n    <string name=\"cookies_cleared\">Cookies 已清除</string>\n    <string name=\"data_restored_success\">数据已全部恢复</string>\n    <string name=\"silent\">无声</string>\n    <string name=\"preparing_\">准备中…</string>\n    <string name=\"file_not_found\">未找到文件</string>\n    <string name=\"yesterday\">昨天</string>\n    <string name=\"backup_information\">可对历史和收藏进行备份与恢复</string>\n    <string name=\"just_now\">刚刚</string>\n    <string name=\"long_ago\">很久以前</string>\n    <string name=\"group\">分组</string>\n    <string name=\"tap_to_try_again\">点击重试</string>\n    <string name=\"reader_mode_hint\">所选模式仅应用于当前漫画</string>\n    <string name=\"captcha_required\">需要通过人机验证</string>\n    <string name=\"captcha_solve\">开始验证</string>\n    <string name=\"today\">今天</string>\n    <string name=\"clear_cookies\">清除 Cookies</string>\n    <string name=\"new_sources_text\">发现了新的可用图源</string>\n    <string name=\"suggestions_summary\">根据你的喜好推荐漫画</string>\n    <string name=\"suggestions_info\">所有数据都在本地分析，不会发送至任何地方</string>\n    <string name=\"never\">从不</string>\n    <string name=\"show_notification_new_chapters_on\">接收在读漫画的更新通知</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">多语言</string>\n    <string name=\"search_chapters\">查找章节</string>\n    <string name=\"suggestions_excluded_genres\">屏蔽类别</string>\n    <string name=\"suggestions_updating\">漫画推荐更新中</string>\n    <string name=\"check_new_chapters_title\">检查漫画新章节并接收通知</string>\n    <string name=\"details\">详情</string>\n    <string name=\"detailed_list\">详情</string>\n    <string name=\"grid\">网格</string>\n    <string name=\"list_mode\">视图选项</string>\n    <string name=\"remote_sources\">图源</string>\n    <string name=\"loading_\">加载中…</string>\n    <string name=\"computing_\">计算中…</string>\n    <string name=\"chapter_d_of_d\">%1$d/%2$d 章</string>\n    <string name=\"close\">关闭</string>\n    <string name=\"try_again\">重试</string>\n    <string name=\"clear_history\">清除阅读记录</string>\n    <string name=\"nothing_found\">结果为空</string>\n    <string name=\"history_is_empty\">暂无历史记录</string>\n    <string name=\"read\">阅读</string>\n    <string name=\"you_have_not_favourites_yet\">暂无收藏</string>\n    <string name=\"add_to_favourites\">收藏</string>\n    <string name=\"add_new_category\">添加分类</string>\n    <string name=\"add\">添加</string>\n    <string name=\"save\">保存</string>\n    <string name=\"share\">分享</string>\n    <string name=\"create_shortcut\">创建快捷方式</string>\n    <string name=\"share_s\">分享 %s</string>\n    <string name=\"search\">搜索</string>\n    <string name=\"search_manga\">搜索漫画</string>\n    <string name=\"manga_downloading_\">正在下载…</string>\n    <string name=\"download_complete\">下载已完成</string>\n    <string name=\"downloads\">下载</string>\n    <string name=\"by_name\">名称（升序）</string>\n    <string name=\"popular\">热度（降序）</string>\n    <string name=\"updated\">更新</string>\n    <string name=\"sort_order\">排序方式</string>\n    <string name=\"filter\">筛选</string>\n    <string name=\"theme\">颜色模式</string>\n    <string name=\"dark\">深色</string>\n    <string name=\"light\">浅色</string>\n    <string name=\"follow_system\">跟随系统</string>\n    <string name=\"pages\">页面</string>\n    <string name=\"clear\">清除</string>\n    <string name=\"remove\">删除</string>\n    <string name=\"_s_deleted_from_local_storage\">已从本地存储中删除“%s”</string>\n    <string name=\"save_page\">保存图片</string>\n    <string name=\"page_saved\">保存成功</string>\n    <string name=\"share_image\">分享图片</string>\n    <string name=\"_import\">导入</string>\n    <string name=\"delete\">删除</string>\n    <string name=\"operation_not_supported\">操作无效</string>\n    <string name=\"text_file_not_supported\">必须选择一个 ZIP 或 CBZ 文件。</string>\n    <string name=\"no_description\">暂无简介</string>\n    <string name=\"clear_pages_cache\">清除页面缓存</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">默认</string>\n    <string name=\"webtoon\">条漫</string>\n    <string name=\"read_mode\">阅读模式</string>\n    <string name=\"grid_size\">网格大小</string>\n    <string name=\"search_on_s\">在%s上搜索</string>\n    <string name=\"delete_manga\">删除漫画</string>\n    <string name=\"text_delete_local_manga\">确定从设备中永久删除\\\"%s\\\"?</string>\n    <string name=\"reader_settings\">阅读</string>\n    <string name=\"switch_pages\">翻页方式</string>\n    <string name=\"_continue\">继续</string>\n    <string name=\"error\">错误</string>\n    <string name=\"clear_thumbs_cache\">清除缩略图缓存</string>\n    <string name=\"clear_search_history\">清除搜索记录</string>\n    <string name=\"search_history_cleared\">清除完毕</string>\n    <string name=\"internal_storage\">内部存储</string>\n    <string name=\"external_storage\">外部存储</string>\n    <string name=\"domain\">图源域名</string>\n    <string name=\"app_update_available\">发现新版本</string>\n    <string name=\"open_in_browser\">在浏览器中打开</string>\n    <string name=\"notifications\">通知</string>\n    <string name=\"new_chapters\">新章节</string>\n    <string name=\"download\">下载</string>\n    <string name=\"notifications_settings\">通知设置</string>\n    <string name=\"notification_sound\">通知声音</string>\n    <string name=\"light_indicator\">LED指示器</string>\n    <string name=\"vibration\">振动</string>\n    <string name=\"favourites_categories\">分类管理</string>\n    <string name=\"remove_category\">删除分类</string>\n    <string name=\"text_empty_holder_primary\">什么都没有…</string>\n    <string name=\"text_search_holder_secondary\">试试换一个关键词再搜索</string>\n    <string name=\"text_history_holder_primary\">看过的漫画将在这里显示</string>\n    <string name=\"text_history_holder_secondary\">在【浏览】页面搜索想读的漫画</string>\n    <string name=\"text_local_holder_primary\">存点什么吧</string>\n    <string name=\"text_local_holder_secondary\">可下载在线图源里的漫画或导入本地漫画</string>\n    <string name=\"manga_shelf\">收藏</string>\n    <string name=\"recent_manga\">最近</string>\n    <string name=\"pages_animation\">翻页动画</string>\n    <string name=\"manga_save_location\">漫画下载目录</string>\n    <string name=\"not_available\">不可用</string>\n    <string name=\"cannot_find_available_storage\">没有多余的存储空间</string>\n    <string name=\"other_storage\">其他存储</string>\n    <string name=\"done\">完成</string>\n    <string name=\"all_favourites\">所有收藏</string>\n    <string name=\"favourites_category_empty\">分类为空</string>\n    <string name=\"read_later\">稍后阅读</string>\n    <string name=\"updates\">更新内容</string>\n    <string name=\"text_feed_holder\">在读漫画的新章节将在这里显示</string>\n    <string name=\"search_results\">搜索结果</string>\n    <string name=\"new_version_s\">新版本：%s</string>\n    <string name=\"clear_updates_feed\">清除订阅更新记录</string>\n    <string name=\"updates_feed_cleared\">订阅更新记录已清除</string>\n    <string name=\"rotate_screen\">旋转屏幕</string>\n    <string name=\"update\">开始更新</string>\n    <string name=\"feed_will_update_soon\">订阅更新即将开始</string>\n    <string name=\"track_sources\">章节更新范围</string>\n    <string name=\"dont_check\">不检查</string>\n    <string name=\"enter_password\">请输入启动密码</string>\n    <string name=\"wrong_password\">密码错误</string>\n    <string name=\"protect_application\">应用锁</string>\n    <string name=\"protect_application_summary\">在启动Kotatsu时要求输入密码</string>\n    <string name=\"repeat_password\">重复密码</string>\n    <string name=\"passwords_mismatch\">密码不一致</string>\n    <string name=\"about\">关于</string>\n    <string name=\"app_version\">版本 %s</string>\n    <string name=\"check_for_updates\">检查更新</string>\n    <string name=\"no_update_available\">已是最新版本</string>\n    <string name=\"right_to_left\">从右到左</string>\n    <string name=\"create_category\">添加分类</string>\n    <string name=\"scale_mode\">缩放模式</string>\n    <string name=\"zoom_mode_fit_center\">填充屏幕</string>\n    <string name=\"zoom_mode_fit_height\">适应高度</string>\n    <string name=\"zoom_mode_fit_width\">适应宽度</string>\n    <string name=\"zoom_mode_keep_start\">原始大小</string>\n    <string name=\"black_dark_theme\">纯黑深色模式</string>\n    <string name=\"black_dark_theme_summary\">在AMOLED屏幕上更省电</string>\n    <string name=\"backup_restore\">备份与恢复</string>\n    <string name=\"create_backup\">创建备份</string>\n    <string name=\"restore_backup\">恢复备份</string>\n    <string name=\"data_restored\">恢复完成</string>\n    <string name=\"clear_feed\">清除订阅记录</string>\n    <string name=\"text_clear_updates_feed_prompt\">确定永久清除所有订阅更新记录？</string>\n    <string name=\"check_for_new_chapters\">章节检查更新</string>\n    <string name=\"reverse\">倒序</string>\n    <string name=\"sign_in\">登录</string>\n    <string name=\"auth_required\">登录后可查看此内容</string>\n    <string name=\"default_s\">默认值：%s</string>\n    <string name=\"next\">下一步</string>\n    <string name=\"protect_application_subtitle\">请填写密码</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"password_length_hint\">密码必须大于或等于4个字符</string>\n    <string name=\"text_clear_search_history_prompt\">确定永久清除所有搜索记录？</string>\n    <string name=\"welcome\">欢迎</string>\n    <string name=\"backup_saved\">备份已保存</string>\n    <string name=\"tracker_warning\">不同设备及不同的系统调度可能会杀掉本应用的后台任务</string>\n    <string name=\"read_more\">了解详情</string>\n    <string name=\"queued\">等待</string>\n    <string name=\"chapter_is_missing\">该章缺失</string>\n    <string name=\"about_app_translation_summary\">协助翻译</string>\n    <string name=\"about_app_translation\">协助翻译</string>\n    <string name=\"auth_complete\">授权</string>\n    <string name=\"auth_not_supported_by\">不支持在%s上登录</string>\n    <string name=\"text_clear_cookies_prompt\">会导致退出所有已登录图源</string>\n    <string name=\"genres\">类别</string>\n    <string name=\"state_ongoing\">连载中</string>\n    <string name=\"state_finished\">已完结</string>\n    <string name=\"system_default\">默认</string>\n    <string name=\"exclude_nsfw_from_history\">屏蔽成人漫画阅读记录</string>\n    <string name=\"show_pages_numbers\">显示页码</string>\n    <string name=\"screenshots_policy\">阅读时截图限制</string>\n    <string name=\"screenshots_allow\">总是允许</string>\n    <string name=\"screenshots_block_nsfw\">阅读成人内容时禁止</string>\n    <string name=\"screenshots_block_all\">总是禁止</string>\n    <string name=\"suggestions\">推荐</string>\n    <string name=\"suggestions_enable\">启用漫画推荐</string>\n    <string name=\"text_suggestion_holder\">开始阅读漫画，即可获取个性化推荐</string>\n    <string name=\"exclude_nsfw_from_suggestions\">禁止推荐成人漫画</string>\n    <string name=\"enabled\">启用</string>\n    <string name=\"disabled\">禁用</string>\n    <string name=\"reset_filter\">重置筛选</string>\n    <string name=\"onboard_text\">选择想要阅读漫画的语言，可之后在设置中更改。</string>\n    <string name=\"only_using_wifi\">仅连接 Wi-Fi 时</string>\n    <string name=\"always\">总是</string>\n    <string name=\"preload_pages\">页面预加载</string>\n    <string name=\"logged_in_as\">以 %s 登录</string>\n    <string name=\"chapters_empty\">此漫画没有章节</string>\n    <string name=\"appearance\">外观</string>\n    <string name=\"suggestions_excluded_genres_summary\">不希望在推荐中看到的类别</string>\n    <string name=\"text_delete_local_manga_batch\">确定从设备中永久删除所选漫画？</string>\n    <string name=\"removal_completed\">删除成功</string>\n    <string name=\"download_slowdown\">限速下载</string>\n    <string name=\"download_slowdown_summary\">帮助避免封禁你的IP地址</string>\n    <string name=\"local_manga_processing\">正在处理已保存漫画</string>\n    <string name=\"chapters_will_removed_background\">章节内容将在后台进行删除</string>\n    <string name=\"hide\">隐藏</string>\n    <string name=\"show_notification_new_chapters_off\">不会接收更新通知但新的章节将在漫画列表中高亮显示</string>\n    <string name=\"notifications_enable\">启用通知</string>\n    <string name=\"name\">名称</string>\n    <string name=\"edit\">编辑</string>\n    <string name=\"edit_category\">编辑分类</string>\n    <string name=\"empty_favourite_categories\">暂无分类</string>\n    <string name=\"bookmark_add\">添加书签</string>\n    <string name=\"bookmark_remove\">删除书签</string>\n    <string name=\"bookmarks\">书签</string>\n    <string name=\"bookmark_removed\">书签已删除</string>\n    <string name=\"bookmark_added\">书签已添加</string>\n    <string name=\"undo\">撤销</string>\n    <string name=\"removed_from_history\">历史记录已删除</string>\n    <string name=\"dns_over_https\">基于 HTTPS 的 DNS</string>\n    <string name=\"default_mode\">默认阅读模式</string>\n    <string name=\"detect_reader_mode\">自动检测阅读模式</string>\n    <string name=\"detect_reader_mode_summary\">阅读条漫时自动应用条漫阅读模式</string>\n    <string name=\"disable_battery_optimization\">禁用电池优化</string>\n    <string name=\"disable_battery_optimization_summary\">帮助保持检查更新时的后台运行</string>\n    <string name=\"crash_text\">出错了，请向开发人员提交错误报告以帮助修复问题。</string>\n    <string name=\"send\">发送</string>\n    <string name=\"disable_all\">全部禁用</string>\n    <string name=\"status_planned\">想读</string>\n    <string name=\"status_on_hold\">休刊中</string>\n    <string name=\"report\">报告</string>\n    <string name=\"tracking\">进度记录</string>\n    <string name=\"logout\">注销</string>\n    <string name=\"status_reading\">在读</string>\n    <string name=\"status_re_reading\">重读</string>\n    <string name=\"status_completed\">已完结</string>\n    <string name=\"use_fingerprint\">如果可用，使用生物认证</string>\n    <string name=\"appwidget_shelf_description\">收藏的漫画</string>\n    <string name=\"appwidget_recent_description\">最近阅读的漫画</string>\n    <string name=\"show_reading_indicators_summary\">在历史和收藏中显示阅读百分比</string>\n    <string name=\"show_reading_indicators\">显示阅读进度</string>\n    <string name=\"data_deletion\">数据删除</string>\n    <string name=\"exclude_nsfw_from_history_summary\">被标记为含有成人内容的漫画将不会记录阅读历史及阅读进度</string>\n    <string name=\"clear_cookies_summary\">能帮助解决部分问题，所有网站的授权将会失效</string>\n    <string name=\"show_all\">显示全部</string>\n    <string name=\"manga_error_description_pattern\">错误详情：&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1.尝试&lt;a href=%2$s&gt;在浏览器中打开漫画&lt;/a&gt;确保漫画在图源中正常显示&lt;br&gt;2.确认正在使用的是&lt;a href=kotatsu://about&gt;最新版本的Kotatsu&lt;/a&gt;&lt;br&gt;3.若已排除以上问题，请向开发人员发送错误报告。</string>\n    <string name=\"invalid_domain_message\">无效域名</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"canceled\">已取消</string>\n    <string name=\"account_already_exists\">账号已存在</string>\n    <string name=\"back\">返回</string>\n    <string name=\"sync\">同步</string>\n    <string name=\"sync_title\">开始同步网络数据</string>\n    <string name=\"email_enter_hint\">输入邮箱地址以继续</string>\n    <string name=\"status_dropped\">已腰斩</string>\n    <string name=\"select_range\">选择范围</string>\n    <string name=\"clear_all_history\">清除所有阅读记录</string>\n    <string name=\"last_2_hours\">过去2小时</string>\n    <string name=\"bookmarks_removed\">书签已删除</string>\n    <string name=\"history_cleared\">阅读记录已清除</string>\n    <string name=\"manage\">管理</string>\n    <string name=\"no_bookmarks_yet\">暂无书签</string>\n    <string name=\"no_bookmarks_summary\">可在阅读漫画时添加书签</string>\n    <string name=\"no_manga_sources\">暂无图源</string>\n    <string name=\"no_manga_sources_text\">启用任意图源即可在线阅读漫画</string>\n    <string name=\"random\">随机</string>\n    <string name=\"categories_delete_confirm\">确定删除选中分类？ \\n该分类中的所有漫画将丢失且无法恢复。</string>\n    <string name=\"reorder\">重新排序</string>\n    <string name=\"empty\">分类为空</string>\n    <string name=\"explore\">浏览</string>\n    <string name=\"automatic_scroll\">自动翻页</string>\n    <string name=\"reader_info_bar\">显示阅读状态</string>\n    <string name=\"comics_archive\">漫画压缩包</string>\n    <string name=\"folder_with_images\">图片文件夹</string>\n    <string name=\"importing_manga\">漫画导入中</string>\n    <string name=\"reader_info_pattern\">第 %1$d/%2$d 章 第 %3$d/%4$d 页</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">共 %2$d ，已启用 %1$d</string>\n    <string name=\"size_s\">大小：%s</string>\n    <string name=\"confirm_exit\">再按一次返回键退出</string>\n    <string name=\"exit_confirmation_summary\">按两次返回键退出应用</string>\n    <string name=\"exit_confirmation\">退出确认</string>\n    <string name=\"saved_manga\">已保存漫画</string>\n    <string name=\"pages_cache\">页面缓存</string>\n    <string name=\"other_cache\">其他缓存</string>\n    <string name=\"storage_usage\">存储占用</string>\n    <string name=\"available\">可用</string>\n    <string name=\"removed_from_favourites\">收藏漫画已删除</string>\n    <string name=\"options\">选项</string>\n    <string name=\"incognito_mode\">无痕模式</string>\n    <string name=\"no_chapters\">暂无章节</string>\n    <string name=\"import_completed\">导入完毕</string>\n    <string name=\"import_completed_hint\">可删除原文件节省存储空间</string>\n    <string name=\"import_will_start_soon\">即将开始导入</string>\n    <string name=\"feed\">订阅</string>\n    <string name=\"memory_usage_pattern\">%1$s - %2$s</string>\n    <string name=\"not_found_404\">没有章节或已被删除</string>\n    <string name=\"reader_control_ltr_summary\">硬件输入设备控制下，翻页方向将不随阅读模式调整。例：按下右键总是翻到下一页</string>\n    <string name=\"reader_control_ltr\">人性化操作</string>\n    <string name=\"history_shortcuts_summary\">长按应用图标显示最近阅读的漫画</string>\n    <string name=\"history_shortcuts\">显示最近阅读漫画的快捷方式</string>\n    <string name=\"reset\">重置</string>\n    <string name=\"color_correction\">颜色校正</string>\n    <string name=\"brightness\">亮度</string>\n    <string name=\"contrast\">对比度</string>\n    <string name=\"text_unsaved_changes_prompt\">保存还是放弃未保存的更改？</string>\n    <string name=\"discard\">放弃</string>\n    <string name=\"error_no_space_left\">剩余空间不足</string>\n    <string name=\"reader_slider\">显示滑动进度条</string>\n    <string name=\"webtoon_zoom\">条漫缩放</string>\n    <string name=\"network_unavailable\">网络未连接</string>\n    <string name=\"network_unavailable_hint\">打开 Wi-Fi 或移动网络开始在线阅读漫画</string>\n    <string name=\"clear_new_chapters_counters\">同时清除新章节详细信息</string>\n    <string name=\"server_error\">服务器错误 (%1$d)。请稍后重试</string>\n    <string name=\"compact\">列表</string>\n    <string name=\"source_disabled\">图源已禁用</string>\n    <string name=\"prefetch_content\">内容预加载</string>\n    <string name=\"mark_as_current\">标为当前进度</string>\n    <string name=\"language\">语言</string>\n    <string name=\"enable_logging\">启用日志记录</string>\n    <string name=\"share_logs\">分享日志</string>\n    <string name=\"enable_logging_summary\">出于调试目的记录一些操作，若不知道有何用处请不要自行开启</string>\n    <string name=\"show_suspicious_content\">显示可疑内容</string>\n    <string name=\"theme_name_dynamic\">动态</string>\n    <string name=\"color_theme\">配色主题</string>\n    <string name=\"show_in_grid_view\">网格视图</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"services\">服务</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"nothing_here\">一片荒漠</string>\n    <string name=\"scrobbling_empty_hint\">要记录阅读进度，在漫画详情页里选择【菜单】→【进度记录】。</string>\n    <string name=\"allow_unstable_updates\">允许更新至测试版本</string>\n    <string name=\"allow_unstable_updates_summary\">接收测试版本的更新通知</string>\n    <string name=\"download_started\">下载已开始</string>\n    <string name=\"user_agent\">UserAgent 标识</string>\n    <string name=\"settings_apply_restart_required\">重启程序后生效</string>\n    <string name=\"sources_reorder_tip\">点击并按住项目排序</string>\n    <string name=\"got_it\">知道了</string>\n    <string name=\"speed\">速度</string>\n    <string name=\"show_on_shelf\">在收藏中显示</string>\n    <string name=\"comics_archive_import_description\">可选择一个或多个 CBZ 或 ZIP 文件，每个文件都将识别为一个单独的漫画。</string>\n    <string name=\"folder_with_images_import_description\">可选择一个包含压缩包或图片的文件夹，每个压缩包 (或子文件夹) 都会被识别为一个章节。</string>\n    <string name=\"find_similar\">搜索相似漫画</string>\n    <string name=\"web_view_unavailable\">WebView不可用：检查是否已安装WebView</string>\n    <string name=\"sync_host_description\">可使用自建同步服务器或默认同步服务器，若不知道有何用处请不要自行修改。</string>\n    <string name=\"mirror_switching\">自动选择镜像网址</string>\n    <string name=\"mirror_switching_summary\">若存在可用的镜像网址，在出错时开始自动切换</string>\n    <string name=\"paused\">已暂停</string>\n    <string name=\"downloads_wifi_only_summary\">切换到移动网络时停止下载</string>\n    <string name=\"remove_completed\">清除已完成任务</string>\n    <string name=\"cancel_all\">取消所有下载任务</string>\n    <string name=\"downloads_wifi_only\">仅在连接 Wi-Fi 时下载</string>\n    <string name=\"enable\">启用</string>\n    <string name=\"no_thanks\">不了谢谢</string>\n    <string name=\"sync_settings\">同步设定</string>\n    <string name=\"server_address\">服务器地址</string>\n    <string name=\"pause\">暂停</string>\n    <string name=\"resume\">继续</string>\n    <string name=\"ignore_ssl_errors\">忽略 SSL 证书错误</string>\n    <string name=\"text_downloads_list_holder\">暂无下载任务</string>\n    <string name=\"downloads_resumed\">下载已恢复</string>\n    <string name=\"downloads_paused\">下载已暂停</string>\n    <string name=\"downloads_removed\">下载记录已清除</string>\n    <string name=\"downloads_cancelled\">下载已取消</string>\n    <string name=\"suggestions_enable_prompt\">想要接收个性化的漫画推荐吗？</string>\n    <string name=\"suggestion_manga\">推荐：%s</string>\n    <string name=\"suggestions_notifications_summary\">偶尔显示漫画推荐通知</string>\n    <string name=\"more\">更多</string>\n    <string name=\"cancel_all_downloads_confirm\">将取消所有进行中的下载，部分已下载完成的数据将会丢失</string>\n    <string name=\"remove_completed_downloads_confirm\">下载记录将会永久清除，已下载文件不受影响</string>\n    <string name=\"sync_auth_hint\">可登陆已有账号或创建新账号</string>\n    <string name=\"address\">地址</string>\n    <string name=\"clear_network_cache\">清除网络缓存</string>\n    <string name=\"proxy\">代理</string>\n    <string name=\"type\">类型</string>\n    <string name=\"invalid_value_message\">无效值</string>\n    <string name=\"network\">网络</string>\n    <string name=\"restore_summary\">从以前创建的备份恢复</string>\n    <string name=\"show_pages_numbers_summary\">在屏幕右下角显示页码</string>\n    <string name=\"clear_source_cookies_summary\">仅清除特定域名的 Cookies，大多数情况下会使网站授权失效</string>\n    <string name=\"data_and_privacy\">数据与隐私</string>\n    <string name=\"reader_info_bar_summary\">在屏幕顶部显示当前时间和阅读进度</string>\n    <string name=\"webtoon_zoom_summary\">允许在条漫模式下使用缩放手势</string>\n    <string name=\"invalid_port_number\">无效端口</string>\n    <string name=\"no_access_to_file\">没有权限访问该文件或目录</string>\n    <string name=\"local_manga_directories\">本地漫画目录</string>\n    <string name=\"port\">端口</string>\n    <string name=\"download_option_all_unread\">所有未读章节</string>\n    <string name=\"pick_custom_directory\">选择自定义目录</string>\n    <string name=\"password\">密码</string>\n    <string name=\"download_option_whole_manga\">整部漫画</string>\n    <string name=\"download_option_manual_selection\">手动选择章节</string>\n    <string name=\"description\">简介</string>\n    <string name=\"images_proxy_title\">图像加载优化代理</string>\n    <string name=\"username\">用户名</string>\n    <string name=\"download_option_all_unread_b\">所有未读章节 (%s)</string>\n    <string name=\"authorization_optional\">授权 (可选)</string>\n    <string name=\"download_option_first_n_chapters\">前 %s</string>\n    <string name=\"downloaded\">已下载</string>\n    <string name=\"download_option_next_unread_n_chapters\">后续未读的 %s</string>\n    <string name=\"images_procy_description\">尽可能使用 wsrv.nl 代理服务减少流量使用并加快图片加载</string>\n    <string name=\"invert_colors\">反色</string>\n    <string name=\"related_manga\">相关漫画</string>\n    <string name=\"voice_search\">语音搜索</string>\n    <string name=\"this_month\">本月</string>\n    <string name=\"languages\">语言</string>\n    <string name=\"captcha_required_summary\">%s 需要通过人机验证才能正常运行</string>\n    <string name=\"progress\">阅读进度</string>\n    <string name=\"error_corrupted_file\">回传的数据无效或文件已损坏</string>\n    <string name=\"related_manga_summary\">显示相关漫画，可能并不相关或没有相关漫画</string>\n    <string name=\"tracker_wifi_only_summary\">使用移动网络时停止检查新章节</string>\n    <string name=\"order_added\">添加日期（降序）</string>\n    <string name=\"on_device\">本地</string>\n    <string name=\"moved_to_top\">已移动至顶部</string>\n    <string name=\"data_not_restored_text\">备份文件不正确</string>\n    <string name=\"unknown\">未知</string>\n    <string name=\"in_progress\">进行中</string>\n    <string name=\"items_limit_exceeded\">已达项目添加上限</string>\n    <string name=\"data_not_restored\">数据恢复失败</string>\n    <string name=\"directories\">存储目录</string>\n    <string name=\"manage_categories\">分类管理</string>\n    <string name=\"color_light\">浅色</string>\n    <string name=\"search_hint\">输入漫画标题、类别或图源名称</string>\n    <string name=\"main_screen_sections\">主页底部导航栏</string>\n    <string name=\"advanced\">高级</string>\n    <string name=\"color_dark\">深色</string>\n    <string name=\"too_many_requests_message\">请求次数过多，请稍后重试</string>\n    <string name=\"suggestions_wifi_only_summary\">使用移动网络时停止推荐漫画</string>\n    <string name=\"background\">阅读背景颜色</string>\n    <string name=\"manga_list\">漫画列表</string>\n    <string name=\"disable_nsfw\">隐藏成人内容</string>\n    <string name=\"color_white\">白色</string>\n    <string name=\"to_top\">移动至顶部</string>\n    <string name=\"show\">显示</string>\n    <string name=\"color_black\">黑色</string>\n    <string name=\"zoom_in\">放大</string>\n    <string name=\"reader_zoom_buttons_summary\">在右下角显示放大缩小按钮</string>\n    <string name=\"reader_zoom_buttons\">缩放按钮</string>\n    <string name=\"zoom_out\">缩小</string>\n    <string name=\"keep_screen_on\">保持屏幕常亮</string>\n    <string name=\"keep_screen_on_summary\">阅读时保持亮屏</string>\n    <string name=\"list_options\">显示选项</string>\n    <string name=\"suggest_new_sources\">推荐新增图源</string>\n    <string name=\"enhanced_colors_summary\">减少色带，但可能会影响性能</string>\n    <string name=\"enhanced_colors\">32位色彩模式</string>\n    <string name=\"suggest_new_sources_summary\">应用更新后推送新增图源</string>\n    <string name=\"frequency_every_day\">每天一次</string>\n    <string name=\"categories\">分类</string>\n    <string name=\"backup_frequency\">备份频率</string>\n    <string name=\"periodic_backups_enable\">定期备份</string>\n    <string name=\"frequency_every_2_days\">每两天一次</string>\n    <string name=\"frequency_once_per_week\">每周一次</string>\n    <string name=\"periodic_backups\">自动备份</string>\n    <string name=\"frequency_twice_per_month\">每月两次</string>\n    <string name=\"frequency_once_per_month\">每月一次</string>\n    <string name=\"last_successful_backup\">最近备份成功：%s</string>\n    <string name=\"backups_output_directory\">备份文件保存目录</string>\n    <string name=\"download_option_all_chapters\">已翻译的章节 (%s)</string>\n    <string name=\"state_upcoming\">即将推出</string>\n    <string name=\"by_name_reverse\">名称（降序）</string>\n    <string name=\"manage_sources\">图源管理</string>\n    <string name=\"catalog\">图源目录</string>\n    <string name=\"content_type_comics\">美漫</string>\n    <string name=\"content_type_other\">其他</string>\n    <string name=\"content_type_manga\">日漫</string>\n    <string name=\"source_summary_pattern\">%1$s，%2$s</string>\n    <string name=\"sources_catalog\">图源目录</string>\n    <string name=\"source_enabled\">图源已启用</string>\n    <string name=\"content_type_hentai\">成人</string>\n    <string name=\"no_manga_sources_found\">未搜索到可用图源</string>\n    <string name=\"no_manga_sources_catalog_text\">此页面暂无可用图源，或可能已添加所有可用图源。\n\\n敬请期待后续更新</string>\n    <string name=\"welcome_text\">请选择需要启用的图源，可之后在设置中更改</string>\n    <string name=\"sync_auth\">网络同步</string>\n    <string name=\"downloads_settings_info\">若出现服务器阻塞的情况，可在图源设置中为每个图源单独启用限速下载功能</string>\n    <string name=\"skip\">跳过</string>\n    <string name=\"backup_date_\">备份日期：%s</string>\n    <string name=\"content_rating\">内容分级</string>\n    <string name=\"genres_exclude\">类别屏蔽</string>\n    <string name=\"rating_safe\">全年龄</string>\n    <string name=\"rating_suggestive\">R15</string>\n    <string name=\"rating_adult\">R18</string>\n    <string name=\"lock_screen_rotation\">锁定屏幕方向</string>\n    <string name=\"default_tab\">漫画详情页默认界面</string>\n    <string name=\"grayscale\">灰度</string>\n    <string name=\"globally\">全局</string>\n    <string name=\"this_manga\">此漫画</string>\n    <string name=\"color_correction_apply_text\">这些设置可以全局应用，也可以只应用于当前漫画。即使全局应用，也不会覆盖单独的设置。</string>\n    <string name=\"apply\">应用</string>\n    <string name=\"online_variant\">在线版本</string>\n    <string name=\"restore\">恢复</string>\n    <string name=\"state_abandoned\">已腰斩</string>\n    <string name=\"manual\">手动</string>\n    <string name=\"available_d\">%1$d 个可用</string>\n    <string name=\"disable_nsfw_summary\">关闭含有成人内容的图源并尽可能从列表中隐藏成人漫画</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"error_filter_locale_genre_not_supported\">此图源不支持同时按类别和区域筛选</string>\n    <string name=\"error_filter_states_genre_not_supported\">此图源不支持同时按类别和状态筛选</string>\n    <string name=\"genres_search_hint\">请输入类别名称</string>\n    <string name=\"state_paused\">休刊中</string>\n    <string name=\"reader_optimize\">降低内存占用 (测试)</string>\n    <string name=\"reader_optimize_summary\">降低当前画面外的页面质量以减少内存占用</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">也许能帮助解决下载过程中的相关问题</string>\n    <string name=\"error_search_not_supported\">此图源不支持搜索</string>\n    <string name=\"state\">状态</string>\n    <string name=\"error_multiple_genres_not_supported\">此图源不支持按多个类别筛选</string>\n    <string name=\"error_multiple_states_not_supported\">此图源不支持按多个状态筛选</string>\n    <string name=\"by_relevance\">关联</string>\n    <string name=\"mark_as_completed\">标记为已读</string>\n    <string name=\"mark_as_completed_prompt\">确定将选定漫画标记为已读？\n\\n\n\\n警告： 当前的阅读进度将会丢失。</string>\n    <string name=\"category_hidden_done\">此分类已从主页隐藏，可通过菜单 → 分类管理来访问</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"volume_\">第 %d 卷</string>\n    <string name=\"volume_unknown\">未知卷</string>\n    <string name=\"incognito_mode_hint\">阅读进度不会保存</string>\n    <string name=\"vertical\">从上到下</string>\n    <string name=\"last_read\">最近阅读（降序）</string>\n    <string name=\"switch_pages_volume_buttons_summary\">使用音量键翻页</string>\n    <string name=\"show_menu\">显示菜单</string>\n    <string name=\"toggle_ui\">显示/隐藏 UI</string>\n    <string name=\"prev_chapter\">上一章</string>\n    <string name=\"next_chapter\">下一章</string>\n    <string name=\"prev_page\">上一页</string>\n    <string name=\"next_page\">下一页</string>\n    <string name=\"switch_pages_volume_buttons\">音量键翻页</string>\n    <string name=\"long_tap_action\">长按操作</string>\n    <string name=\"none\">无</string>\n    <string name=\"reader_actions_summary\">配置屏幕点按区域的操作</string>\n    <string name=\"reader_actions\">阅读操作</string>\n    <string name=\"tap_action\">点按操作</string>\n    <string name=\"use_two_pages_landscape\">横屏时开启双页模式 (测试)</string>\n    <string name=\"config_reset_confirm\">确定重置为默认设置？重置后无法撤销。</string>\n    <string name=\"email_password_enter_hint\">输入邮箱和密码以继续</string>\n    <string name=\"fullscreen_mode\">全屏模式</string>\n    <string name=\"default_webtoon_zoom_out\">条漫默认缩小值</string>\n    <string name=\"reader_fullscreen_summary\">将系统状态栏与通知栏隐藏</string>\n    <string name=\"suggestions_unavailable_text\">漫画推荐功能已禁用</string>\n    <string name=\"check_for_new_chapters_disabled\">章节检查更新功能已禁用</string>\n    <string name=\"reading_time_estimation\">在漫画详情页显示估计阅读时间</string>\n    <string name=\"reading_time_estimation_summary\">估计阅读时间可能会不准确</string>\n    <string name=\"show_labels_in_navbar\">显示底部导航栏标签</string>\n    <string name=\"ask_for_dest_dir_every_time\">保存图片时总是询问保存目录</string>\n    <string name=\"pages_saving\">图片保存</string>\n    <string name=\"default_page_save_dir\">图片默认保存目录</string>\n    <string name=\"remove_from_history\">删除阅读历史</string>\n    <string name=\"location\">存储路径</string>\n    <string name=\"automatic\">自动选择</string>\n    <string name=\"single_cbz_file\">保存为单个 CBZ 文件</string>\n    <string name=\"multiple_cbz_files\">保存为多个 CBZ 文件</string>\n    <string name=\"preferred_download_format\">下载章节保存方式</string>\n    <string name=\"reading_stats\">阅读统计</string>\n    <string name=\"other_manga\">其他漫画</string>\n    <string name=\"less_than_minute\">不到1分钟</string>\n    <string name=\"statistics\">阅读统计</string>\n    <string name=\"month\">月</string>\n    <string name=\"clear_stats_confirm\">确定清除所有统计记录？清除后无法恢复。</string>\n    <string name=\"pages_read_s\">已读页数： %s</string>\n    <string name=\"clear_stats\">清除统计记录</string>\n    <string name=\"stats_cleared\">统计记录已清除</string>\n    <string name=\"week\">周</string>\n    <string name=\"all_time\">所有时间</string>\n    <string name=\"day\">日</string>\n    <string name=\"three_months\">三个月</string>\n    <string name=\"empty_stats_text\">选择的时间段没有阅读统计记录</string>\n    <string name=\"migrate\">换源</string>\n    <string name=\"delete_read_chapters\">删除已读章节</string>\n    <string name=\"no_chapters_deleted\">没有章节可供删除</string>\n    <string name=\"chapters_deleted_pattern\">已删除 %1$s, 释放了 %2$s 的存储空间</string>\n    <string name=\"delete_read_chapters_summary\">从本地设备中删除已读章节来释放存储空间</string>\n    <string name=\"alternatives\">漫画换源</string>\n    <string name=\"manga_migration\">即将换源</string>\n    <string name=\"migration_completed\">换源成功</string>\n    <string name=\"chapters_grid_view\">网格视图</string>\n    <string name=\"delete_read_chapters_prompt\">将会删除所有储存在本地并被标记为已读的章节，可之后再重新下载，但已导入的章节将永久丢失</string>\n    <string name=\"delete_read_chapters_auto\">自动删除已读章节</string>\n    <string name=\"runs_on_app_start\">启动应用时开始删除</string>\n    <string name=\"migrate_confirmation\">将会把历史记录和收藏中 \\\"%2$s\\\" 图源的 \\\"%1$s\\\" 替换成 \\\"%4$s\\\" 图源的 \\\"%3$s\\\"（若资源存在）</string>\n    <string name=\"unread\">阅读进度（升序）</string>\n    <string name=\"order_oldest\">最旧</string>\n    <string name=\"long_ago_read\">最近阅读（升序）</string>\n    <string name=\"split_by_translations\">翻译版本分开显示</string>\n    <string name=\"split_by_translations_summary\">按翻译版本分开显示章节，而不是显示在单一列表中</string>\n    <string name=\"enable_source\">启用图源</string>\n    <string name=\"unsupported_source\">不支持此漫画图源</string>\n    <string name=\"show_pages_thumbs\">显示漫画缩略图</string>\n    <string name=\"show_pages_thumbs_summary\">在漫画详情页启用漫画缩略图（页面）栏</string>\n    <string name=\"error_no_data_received\">未从服务器接收到任何数据</string>\n    <string name=\"unsupported_backup_message\">请选择正确的 Kotatsu 备份文件</string>\n    <string name=\"minutes_short\">%d 分</string>\n    <string name=\"hours_short\">%d 时</string>\n    <string name=\"hours_minutes_short\">%1$d 时 %2$d 分</string>\n    <string name=\"fix\">修复</string>\n    <string name=\"missing_storage_permission\">无访问外部存储漫画权限</string>\n    <string name=\"last_used\">上次打开</string>\n    <string name=\"show_updated\">显示更新</string>\n    <string name=\"webtoon_gaps_summary\">条漫模式下在页与页之间添加添加间距</string>\n    <string name=\"webtoon_gaps\">条漫分割</string>\n    <string name=\"pin_navigation_ui\">固定导航 UI</string>\n    <string name=\"frequency_of_check\">自动检查更新频率</string>\n    <string name=\"new_chapters_pattern\">%1$s: %2$d</string>\n    <string name=\"pin_navigation_ui_summary\">当上下滑动时不隐藏导航栏和搜索框</string>\n    <string name=\"more_frequently\">高频</string>\n    <string name=\"less_frequently\">低频</string>\n    <string name=\"authors\">作者</string>\n    <string name=\"search_suggestions\">搜索建议</string>\n    <string name=\"suggested_queries\">查询建议</string>\n    <string name=\"recent_queries\">最近搜索</string>\n    <string name=\"blocked_by_server_message\">你的访问已被服务器拦截，请尝试使用不同的网络连接访问（如VPN代理等）</string>\n    <string name=\"disable\">禁用</string>\n    <string name=\"sources_disabled\">图源已禁用</string>\n    <string name=\"disable_connectivity_check\">关闭连接连通性检查</string>\n    <string name=\"disable_connectivity_check_summary\">若连通性检查存在问题可打开此选项（例：即使连接了网络但依旧提示网络断开）</string>\n    <string name=\"ignore_ssl_errors_summary\">连接在线图源时若SSL证书出现问题，可关闭SSL证书认证，关闭后对安全性有所影响，需要重启应用来更改设置。</string>\n    <string name=\"disable_nsfw_notifications\">禁用成人内容相关通知</string>\n    <string name=\"disable_nsfw_notifications_summary\">不接收成人漫画的更新通知</string>\n    <string name=\"tracker_debug_info\">章节检查更新日志</string>\n    <string name=\"tracker_debug_info_summary\">记录漫画后台更新时的调试日志</string>\n    <string name=\"_new\">最新</string>\n    <string name=\"screenshots_block_incognito\">开启无痕模式时禁止</string>\n    <string name=\"all_languages\">所有语言</string>\n    <string name=\"image_server\">图片质量</string>\n    <string name=\"crop_pages\">页面裁切</string>\n    <string name=\"pin\">置顶</string>\n    <string name=\"unpin\">取消置顶</string>\n    <string name=\"source_pinned\">图源已置顶</string>\n    <string name=\"source_unpinned\">图源置顶已取消</string>\n    <string name=\"sources_unpinned\">图源置顶已取消</string>\n    <string name=\"sources_pinned\">图源已置顶</string>\n    <string name=\"recent_sources\">最近使用图源</string>\n    <string name=\"percent_read\">已读章节百分比</string>\n    <string name=\"percent_left\">剩余章节百分比</string>\n    <string name=\"chapters_read\">已读章节数</string>\n    <string name=\"chapters_left\">剩余章节数</string>\n    <string name=\"external_source\">外部插件</string>\n    <string name=\"plugin_incompatible\">插件不兼容或出现了外部错误，请确保你已经将 Kotatsu 以及插件更新至最新版本</string>\n    <string name=\"connection_ok\">连接正常</string>\n    <string name=\"text_empty_holder_secondary_filtered\">无筛选结果</string>\n    <string name=\"invalid_proxy_configuration\">无效代理配置</string>\n    <string name=\"show_quick_filters\">显示快速筛选</string>\n    <string name=\"show_quick_filters_summary\">可通过某些确定的范围来筛选漫画</string>\n    <string name=\"invalid_server_address_message\">无效服务器地址</string>\n    <string name=\"sfw\">全年龄</string>\n    <string name=\"too_many_requests_message_retry\">请求过多，%s 之后再尝试</string>\n    <string name=\"seconds_short\">%d 秒</string>\n    <string name=\"skip_all\">跳过全部</string>\n    <string name=\"stuck\">受限</string>\n    <string name=\"retry\">重试</string>\n    <string name=\"minutes_seconds_short\">%1$d 分 %2$d 秒</string>\n    <string name=\"not_in_favorites\">非收藏</string>\n    <string name=\"updated_long_ago\">最近更新（倒序）</string>\n    <string name=\"low_rating\">评分（升序）</string>\n    <string name=\"unpopular\">热度（升序）</string>\n    <string name=\"sort_order_asc\">升序</string>\n    <string name=\"sort_order_desc\">降序</string>\n    <string name=\"by_date\">日期</string>\n    <string name=\"popularity\">热度</string>\n    <string name=\"scrobbler_auth_required\">登录 %s 以继续</string>\n    <string name=\"scrobbler_auth_intro\">登录以连接 %s，这个操作会允许记录你的漫画阅读进度和漫画状态</string>\n    <string name=\"unstable_feature\">不稳定功能</string>\n    <string name=\"unstable_feature_summary\">实验性功能，请确保您已备份，以防数据丢失</string>\n    <string name=\"downloads_background\">后台下载</string>\n    <string name=\"download_new_chapters\">下载新章节</string>\n    <string name=\"fixing_manga\">正在修复漫画</string>\n    <string name=\"fixed\">修复成功</string>\n    <string name=\"no_fix_required\">没有修复 \\\"%s\\\" 的请求</string>\n    <string name=\"no_alternatives_found\">找不到 \\\"%s\\\" 的其他版本</string>\n    <string name=\"manga_fix_prompt\">这个功能将会从其他来源寻找你所选择的漫画，此操作耗时较长且会在后台进行</string>\n    <string name=\"content_type_novel\">小说</string>\n    <string name=\"content_type_manhua\">国漫</string>\n    <string name=\"content_type_manhwa\">韩漫</string>\n    <string name=\"recently_added\">添加日期（降序）</string>\n    <string name=\"added_long_ago\">添加日期（升序）</string>\n    <string name=\"popular_today\">今日热门</string>\n    <string name=\"popular_in_week\">本周热门</string>\n    <string name=\"popular_in_month\">本月热门</string>\n    <string name=\"popular_in_year\">年度热门</string>\n    <string name=\"original_language\">原语言</string>\n    <string name=\"year\">年</string>\n    <string name=\"demographics\">受众人群</string>\n    <string name=\"demographic_shounen\">少年漫</string>\n    <string name=\"demographic_shoujo\">少女漫</string>\n    <string name=\"demographic_seinen\">青年漫</string>\n    <string name=\"demographic_josei\">女性向</string>\n    <string name=\"years\">年</string>\n    <string name=\"any\">所有</string>\n    <string name=\"filter_search_warning\">本图源不支持筛选搜索，筛选已被重置</string>\n    <string name=\"manga_replaced\">来自(%2$s)源的\\\"%1$s\\\"漫画已替换成来自(%4$s)源的\\\"%3$s\\\"</string>\n    <string name=\"popular_in_hour\">一小时内热门</string>\n    <string name=\"demographic_kodomo\">子供向</string>\n    <string name=\"content_type_one_shot\">短篇</string>\n    <string name=\"content_type_doujinshi\">同人志</string>\n    <string name=\"content_type_image_set\">图片集</string>\n    <string name=\"content_type_game_cg\">游戏CG</string>\n    <string name=\"content_type_artist_cg\">艺术家CG</string>\n    <string name=\"debug\">调试</string>\n    <string name=\"source_code\">源代码</string>\n    <string name=\"user_manual\">用户手册</string>\n    <string name=\"telegram_group\">Telegram 群组</string>\n    <string name=\"manga_with_downloaded_chapters\">当存在已下载章节的漫画时</string>\n    <string name=\"error_image_format\">不支持的图片格式：%s</string>\n    <string name=\"save_manga\">下载漫画</string>\n    <string name=\"start_download\">开始下载</string>\n    <string name=\"save_manga_confirm\">要下载选择的漫画吗？下载漫画会消耗流量和磁盘空间</string>\n    <string name=\"genre\">类别</string>\n    <string name=\"destination_directory\">目标目录</string>\n    <string name=\"download_added\">已开始下载</string>\n    <string name=\"more_options\">更多设置</string>\n    <string name=\"chapter_selection_hint\">可在章节列表长按选择章节下载。</string>\n    <string name=\"chapters_all\">全部</string>\n    <string name=\"allow_always\">总是允许</string>\n    <string name=\"download_over_cellular\">使用数据流量下载</string>\n    <string name=\"allow_once\">允许一次</string>\n    <string name=\"ask_every_time\">每次询问</string>\n    <string name=\"dont_allow\">拒绝</string>\n    <string name=\"download_cellular_confirm\">确定使用数据流量下载漫画？</string>\n    <string name=\"landscape\">横屏</string>\n    <string name=\"screen_orientation\">屏幕方向</string>\n    <string name=\"portrait\">竖屏</string>\n    <string name=\"plugin_incompatible_with_cause\">插件错误： %s\\n请确认正在使用的插件及Kotatsu是否为最新版本</string>\n    <string name=\"max_backups_count\">最大备份数</string>\n    <string name=\"access_denied_403\">拒绝访问 (403)</string>\n    <string name=\"error_not_image\">无效格式：预期是图片但得到了 %s</string>\n    <string name=\"pages_saved\">图片已保存</string>\n    <string name=\"delete_old_backups\">删除旧备份文件</string>\n    <string name=\"delete_old_backups_summary\">自动删除旧备份文件以释放存储空间</string>\n    <string name=\"handle_links\">处理链接</string>\n    <string name=\"handle_links_summary\">处理来自外部程序的漫画链接（如 Web 浏览器）， 可能需要在程序的系统设置中手动启用</string>\n    <string name=\"email\">电子邮箱</string>\n    <string name=\"captcha_required_message\">此图源需要通过人机验证以继续操作</string>\n    <string name=\"author\">作者</string>\n    <string name=\"rating\">评分</string>\n    <string name=\"source\">来源</string>\n    <string name=\"translation\">翻译</string>\n    <string name=\"incognito\">无痕阅读</string>\n    <string name=\"show_slider\">显示滑块</string>\n    <string name=\"error_connection_reset\">远程主机重置了连接</string>\n    <string name=\"backup_tg_echo\">测试消息</string>\n    <string name=\"backup_tg_check\">检查 API 是否可用</string>\n    <string name=\"backup_tg_id_not_set\">未设置聊天ID</string>\n    <string name=\"telegram_chat_id\">Telegram 聊天 ID</string>\n    <string name=\"open_telegram_bot\">打开 Telegram 机器人</string>\n    <string name=\"send_backups_telegram\">用 Telegram 发送备份</string>\n    <string name=\"telegram_chat_id_summary\">输入接收备份的 Telegram 聊天 ID</string>\n    <string name=\"test_connection\">测试连接</string>\n    <string name=\"open_telegram_bot_summary\">按下打开和 Kotasu Backup 机器人的聊天</string>\n    <string name=\"clear_database\">清除数据库</string>\n    <string name=\"clear_database_summary\">删除未使用的漫画信息</string>\n    <string name=\"enable_all_sources\">启用所有图源</string>\n    <string name=\"all_sources_enabled\">已启用所有图源</string>\n    <string name=\"enable_all_sources_summary\">将启用所有可用图源</string>\n    <string name=\"reader_info_bar_transparent\">透明阅读状态栏</string>\n    <string name=\"restoring_backup\">正在恢复备份</string>\n    <string name=\"backup_restored_background\">将在后台恢复备份</string>\n    <string name=\"pages_slider\">页面切换滑动条</string>\n    <string name=\"screen_rotation_locked\">屏幕方向已锁定</string>\n    <string name=\"screen_rotation_unlocked\">屏幕方向已解锁</string>\n    <string name=\"chapters_and_pages\">章节与页面</string>\n    <string name=\"reader_controls_in_bottom_bar\">阅读器底部控制栏</string>\n    <string name=\"disable_captcha_notifications\">禁用人机验证通知</string>\n    <string name=\"disable_captcha_notifications_summary\">启用后将不会在本图源收到需要通过人机验证的通知，但会导致后台进程出现异常 (章节更新和推荐漫画等)</string>\n    <string name=\"global_search\">全局搜索</string>\n    <string name=\"badges_in_lists\">显示漫画标记</string>\n    <string name=\"chapter_volume_number\">第 %1$s 卷 第 %2$s 章</string>\n    <string name=\"chapter_number\">第 %s 章</string>\n    <string name=\"unnamed_chapter\">未命名章节</string>\n    <string name=\"search_disabled_sources\">在已禁用的图源中搜索</string>\n    <string name=\"error_details\">错误详细信息</string>\n    <string name=\"error_disclaimer_manga\">尝试在网络浏览器中打开漫画，以确保此漫画在图源中可用</string>\n    <string name=\"error_disclaimer_report\">可向开发者提交错误报告，将会帮助我们定位并修复错误</string>\n    <string name=\"error_disclaimer_app_outdated\">您的 Kotatsu 版本过旧，请下载最新版本以修复问题</string>\n    <string name=\"clear_browser_data\">清除浏览器数据</string>\n    <string name=\"no_write_permission_to_file\">写入文件权限被禁止</string>\n    <string name=\"clear_browser_data_summary\">点击后清除浏览器（如缓存、Cookies）数据，警告：图源授权可能变为无效</string>\n    <string name=\"nsfw_16\">16+</string>\n    <string name=\"exclude_nsfw_from_suggestions_summary\">搜索建议中将不会出现成人漫画，此选项可能对某些图源不生效</string>\n    <string name=\"include_disabled_sources\">包括已禁用图源</string>\n    <string name=\"suggestions_disabled_sources_summary\">从所有图源获取漫画推荐，包括已禁用图源</string>\n    <string name=\"simple\">默认</string>\n    <string name=\"tags_warnings\">高亮敏感类别</string>\n    <string name=\"tags_warnings_summary\">将可能会对大部分用户不友善的类别高亮</string>\n    <string name=\"error_non_file_uri\">选择的路径无法保存因其无法表示一个文件或存储目录</string>\n    <string name=\"link_to_manga_on_s\">在 %s 上连接漫画</string>\n    <string name=\"link_to_manga_in_app\">在 Kotatsu 上连接漫画</string>\n    <string name=\"manga_override_hint\">这些设置将影响漫画在应用内的显示</string>\n    <string name=\"pick_custom_file\">使用自定义封面</string>\n    <string name=\"change_cover\">更改封面</string>\n    <string name=\"use_default_cover\">使用默认封面</string>\n    <string name=\"pick_manga_page\">使用漫画页面</string>\n    <string name=\"page_switch_timer\">本页将在每 %d 秒切换到下一页</string>\n    <string name=\"incognito_for_nsfw\">成人漫画无痕模式</string>\n    <string name=\"incognito_mode_hint_nsfw\">此漫画可能包含成人内容，需要打开无痕模式继续浏览吗？</string>\n    <string name=\"dont_ask_again\">不再询问</string>\n    <string name=\"hide_from_main_screen\">从主页面中隐藏</string>\n    <string name=\"collapse\">折叠</string>\n    <string name=\"expand\">展开</string>\n    <string name=\"adblock\">屏蔽广告</string>\n    <string name=\"adblock_summary\">屏蔽内置浏览器内显示的广告 (实验性)</string>\n    <string name=\"theme_name_expressive\">印象 (实验性)</string>\n    <string name=\"collapse_long_description\">折叠过长简介</string>\n    <string name=\"changelog_summary\">最近已发布版本的更新日志</string>\n    <string name=\"changelog\">更新日志</string>\n    <string name=\"share_backup\">分享备份</string>\n    <string name=\"creating_backup\">创建备份中</string>\n    <string name=\"reader_multitask\">以独立窗口打开阅读器</string>\n    <string name=\"reader_multitask_summary\">允许阅读不同漫画时打开多个同时共存的阅读器</string>\n    <string name=\"theme_name_itsuka\">Itsuka</string>\n    <string name=\"theme_name_totoro\">Totoro</string>\n    <string name=\"main_screen\">主页</string>\n    <string name=\"main_screen_fab\">显示浮动继续阅读按钮</string>\n    <string name=\"main_screen_fab_summary\">一键续读。这个按钮不会在无痕模式或没有阅读历史时出现</string>\n    <string name=\"local_storage_cleanup\">清理本地存储</string>\n    <string name=\"packup_creation_failed\">创建备份文件失败</string>\n    <string name=\"reader_navigation_inverted\">反转操作</string>\n    <string name=\"reader_navigation_inverted_summary\">将音量键和硬件设备方向键的导航方向交换 (左/上/下/右)</string>\n    <string name=\"error_corrupted_zip\">损坏的 ZIP 文件 (%s)</string>\n    <string name=\"discord_rpc\">绑定 Discord 状态</string>\n    <string name=\"discord_token\">Discord Token</string>\n    <string name=\"discord_token_summary\">在这里输入你的 Discord Token 来绑定 Discord 状态</string>\n    <string name=\"discord_token_description\">输入你的 Discord Token 或者点击 %s 打开内置浏览器获取 Token</string>\n    <string name=\"discord_token_hint\">在这里复制你的 Discord Token</string>\n    <string name=\"discord_rpc_summary\">在 Discord 个人信息栏内显示你的阅读状态</string>\n    <string name=\"obtain\">获取</string>\n    <string name=\"discord_rpc_description\">正在 Kotatsu - 一个漫画阅读软件上阅读漫画</string>\n    <string name=\"reading_s\">正在阅读 %s</string>\n    <string name=\"rpc_skip_nsfw_summary\">阅读成人内容时不在 Discord 上显示阅读状态</string>\n    <string name=\"invalid_token\">非法Token：%s</string>\n    <string name=\"show_floating_control_button\">显示浮动控制按钮</string>\n    <string name=\"unavailable\">不可用</string>\n    <string name=\"no_chapters_in_manga\">没有找到此漫画的任何章节</string>\n    <string name=\"chapters_load_failed\">加载章节列表失败</string>\n    <string name=\"manga_restricted_description\">此漫画在当前图源无法阅读，尝试在别的图源中搜索此漫画或打开内置浏览器获取更多信息</string>\n    <string name=\"telegram_integration\">Telegram 集成</string>\n    <string name=\"test_parser\">测试图源</string>\n    <string name=\"search_everywhere\">在所有图源搜索</string>\n    <string name=\"additional_action_required\">需要额外操作</string>\n    <string name=\"book_effect\">护眼模式（蓝光过滤）</string>\n    <string name=\"read_on_s\">在 %s 查看</string>\n    <string name=\"pull_to_prev_chapter\">松开即可阅读上一章</string>\n    <string name=\"pull_to_next_chapter\">松开即可阅读下一章</string>\n    <string name=\"pull_top_no_prev\">到头了</string>\n    <string name=\"pull_bottom_no_next\">到底了</string>\n    <string name=\"enable_pull_gesture_title\">启用推拉手势</string>\n    <string name=\"enable_pull_gesture_summary\">条漫模式下使用推拉手势切换章节</string>\n    <string name=\"auto_double_foldable\">折叠设备自动双页</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rTW/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <plurals name=\"new_chapters\">\n        <item quantity=\"other\">%1$d 個新章節</item>\n    </plurals>\n    <plurals name=\"chapters\">\n        <item quantity=\"other\">%1$d 個章節</item>\n    </plurals>\n    <plurals name=\"minutes_ago\">\n        <item quantity=\"other\">%1$d 分鐘前</item>\n    </plurals>\n    <plurals name=\"hours_ago\">\n        <item quantity=\"other\">%1$d 小時前</item>\n    </plurals>\n    <plurals name=\"days_ago\">\n        <item quantity=\"other\">%1$d 天前</item>\n    </plurals>\n    <plurals name=\"months_ago\">\n        <item quantity=\"other\">%1$d 個月前</item>\n    </plurals>\n    <plurals name=\"hours\">\n        <item quantity=\"other\">%1$d 小時</item>\n    </plurals>\n    <plurals name=\"minutes\">\n        <item quantity=\"other\">%1$d 分鐘</item>\n    </plurals>\n    <plurals name=\"items\">\n        <item quantity=\"other\">%1$d 個項目</item>\n    </plurals>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rTW/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"show_notification_new_chapters_off\">您將不會收到通知，但新的章節將在清單中被凸顯</string>\n    <string name=\"computing_\">計算中…</string>\n    <string name=\"try_again\">重試</string>\n    <string name=\"history_is_empty\">尚無歷史紀錄</string>\n    <string name=\"operation_not_supported\">不支援此操作</string>\n    <string name=\"text_local_holder_secondary\">從線上來源儲存或者匯入檔案。</string>\n    <string name=\"text_history_holder_primary\">您所閱讀的將被顯示在此</string>\n    <string name=\"text_history_holder_secondary\">在側「瀏覽」部分找到想要閱讀的內容</string>\n    <string name=\"text_empty_holder_primary\">這裡有點空…</string>\n    <string name=\"feed_will_update_soon\">訂閱更新即將開始</string>\n    <string name=\"reader_mode_hint\">於此漫畫選擇的組態將會被記住</string>\n    <string name=\"data_restored_with_errors\">資料已還原，但有錯誤</string>\n    <string name=\"black_dark_theme_summary\">在 AMOLED 螢幕上使用更少電量</string>\n    <string name=\"auth_required\">登陸以檢視此內容</string>\n    <string name=\"text_clear_search_history_prompt\">永久地移除所有最近的搜尋查詢\\?</string>\n    <string name=\"text_clear_updates_feed_prompt\">永久地清除更新歷史資料\\?</string>\n    <string name=\"text_clear_cookies_prompt\">您將從所有來源中登出</string>\n    <string name=\"exclude_nsfw_from_history\">從歷史紀錄中排除 NSFW 漫畫</string>\n    <string name=\"text_suggestion_holder\">開始閱讀漫畫，您將得到個人化推薦</string>\n    <string name=\"suggestions_summary\">根據您的偏好推薦漫畫</string>\n    <string name=\"suggestions_excluded_genres_summary\">指定您不希望在推薦中看到的類型</string>\n    <string name=\"text_delete_local_manga_batch\">從裝置中永久地刪除選取項目\\?</string>\n    <string name=\"chapters_will_removed_background\">章節將在背景被刪除</string>\n    <string name=\"check_new_chapters_title\">檢查新的章節並通知關於它資訊</string>\n    <string name=\"download_slowdown_summary\">有助于避免阻断您的IP地址</string>\n    <string name=\"email_enter_hint\">輸入您的 email 以繼續</string>\n    <string name=\"new_sources_text\">有新的漫畫來源可用</string>\n    <string name=\"detect_reader_mode_summary\">自動檢測是否為 webtoon 漫畫</string>\n    <string name=\"show_reading_indicators_summary\">在歷史紀錄與最愛中顯示閱讀百分比</string>\n    <string name=\"confirm_exit\">再點擊一次返回鍵以退出</string>\n    <string name=\"local_storage\">本機儲存區</string>\n    <string name=\"reader_control_ltr_summary\">不要根據閱讀模式調整翻頁方向，例如，按右鍵總是切換到下一頁。此選項僅影響硬體輸入設備</string>\n    <string name=\"text_unsaved_changes_prompt\">儲存或放棄未儲存的變更？</string>\n    <string name=\"import_completed_hint\">您可以從儲存區中刪除原始檔案以節省空間</string>\n    <string name=\"favourites\">最愛</string>\n    <string name=\"chapters\">章節</string>\n    <string name=\"list\">清單</string>\n    <string name=\"grid\">網格</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"nothing_found\">沒有發現</string>\n    <string name=\"onboard_text\">選擇您想閱讀的漫畫的語言。您可以之後在設定中變更。</string>\n    <string name=\"clear_cookies_summary\">可以在出現一些問題時提供幫助。 所有授權將被視為無效</string>\n    <string name=\"history\">歷史紀錄</string>\n    <string name=\"error_occurred\">發生錯誤</string>\n    <string name=\"network_error\">網路錯誤</string>\n    <string name=\"details\">詳細資料</string>\n    <string name=\"chapter_d_of_d\">%1$d/%2$d 章節</string>\n    <string name=\"detailed_list\">詳細清單</string>\n    <string name=\"list_mode\">清單模式</string>\n    <string name=\"remote_sources\">漫畫來源</string>\n    <string name=\"loading_\">載入中…</string>\n    <string name=\"close\">關閉</string>\n    <string name=\"clear_history\">清除歷史紀錄</string>\n    <string name=\"suggestions_info\">所有的資料都在此裝置上進本機分析，並不會傳送至任何其他地方。</string>\n    <string name=\"exclude_nsfw_from_history_summary\">標記為 NSFW 的漫畫將永遠不會被加入到歷史資料中，您的進度也不會被儲存</string>\n    <string name=\"backup_information\">您可以建立您的歷史紀錄與最愛的備份，並還原它</string>\n    <string name=\"crash_text\">發生了一些問題。請向開發人員提交一個錯誤告告，以幫助我們修復它。</string>\n    <string name=\"text_file_not_supported\">選擇 ZIP 或 CBZ 檔案。</string>\n    <string name=\"no_bookmarks_summary\">您可以在閱讀漫畫時建立書籤</string>\n    <string name=\"show_notification_new_chapters_on\">您將會收到您正在閱讀的漫畫的更新通知</string>\n    <string name=\"tracker_warning\">一些裝置有不同的系統行為，這可能會破壞背景任務。</string>\n    <string name=\"protect_application_subtitle\">輸入密碼以啟動應用程式</string>\n    <string name=\"protect_application_summary\">在啟動 Kotatsu 時要求密碼</string>\n    <string name=\"chapters_empty\">此漫畫沒有章節</string>\n    <string name=\"text_feed_holder\">您想要閱讀的漫畫的新的章節會顯示在此</string>\n    <string name=\"history_shortcuts_summary\">通過長案應用程式圖示來提供最近的漫畫</string>\n    <string name=\"app_update_available\">已有可用的新版本應用程式</string>\n    <string name=\"password_length_hint\">密碼必須是 4 個字元以上</string>\n    <string name=\"auth_not_supported_by\">不支援在 %s 上登錄</string>\n    <string name=\"_s_deleted_from_local_storage\">\\\"%s\\\" 已從本機儲存區中刪除</string>\n    <string name=\"exit_confirmation_summary\">點擊兩次返回鍵以退出應用程式</string>\n    <string name=\"no_manga_sources_text\">啟用漫畫來源以閱讀線上漫畫</string>\n    <string name=\"text_delete_local_manga\">從裝置中永久刪除 \\\"%s\\\"?</string>\n    <string name=\"text_search_holder_secondary\">嘗試重新表述查詢。</string>\n    <string name=\"disable_battery_optimization_summary\">帮助进行背景更新检查</string>\n    <string name=\"exclude_nsfw_from_suggestions\">不要推薦 NSFW 漫畫</string>\n    <string name=\"add_new_category\">新分類</string>\n    <string name=\"read\">閱讀</string>\n    <string name=\"you_have_not_favourites_yet\">尚無最愛</string>\n    <string name=\"add_to_favourites\">加入最愛</string>\n    <string name=\"add\">加入</string>\n    <string name=\"save\">儲存</string>\n    <string name=\"share_s\">分享 %s</string>\n    <string name=\"search\">搜尋</string>\n    <string name=\"manga_downloading_\">下載中…</string>\n    <string name=\"download_complete\">已下載</string>\n    <string name=\"filter\">篩選器</string>\n    <string name=\"theme\">主題</string>\n    <string name=\"light\">淺色</string>\n    <string name=\"clear\">清除</string>\n    <string name=\"remove\">移除</string>\n    <string name=\"share_image\">分享圖片</string>\n    <string name=\"delete\">刪除</string>\n    <string name=\"clear_pages_cache\">清除頁面快取</string>\n    <string name=\"network\">網路</string>\n    <string name=\"webtoon_zoom_summary\">在 webtoon 模式中允許手勢縮放</string>\n    <string name=\"show_pages_numbers_summary\">在底部角落顯示頁碼</string>\n    <string name=\"reader_info_bar_summary\">在畫面頂部顯示目前時間與閱讀進度</string>\n    <string name=\"search_history_cleared\">已清除</string>\n    <string name=\"notifications\">通知</string>\n    <string name=\"download\">下載</string>\n    <string name=\"notifications_settings\">通知設定</string>\n    <string name=\"notification_sound\">通知聲音</string>\n    <string name=\"light_indicator\">LED 指示燈</string>\n    <string name=\"manga_shelf\">書架</string>\n    <string name=\"new_chapters\">新章節</string>\n    <string name=\"recent_manga\">最近</string>\n    <string name=\"cannot_find_available_storage\">沒有可用的儲存空間</string>\n    <string name=\"other_storage\">其他儲存</string>\n    <string name=\"done\">完成</string>\n    <string name=\"all_favourites\">所有最愛</string>\n    <string name=\"favourites_category_empty\">空白分類</string>\n    <string name=\"search_results\">搜尋結果</string>\n    <string name=\"new_version_s\">新版本: %s</string>\n    <string name=\"size_s\">大小: %s</string>\n    <string name=\"updates_feed_cleared\">已清除</string>\n    <string name=\"clear_updates_feed\">清除更新訂閱</string>\n    <string name=\"rotate_screen\">旋轉畫面</string>\n    <string name=\"update\">更新</string>\n    <string name=\"track_sources\">尋找更新</string>\n    <string name=\"dont_check\">不要檢查</string>\n    <string name=\"enter_password\">輸入密碼</string>\n    <string name=\"wrong_password\">密碼錯誤</string>\n    <string name=\"protect_application\">保護應用程式</string>\n    <string name=\"repeat_password\">重複密碼</string>\n    <string name=\"passwords_mismatch\">密碼不匹配</string>\n    <string name=\"about\">關於</string>\n    <string name=\"app_version\">版本 %s</string>\n    <string name=\"check_for_updates\">檢查更新</string>\n    <string name=\"no_update_available\">沒有可用的更新</string>\n    <string name=\"right_to_left\">由右至左</string>\n    <string name=\"create_category\">新分類</string>\n    <string name=\"scale_mode\">縮放模式</string>\n    <string name=\"backup_restore\">備份與還原</string>\n    <string name=\"create_backup\">建立資料備份</string>\n    <string name=\"restore_backup\">從備份中還原</string>\n    <string name=\"data_restored\">已還原</string>\n    <string name=\"data_restored_success\">所有資料已還原</string>\n    <string name=\"just_now\">現在</string>\n    <string name=\"yesterday\">昨日</string>\n    <string name=\"long_ago\">很久以前</string>\n    <string name=\"group\">群組</string>\n    <string name=\"today\">今日</string>\n    <string name=\"silent\">無聲</string>\n    <string name=\"cookies_cleared\">所有 cookies 已被移除</string>\n    <string name=\"clear_feed\">清除檔案</string>\n    <string name=\"check_for_new_chapters\">檢查新的章節</string>\n    <string name=\"reverse\">撤銷</string>\n    <string name=\"sign_in\">登錄</string>\n    <string name=\"default_s\">預設: %s</string>\n    <string name=\"next\">下一頁</string>\n    <string name=\"confirm\">確認</string>\n    <string name=\"about_app_translation_summary\">翻譯此應用程式</string>\n    <string name=\"screenshots_policy\">螢幕擷圖</string>\n    <string name=\"screenshots_allow\">允許</string>\n    <string name=\"screenshots_block_nsfw\">禁止 NSFW</string>\n    <string name=\"screenshots_block_all\">始終禁止</string>\n    <string name=\"suggestions\">推薦</string>\n    <string name=\"suggestions_enable\">啟用推薦</string>\n    <string name=\"enabled\">啟用</string>\n    <string name=\"always\">總是</string>\n    <string name=\"preload_pages\">預載頁面</string>\n    <string name=\"logged_in_as\">以 %s 登入</string>\n    <string name=\"nsfw\">18+</string>\n    <string name=\"various_languages\">各種語言</string>\n    <string name=\"search_chapters\">尋找章節</string>\n    <string name=\"percent_string_pattern\">%1$s%%</string>\n    <string name=\"suggestions_updating\">推薦更新</string>\n    <string name=\"suggestions_excluded_genres\">排除流派</string>\n    <string name=\"download_slowdown\">下載減緩</string>\n    <string name=\"back\">返回</string>\n    <string name=\"sync\">同步</string>\n    <string name=\"notifications_enable\">啟用通知</string>\n    <string name=\"name\">名稱</string>\n    <string name=\"edit\">編輯</string>\n    <string name=\"edit_category\">編輯分類</string>\n    <string name=\"tracking\">追蹤</string>\n    <string name=\"empty_favourite_categories\">沒有最愛分類</string>\n    <string name=\"logout\">登出</string>\n    <string name=\"bookmark_remove\">移除書籤</string>\n    <string name=\"bookmarks\">書籤</string>\n    <string name=\"use_fingerprint\">若可用，請使用生物辨識</string>\n    <string name=\"appwidget_shelf_description\">您最愛的漫畫</string>\n    <string name=\"appwidget_recent_description\">您最近閱讀的漫畫</string>\n    <string name=\"data_deletion\">資料刪除</string>\n    <string name=\"manage\">管理</string>\n    <string name=\"clear_new_chapters_counters\">也清除關於新章節的資訊</string>\n    <string name=\"compact\">緊湊</string>\n    <string name=\"source_disabled\">來源已禁用</string>\n    <string name=\"prefetch_content\">內容預先載入</string>\n    <string name=\"mark_as_current\">標記目前</string>\n    <string name=\"enable_logging\">啟動登入</string>\n    <string name=\"theme_name_sakura\">Sakura</string>\n    <string name=\"got_it\">知道了</string>\n    <string name=\"settings_apply_restart_required\">請重新啟動此應用程式以應用這些變更</string>\n    <string name=\"sources_reorder_tip\">點擊並按住一個項目以重新排序它們</string>\n    <string name=\"speed\">速度</string>\n    <string name=\"show_on_shelf\">在書架上顯示</string>\n    <string name=\"comics_archive_import_description\">您可以選擇一個或多個 .cbz 或 .zip 檔案，每個檔案將被認定為個別的漫畫。</string>\n    <string name=\"folder_with_images_import_description\">您可以選擇一個圖片資料夾或壓縮檔。每個壓縮檔 (或子目錄) 將被認定為個別的章節。</string>\n    <string name=\"find_similar\">尋找相似</string>\n    <string name=\"server_address\">伺服器位址</string>\n    <string name=\"ignore_ssl_errors\">忽略 SSL 錯誤</string>\n    <string name=\"mirror_switching\">自動選擇鏡像</string>\n    <string name=\"resume\">還原</string>\n    <string name=\"paused\">已暫停</string>\n    <string name=\"remove_completed\">移除已完成</string>\n    <string name=\"cancel_all\">取消全部</string>\n    <string name=\"suggestion_manga\">推薦: %s</string>\n    <string name=\"enable\">啟用</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"invalid_value_message\">無效的值</string>\n    <string name=\"downloaded\">已下載</string>\n    <string name=\"images_proxy_title\">圖片最佳化代理</string>\n    <string name=\"username\">使用者名稱</string>\n    <string name=\"password\">密碼</string>\n    <string name=\"invalid_port_number\">無效的連接埠編號</string>\n    <string name=\"share_logs\">分享日誌</string>\n    <string name=\"clear_source_cookies_summary\">僅清除指定網域的 cookie，大多數情況下會使授權會失效</string>\n    <string name=\"share\">分享</string>\n    <string name=\"create_shortcut\">建立捷徑…</string>\n    <string name=\"search_manga\">搜尋漫畫</string>\n    <string name=\"processing_\">處理中…</string>\n    <string name=\"newest\">最新</string>\n    <string name=\"by_rating\">評分</string>\n    <string name=\"sort_order\">排列順序</string>\n    <string name=\"dark\">深色</string>\n    <string name=\"follow_system\">跟隨系統</string>\n    <string name=\"pages\">頁數</string>\n    <string name=\"_import\">匯入</string>\n    <string name=\"no_description\">沒有描述</string>\n    <string name=\"text_file_sizes\">B|kB|MB|GB|TB</string>\n    <string name=\"standard\">標準</string>\n    <string name=\"webtoon\">Webtoon</string>\n    <string name=\"search_on_s\">在 %s 上搜尋</string>\n    <string name=\"delete_manga\">刪除漫畫</string>\n    <string name=\"switch_pages\">切換頁面</string>\n    <string name=\"_continue\">繼續</string>\n    <string name=\"error\">錯誤</string>\n    <string name=\"clear_thumbs_cache\">清除縮圖快取</string>\n    <string name=\"clear_search_history\">清除搜尋歷史紀錄</string>\n    <string name=\"internal_storage\">內部儲存區</string>\n    <string name=\"external_storage\">外部儲存區</string>\n    <string name=\"domain\">範圍</string>\n    <string name=\"open_in_browser\">在網頁瀏覽器中開啟</string>\n    <string name=\"favourites_categories\">最愛分類</string>\n    <string name=\"remove_category\">移除</string>\n    <string name=\"text_local_holder_primary\">首先儲存內容</string>\n    <string name=\"manga_save_location\">下載資料夾</string>\n    <string name=\"not_available\">不可用</string>\n    <string name=\"zoom_mode_fit_center\">適應中心</string>\n    <string name=\"zoom_mode_fit_height\">適應高度</string>\n    <string name=\"black_dark_theme\">黑色</string>\n    <string name=\"preparing_\">準備…</string>\n    <string name=\"file_not_found\">沒有找到檔案</string>\n    <string name=\"tap_to_try_again\">輕觸以重試</string>\n    <string name=\"captcha_solve\">解決</string>\n    <string name=\"clear_cookies\">清除 cookies</string>\n    <string name=\"chapter_is_missing\">此章節已遺失</string>\n    <string name=\"about_app_translation\">翻譯</string>\n    <string name=\"auth_complete\">授權</string>\n    <string name=\"system_default\">預設</string>\n    <string name=\"disabled\">禁用</string>\n    <string name=\"reset_filter\">重置篩選器</string>\n    <string name=\"never\">永不</string>\n    <string name=\"only_using_wifi\">僅在 Wi-Fi</string>\n    <string name=\"appearance\">外觀</string>\n    <string name=\"removal_completed\">移除已完成</string>\n    <string name=\"sync_title\">同步您的資料</string>\n    <string name=\"hide\">隱藏</string>\n    <string name=\"bookmark_add\">新增書籤</string>\n    <string name=\"bookmark_removed\">書籤已移除</string>\n    <string name=\"bookmark_added\">書籤已新增</string>\n    <string name=\"show_reading_indicators\">顯示閱讀進度指示</string>\n    <string name=\"last_2_hours\">最近 2 小時</string>\n    <string name=\"history_cleared\">歷史紀錄已清除</string>\n    <string name=\"no_bookmarks_yet\">還沒有書籤</string>\n    <string name=\"bookmarks_removed\">書籤已移除</string>\n    <string name=\"no_manga_sources\">沒有漫畫來源</string>\n    <string name=\"random\">隨機</string>\n    <string name=\"categories_delete_confirm\">您是否確定要刪除選取的最愛分類? \\n所有的漫畫將會遺失且無法復原。</string>\n    <string name=\"other_cache\">其他快取</string>\n    <string name=\"storage_usage\">儲存區使用</string>\n    <string name=\"importing_manga\">正在匯入漫畫</string>\n    <string name=\"import_completed\">匯入已完成</string>\n    <string name=\"import_will_start_soon\">即將開始匯入</string>\n    <string name=\"feed\">訂閱</string>\n    <string name=\"history_shortcuts\">顯示最近閱讀漫畫的捷徑</string>\n    <string name=\"contrast\">對比度</string>\n    <string name=\"reset\">重置</string>\n    <string name=\"discard\">放棄</string>\n    <string name=\"error_no_space_left\">裝置上沒有可用空間</string>\n    <string name=\"webtoon_zoom\">Webtoon 縮放</string>\n    <string name=\"network_unavailable\">網路不可用</string>\n    <string name=\"network_unavailable_hint\">開啟 Wi-Fi 或行動網路以閱讀線上漫畫</string>\n    <string name=\"server_error\">伺服器端錯誤 (%1$d)。請稍後重試</string>\n    <string name=\"theme_name_rikka\">Rikka</string>\n    <string name=\"theme_name_mamimi\">Mamimi</string>\n    <string name=\"theme_name_kanade\">Kanade</string>\n    <string name=\"sync_auth_hint\">您可登錄已有帳號或建立一個新帳號</string>\n    <string name=\"mirror_switching_summary\">如果鏡像可用，在漫畫來源發生錯誤時自動切換網域</string>\n    <string name=\"suggestions_notifications_summary\">偶爾顯示推薦漫畫通知</string>\n    <string name=\"more\">更多</string>\n    <string name=\"downloads_removed\">下載已被移除</string>\n    <string name=\"downloads_cancelled\">下載已被取消</string>\n    <string name=\"suggestions_enable_prompt\">您想要接收個人漫畫推薦嗎\\?</string>\n    <string name=\"web_view_unavailable\">WebView 不可用: 檢查是否已安裝 WebView</string>\n    <string name=\"type\">類型</string>\n    <string name=\"address\">位址</string>\n    <string name=\"data_and_privacy\">資料與隱私</string>\n    <string name=\"restore_summary\">還原先前建立的備份</string>\n    <string name=\"port\">連接埠</string>\n    <string name=\"download_option_all_unread\">所有未讀章節</string>\n    <string name=\"download_option_all_unread_b\">所有未讀章節 (%s)</string>\n    <string name=\"downloads\">下載</string>\n    <string name=\"save_page\">儲存頁面</string>\n    <string name=\"page_saved\">頁面已儲存</string>\n    <string name=\"read_mode\">閱讀模式</string>\n    <string name=\"grid_size\">網格大小</string>\n    <string name=\"reader_settings\">閱讀設定</string>\n    <string name=\"vibration\">震動</string>\n    <string name=\"pages_animation\">頁面動畫</string>\n    <string name=\"zoom_mode_fit_width\">適應寬度</string>\n    <string name=\"zoom_mode_keep_start\">從頭開始</string>\n    <string name=\"captcha_required\">需要驗證碼</string>\n    <string name=\"welcome\">歡迎</string>\n    <string name=\"backup_saved\">備份已儲存</string>\n    <string name=\"read_more\">閱讀更多</string>\n    <string name=\"queued\">排隊</string>\n    <string name=\"show_pages_numbers\">頁碼</string>\n    <string name=\"status_reading\">閱讀</string>\n    <string name=\"download_started\">下載已開始</string>\n    <string name=\"download_option_whole_manga\">整個漫畫</string>\n    <string name=\"download_option_manual_selection\">手動選擇章節</string>\n    <string name=\"language\">語言</string>\n    <string name=\"invert_colors\">反轉色彩</string>\n    <string name=\"images_procy_description\">如果可能的話，使用 wsrv.nl 服務以減少流量使用與提升圖片載入速度</string>\n    <string name=\"disable_battery_optimization\">禁用電池最佳化</string>\n    <string name=\"default_mode\">預設模式</string>\n    <string name=\"detect_reader_mode\">自動檢測閱讀器模式</string>\n    <string name=\"send\">發送</string>\n    <string name=\"status_planned\">計畫</string>\n    <string name=\"reorder\">重新排序</string>\n    <string name=\"empty\">空白</string>\n    <string name=\"explore\">瀏覽</string>\n    <string name=\"exit_confirmation\">退出確認</string>\n    <string name=\"saved_manga\">儲存的漫畫</string>\n    <string name=\"pages_cache\">頁面快取</string>\n    <string name=\"status_completed\">完成</string>\n    <string name=\"status_on_hold\">暫停</string>\n    <string name=\"status_re_reading\">重新閱讀</string>\n    <string name=\"status_dropped\">已放棄</string>\n    <string name=\"disable_all\">全部禁用</string>\n    <string name=\"report\">報告</string>\n    <string name=\"show_all\">選是全部</string>\n    <string name=\"invalid_domain_message\">無效的網域</string>\n    <string name=\"select_range\">選擇範圍</string>\n    <string name=\"clear_all_history\">清除所有歷史紀錄</string>\n    <string name=\"available\">可用</string>\n    <string name=\"options\">選項</string>\n    <string name=\"removed_from_favourites\">從最愛中移除</string>\n    <string name=\"not_found_404\">找不到內容或已刪除</string>\n    <string name=\"incognito_mode\">隱身模式</string>\n    <string name=\"no_chapters\">無章節</string>\n    <string name=\"automatic_scroll\">自動捲動</string>\n    <string name=\"reader_info_bar\">在閱讀器中顯示資訊列</string>\n    <string name=\"comics_archive\">漫畫壓縮檔</string>\n    <string name=\"folder_with_images\">圖片資料夾</string>\n    <string name=\"clear_network_cache\">清除網路快取</string>\n    <string name=\"pick_custom_directory\">選擇自訂目錄</string>\n    <string name=\"no_access_to_file\">您沒有權限存取此檔案或資料夾</string>\n    <string name=\"local_manga_directories\">本機漫畫目錄</string>\n    <string name=\"authorization_optional\">授權 (可選)</string>\n    <string name=\"theme_name_dynamic\">動態</string>\n    <string name=\"color_theme\">顏色方案</string>\n    <string name=\"theme_name_miku\">Miku</string>\n    <string name=\"theme_name_asuka\">Asuka</string>\n    <string name=\"theme_name_mion\">Mion</string>\n    <string name=\"pause\">暫停</string>\n    <string name=\"downloads_wifi_only\">只通過 Wi-Fi 下載</string>\n    <string name=\"downloads_wifi_only_summary\">切換到行動網路時停止下載</string>\n    <string name=\"by_name\">名稱</string>\n    <string name=\"popular\">熱門</string>\n    <string name=\"updated\">已更新</string>\n    <string name=\"read_later\">稍後閱讀</string>\n    <string name=\"updates\">更新</string>\n    <string name=\"genres\">類型</string>\n    <string name=\"state_finished\">已完結</string>\n    <string name=\"state_ongoing\">連載中</string>\n    <string name=\"local_manga_processing\">儲存的漫畫處裡</string>\n    <string name=\"canceled\">已取消</string>\n    <string name=\"account_already_exists\">帳號已存在</string>\n    <string name=\"dns_over_https\">DNS over HTTPS</string>\n    <string name=\"reader_slider\">顯示頁面切換滑塊</string>\n    <string name=\"enable_logging_summary\">紀錄一些動作用於除錯。假如您不確定您在做什麼時，不要將它開啟</string>\n    <string name=\"show_suspicious_content\">顯示可疑內容</string>\n    <string name=\"show_in_grid_view\">在網格檢視中顯示</string>\n    <string name=\"nothing_here\">這裡沒有任何東西</string>\n    <string name=\"allow_unstable_updates\">允許不穩定更新</string>\n    <string name=\"sync_settings\">同步設定</string>\n    <string name=\"no_thanks\">不，謝謝</string>\n    <string name=\"cancel_all_downloads_confirm\">所有進行中的下載將被取消， 未下載完成的資料將會遺失</string>\n    <string name=\"remove_completed_downloads_confirm\">您的下載紀錄將被永久地刪除</string>\n    <string name=\"text_downloads_list_holder\">您沒有任何下載</string>\n    <string name=\"downloads_resumed\">下載已被恢復</string>\n    <string name=\"downloads_paused\">下載已被暫停</string>\n    <string name=\"undo\">撤銷</string>\n    <string name=\"removed_from_history\">從歷史紀錄移除</string>\n    <string name=\"color_correction\">顏色校正</string>\n    <string name=\"brightness\">亮度</string>\n    <string name=\"reader_control_ltr\">人體工學閱讀器控制</string>\n    <string name=\"services\">服務</string>\n    <string name=\"enabled_d_of_d\" tools:ignore=\"PluralsCandidate\">%2$d 之 %1$d 啟用</string>\n    <string name=\"user_agent\">UserAgent 標頭</string>\n    <string name=\"sync_host_description\">您可以使用自建同步伺服器或預設伺服器。如果您不確定您在做什麼，請勿變更此項。</string>\n    <string name=\"reader_info_pattern\">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>\n    <string name=\"download_option_all_chapters\">所有 %s 翻譯的章節</string>\n    <string name=\"download_option_next_unread_n_chapters\">下一個未讀 %s</string>\n    <string name=\"download_option_first_n_chapters\">最初 %s</string>\n    <string name=\"memory_usage_pattern\">%s - %s</string>\n    <string name=\"allow_unstable_updates_summary\">接收不穩定建置的更新通知</string>\n    <string name=\"scrobbling_empty_hint\">要追蹤閱讀進度，在漫畫詳細畫面選擇選單 → 追蹤。</string>\n    <string name=\"manga_error_description_pattern\">錯誤詳情:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. 嘗試&lt;a href=%2$s&gt;在瀏覽器中開啟漫畫&lt;/a&gt; 以確保它在其來源中是可用的&lt;br&gt;2. 確認您使用的是 &lt;a href=kotatsu://about&gt;最新版的的 Kotatsu&lt;/a&gt;&lt;br&gt;3. 如果可用，請向開發人員發送錯誤報告。</string>\n    <string name=\"languages\">語言</string>\n    <string name=\"zoom_in\">放大</string>\n    <string name=\"captcha_required_summary\">%s 需要通過驗證碼才能正常工作</string>\n    <string name=\"progress\">進度</string>\n    <string name=\"error_corrupted_file\">無效的資料回傳或是檔案損毀</string>\n    <string name=\"related_manga_summary\">顯示相關漫畫的清單。在某些情況下它也許不準確或缺失</string>\n    <string name=\"reader_zoom_buttons_summary\">是否在右下角顯示縮放控制按鈕</string>\n    <string name=\"tracker_wifi_only_summary\">使用計量付費網路連線時不檢查新章節</string>\n    <string name=\"order_added\">加入日期</string>\n    <string name=\"on_device\">在裝置上</string>\n    <string name=\"suggest_new_sources\">在應用程式更新之後推薦新的來源</string>\n    <string name=\"moved_to_top\">移動至頂部</string>\n    <string name=\"data_not_restored_text\">確保您選取了正確的備份檔案</string>\n    <string name=\"unknown\">未知</string>\n    <string name=\"in_progress\">進行中</string>\n    <string name=\"items_limit_exceeded\">沒有更多的項目可被加入</string>\n    <string name=\"data_not_restored\">資料未恢復</string>\n    <string name=\"directories\">目錄</string>\n    <string name=\"manage_categories\">漫畫類別</string>\n    <string name=\"color_light\">淺色</string>\n    <string name=\"search_hint\">輸入漫畫標題、類型或名稱</string>\n    <string name=\"description\">描述</string>\n    <string name=\"reader_zoom_buttons\">顯示縮放按鈕</string>\n    <string name=\"main_screen_sections\">主畫面欄目</string>\n    <string name=\"advanced\">進階</string>\n    <string name=\"color_dark\">深色</string>\n    <string name=\"too_many_requests_message\">請求次數太多。請稍後再試</string>\n    <string name=\"related_manga\">相關的漫畫</string>\n    <string name=\"keep_screen_on\">維持螢幕開啟</string>\n    <string name=\"suggestions_wifi_only_summary\">使用計量付費網路連線時不更新推薦</string>\n    <string name=\"enhanced_colors\">32 位元色彩模式</string>\n    <string name=\"background\">背景</string>\n    <string name=\"zoom_out\">縮小</string>\n    <string name=\"keep_screen_on_summary\">當您正在閱讀漫畫時不要關閉螢幕</string>\n    <string name=\"voice_search\">語音搜尋</string>\n    <string name=\"manga_list\">漫畫清單</string>\n    <string name=\"disable_nsfw\">禁用 NSFW</string>\n    <string name=\"color_white\">白色</string>\n    <string name=\"to_top\">至頂</string>\n    <string name=\"show\">顯示</string>\n    <string name=\"suggest_new_sources_summary\">在應用程式更新之後提示啟用新加入的來源</string>\n    <string name=\"color_black\">黑色</string>\n    <string name=\"this_month\">這個月</string>\n    <string name=\"state_upcoming\">即將推出</string>\n    <string name=\"catalog\">目錄</string>\n    <string name=\"manage_sources\">漫畫來源</string>\n    <string name=\"content_type_comics\">漫畫</string>\n    <string name=\"content_type_manga\">日本漫畫</string>\n    <string name=\"content_type_other\">其它</string>\n    <string name=\"sources_catalog\">來源目錄</string>\n    <string name=\"source_enabled\">已啟用來源</string>\n    <string name=\"sync_auth\">登入以同步帳號</string>\n    <string name=\"skip\">略過</string>\n    <string name=\"rating_safe\">安全</string>\n    <string name=\"lock_screen_rotation\">鎖定畫面方向</string>\n    <string name=\"apply\">應用</string>\n    <string name=\"this_manga\">此漫畫</string>\n    <string name=\"globally\">全域</string>\n    <string name=\"manual\">手動</string>\n    <string name=\"available_d\">可ˋ用： %1$d</string>\n    <string name=\"state_paused\">已暫停</string>\n    <string name=\"reader_optimize\">減少記憶體消耗（beta）</string>\n    <string name=\"mark_as_completed\">標記為已完成</string>\n    <string name=\"by_relevance\">關聯</string>\n    <string name=\"list_options\">清單選項</string>\n    <string name=\"frequency_every_day\">每日</string>\n    <string name=\"frequency_once_per_month\">每月一次</string>\n    <string name=\"backup_date_\">備份日期： %s</string>\n    <string name=\"rating_adult\">成人</string>\n    <string name=\"categories\">分類</string>\n    <string name=\"frequency_every_2_days\">每 2 日</string>\n    <string name=\"frequency_once_per_week\">每周一次</string>\n    <string name=\"frequency_twice_per_month\">每月兩次</string>\n    <string name=\"periodic_backups_enable\">啟用週期性備份</string>\n    <string name=\"backups_output_directory\">備份輸出目錄</string>\n    <string name=\"backup_frequency\">備份頻率</string>\n    <string name=\"periodic_backups\">定期備份</string>\n    <string name=\"last_successful_backup\">上次成功備份： %s</string>\n    <string name=\"by_name_reverse\">名稱顛倒</string>\n    <string name=\"source_summary_pattern\">%1$s, %2$s</string>\n    <string name=\"no_manga_sources_found\">本次搜尋未發現可用的漫畫圖源</string>\n    <string name=\"welcome_text\">請選擇需要啟用的內容圖源。也可稍後在設定中進行配置</string>\n    <string name=\"volume_\">卷 %d</string>\n    <string name=\"volume_unknown\">未知卷</string>\n    <string name=\"downloads_settings_info\">如果您遇到伺服器端阻塞問題，可以在圖源設定中為每個漫畫圖源單獨啟用下載限速功能</string>\n    <string name=\"restore\">恢復</string>\n    <string name=\"incognito_mode_hint\">您的閱讀進度將不會被儲存</string>\n    <string name=\"content_rating\">內容評級</string>\n    <string name=\"genres_exclude\">排除流派</string>\n    <string name=\"rating_suggestive\">建議</string>\n    <string name=\"online_variant\">其他線上版本</string>\n    <string name=\"vertical\">垂直</string>\n    <string name=\"category_hidden_done\">主屏上看不到此分類，可通過選單 → 管理分類來訪問</string>\n    <string name=\"state_abandoned\">拖入</string>\n    <string name=\"disable_nsfw_summary\">禁用NSFW圖源並儘可能從列表中隱藏成人漫畫</string>\n    <string name=\"speed_value\">x%.1f</string>\n    <string name=\"last_read\">最後閱讀</string>\n    <string name=\"content_type_hentai\">紳士漫畫</string>\n    <string name=\"grayscale\">灰階</string>\n    <string name=\"default_tab\">預設標籤欄</string>\n    <string name=\"no_manga_sources_catalog_text\">此頁面中沒有可用的圖源，或者可能已經添加了所有圖源。\n\\n敬請期待</string>\n    <string name=\"color_correction_apply_text\">這些設定可以全局應用，也可以只應用於當前漫畫。如果全局應用，單獨的設定將不會被覆蓋。</string>\n    <string name=\"error_filter_states_genre_not_supported\">此圖源不支援同時按分類和狀態過濾</string>\n    <string name=\"error_filter_locale_genre_not_supported\">此圖源不支援同時按分類和區域過濾</string>\n    <string name=\"genres_search_hint\">開始輸入分類名稱</string>\n    <string name=\"reader_optimize_summary\">降低當前畫面外的頁面質量以減少記憶體佔用</string>\n    <string name=\"mark_as_completed_prompt\">是否將已選定的漫畫標記為閱畢？\n\\n\n\\n警告: 當前的閱讀進度將會丟失。</string>\n    <string name=\"disable_battery_optimization_summary_downloads\">這可能幫助您解決下載過程相關的問題</string>\n    <string name=\"state\">狀態</string>\n    <string name=\"error_multiple_genres_not_supported\">此漫畫圖源不支援按多個分類過濾</string>\n    <string name=\"error_multiple_states_not_supported\">此漫畫圖源不支援按多個狀態過濾</string>\n    <string name=\"error_search_not_supported\">此漫畫圖源不支援搜尋</string>\n    <string name=\"enhanced_colors_summary\">減少色帶，但可能會影響效能</string>\n    <string name=\"remaining_time_pattern\">%1$s %2$s</string>\n    <string name=\"remove_from_history\">從歷史記錄中移除</string>\n    <string name=\"email_password_enter_hint\">請輸入您的電子郵件和密碼以繼續</string>\n    <string name=\"config_reset_confirm\">重置設定為預設值？此動作無法撤銷。</string>\n    <string name=\"use_two_pages_landscape\">在橫向模式下使用雙頁佈局（測試版）</string>\n    <string name=\"default_webtoon_zoom_out\">預設網頁漫畫縮小</string>\n    <string name=\"fullscreen_mode\">全螢幕模式</string>\n    <string name=\"reader_fullscreen_summary\">隱藏系統狀態欄和導航欄</string>\n    <string name=\"show_menu\">顯示選單</string>\n    <string name=\"prev_chapter\">前一個章節</string>\n    <string name=\"prev_page\">前一頁</string>\n    <string name=\"next_page\">下一頁</string>\n    <string name=\"toggle_ui\">顯示/隱藏介面</string>\n    <string name=\"none\">無</string>\n    <string name=\"reader_actions\">閱讀器動作</string>\n    <string name=\"switch_pages_volume_buttons\">啟用音量鍵</string>\n    <string name=\"switch_pages_volume_buttons_summary\">使用音量鍵來翻頁</string>\n    <string name=\"long_tap_action\">長按動作</string>\n    <string name=\"suggestions_unavailable_text\">建議功能已禁用</string>\n    <string name=\"check_for_new_chapters_disabled\">檢查新章節功能已禁用</string>\n    <string name=\"reading_time_estimation\">顯示預估閱讀時間</string>\n    <string name=\"reading_time_estimation_summary\">預估時間值可能不準確</string>\n    <string name=\"location\">位置</string>\n    <string name=\"next_chapter\">下一個章節</string>\n    <string name=\"reader_actions_summary\">設定可點擊螢幕區域的動作</string>\n    <string name=\"tap_action\">點擊動作</string>\n    <string name=\"show_labels_in_navbar\">在導航欄顯示標籤</string>\n    <string name=\"pages_saving\">正在儲存頁面</string>\n    <string name=\"ask_for_dest_dir_every_time\">每次都詢問目的地目錄</string>\n    <string name=\"default_page_save_dir\">預設頁面儲存目錄</string>\n    <string name=\"multiple_cbz_files\">多個 CBZ 檔案</string>\n    <string name=\"preferred_download_format\">偏好的下載格式</string>\n    <string name=\"automatic\">自動</string>\n    <string name=\"single_cbz_file\">單一 CBZ 檔案</string>\n    <string name=\"unsupported_backup_message\">請選擇一個適當的 Kotatsu 備份檔案</string>\n    <string name=\"last_used\">最後使用</string>\n    <string name=\"delete_read_chapters_auto\">自動刪除已讀章節</string>\n    <string name=\"runs_on_app_start\">當應用程式啟動時運行</string>\n    <string name=\"minutes_short\">%d 分</string>\n    <string name=\"hours_minutes_short\">%1$d 時 %2$d 分</string>\n    <string name=\"hours_short\">%d 時</string>\n    <string name=\"chapters_grid_view\">網格檢視</string>\n    <string name=\"reading_stats\">閱讀統計</string>\n    <string name=\"other_manga\">其他漫畫</string>\n    <string name=\"clear_stats\">清除統計</string>\n    <string name=\"stats_cleared\">統計已清除</string>\n    <string name=\"clear_stats_confirm\">真的要清除所有閱讀統計嗎？此動作無法撤銷。</string>\n    <string name=\"week\">週</string>\n    <string name=\"month\">月</string>\n    <string name=\"day\">日</string>\n    <string name=\"all_time\">全部時間</string>\n    <string name=\"three_months\">三個月</string>\n    <string name=\"alternatives\">替代方案</string>\n    <string name=\"migrate\">遷移</string>\n    <string name=\"migrate_confirmation\">漫畫「%1$s」來自「%2$s」將被「%3$s」來自「%4$s」在您的歷史和收藏夾中替換（如果存在）</string>\n    <string name=\"manga_migration\">漫畫遷移</string>\n    <string name=\"migration_completed\">遷移完成</string>\n    <string name=\"delete_read_chapters\">刪除已讀章節</string>\n    <string name=\"no_chapters_deleted\">沒有章節被刪除</string>\n    <string name=\"chapters_deleted_pattern\">移除了 %1$s，清除了 %2$s</string>\n    <string name=\"delete_read_chapters_summary\">刪除本機儲存的已讀章節，以釋放空間</string>\n    <string name=\"delete_read_chapters_prompt\">這將永久刪除本機儲存中標記為已讀的所有章節。你可以稍後重新下載，但導入的章節可能會永遠丟失</string>\n    <string name=\"less_than_minute\">少於一分鐘</string>\n    <string name=\"statistics\">統計</string>\n    <string name=\"empty_stats_text\">選定期間內沒有統計</string>\n    <string name=\"pages_read_s\">頁面閱讀數：%s</string>\n    <string name=\"show_pages_thumbs\">顯示頁面縮略圖</string>\n    <string name=\"show_pages_thumbs_summary\">在詳細資訊上啟用「頁面」標籤</string>\n    <string name=\"split_by_translations\">按翻譯分割</string>\n    <string name=\"split_by_translations_summary\">分別顯示不同翻譯的章節，而不是在一個列表中</string>\n    <string name=\"order_oldest\">最舊</string>\n    <string name=\"long_ago_read\">很久以前讀過</string>\n    <string name=\"unread\">未讀</string>\n    <string name=\"fix\">固定</string>\n    <string name=\"missing_storage_permission\">沒有權限訪問外部儲存上的漫畫</string>\n    <string name=\"error_no_data_received\">未從服務器接收到資料</string>\n    <string name=\"enable_source\">啟用來源</string>\n    <string name=\"unsupported_source\">此漫畫來源不支援</string>\n    <string name=\"show_updated\">顯示更新</string>\n    <string name=\"webtoon_gaps\">網路漫畫模式中的間隙</string>\n    <string name=\"webtoon_gaps_summary\">在網路漫畫模式中顯示頁面之間的垂直間隙</string>\n    <string name=\"search_suggestions\">搜尋建議</string>\n    <string name=\"recent_queries\">近期查詢</string>\n    <string name=\"suggested_queries\">建議查詢</string>\n    <string name=\"authors\">作者</string>\n    <string name=\"blocked_by_server_message\">您已被伺服器封鎖。嘗試使用不同的網路連線（VPN、代理等）</string>\n    <string name=\"less_frequently\">較少頻率</string>\n    <string name=\"more_frequently\">較多頻率</string>\n    <string name=\"frequency_of_check\">檢查頻率</string>\n    <string name=\"new_chapters_pattern\">%1$s：%2$d</string>\n    <string name=\"pin_navigation_ui\">固定導航介面</string>\n    <string name=\"pin_navigation_ui_summary\">滾動時不隱藏導航欄和搜尋視圖</string>\n    <string name=\"scrobbler_auth_intro\">登入以設定與 %s 的整合。這將允許您追蹤您的漫畫閱讀進度與狀態</string>\n    <string name=\"unstable_feature_summary\">此功能為實驗性質，請確保您有備份以避免資料遺失</string>\n    <string name=\"ignore_ssl_errors_summary\">您可以停用 SSL 憑證驗證，以防在存取網路資源時遇到 SSL 相關問題。這可能會影響您的安全性。更改此設定後，需重新啟動應用程式。</string>\n    <string name=\"disable_connectivity_check_summary\">若您遇到連線檢查問題（例如，網路已連接但應用程式進入離線模式），可以略過連線檢查</string>\n    <string name=\"manga_fix_prompt\">此功能將為選定的漫畫尋找替代來源。此任務將需要一些時間，並會在背景中進行</string>\n    <string name=\"show_quick_filters_summary\">提供依照特定參數過濾漫畫列表的功能</string>\n    <string name=\"plugin_incompatible\">不相容的外掛或內部錯誤。請確保您使用的是最新版本的外掛與 Kotatsu</string>\n    <string name=\"filter_search_warning\">此來源不支援帶有過濾條件的搜尋。您的過濾條件已被清除</string>\n    <string name=\"text_empty_holder_secondary_filtered\">沒有符合您所選過濾條件的漫畫</string>\n    <string name=\"too_many_requests_message_retry\">請求過多。請在 %s 之後再試</string>\n    <string name=\"minutes_seconds_short\">%1$d 分 %2$d 秒</string>\n    <string name=\"disable_nsfw_notifications\">停用 NSFW 通知</string>\n    <string name=\"_new\">新</string>\n    <string name=\"manga_with_downloaded_chapters\">已下載章節的漫畫</string>\n    <string name=\"manga_replaced\">漫畫「%1$s」（%2$s）已被「%3$s」（%4$s）取代</string>\n    <string name=\"invalid_server_address_message\">無效的伺服器位址</string>\n    <string name=\"seconds_short\">%d 秒</string>\n    <string name=\"disable_connectivity_check\">停用連線檢查</string>\n    <string name=\"disable_nsfw_notifications_summary\">不顯示有關 NSFW 漫畫更新的通知</string>\n    <string name=\"tracker_debug_info\">檢查新章節日誌</string>\n    <string name=\"tracker_debug_info_summary\">有關背景檢查新章節的偵錯資訊</string>\n    <string name=\"sources_disabled\">來源已停用</string>\n    <string name=\"disable\">停用</string>\n    <string name=\"pages_saved\">圖片已儲存</string>\n    <string name=\"scrobbler_auth_required\">登入%s以繼續</string>\n    <string name=\"unstable_feature\">不穩定的功能</string>\n    <string name=\"downloads_background\">背景下載</string>\n    <string name=\"download_new_chapters\">下載新的漫畫章節</string>\n    <string name=\"enable_all_sources\">啟用所有漫畫來源</string>\n    <string name=\"all_sources_enabled\">所有漫畫來源已啟用</string>\n    <string name=\"auto_double_foldable\">摺疊設備自動雙頁</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/authenticator_sync.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<account-authenticator\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:accountPreferences=\"@xml/pref_sync_header\"\n\tandroid:accountType=\"@string/account_type_sync\"\n\tandroid:icon=\"@mipmap/ic_launcher_round\"\n\tandroid:label=\"@string/app_name\" />\n"
  },
  {
    "path": "app/src/main/res/xml/backup_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<full-backup-content>\n\t<include\n\t\tdomain=\"sharedpref\"\n\t\tpath=\".\" />\n\t<exclude\n\t\tdomain=\"sharedpref\"\n\t\tpath=\"_local_index.xml\" />\n\t<exclude domain=\"database\" />\n\t<exclude domain=\"device_database\" />\n\t<exclude domain=\"external\" />\n</full-backup-content>\n"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<data-extraction-rules>\n\t<cloud-backup disableIfNoEncryptionCapabilities=\"false\">\n\t\t<include\n\t\t\tdomain=\"sharedpref\"\n\t\t\tpath=\".\" />\n\t\t<exclude\n\t\t\tdomain=\"sharedpref\"\n\t\t\tpath=\"_local_index.xml\" />\n\t\t<exclude domain=\"database\" />\n\t\t<exclude domain=\"device_database\" />\n\t\t<exclude domain=\"external\" />\n\t</cloud-backup>\n\t<device-transfer>\n\t\t<include\n\t\t\tdomain=\"sharedpref\"\n\t\t\tpath=\".\" />\n\t\t<exclude\n\t\t\tdomain=\"sharedpref\"\n\t\t\tpath=\"_local_index.xml\" />\n\t\t<exclude domain=\"database\" />\n\t\t<exclude domain=\"device_database\" />\n\t</device-transfer>\n</data-extraction-rules>\n"
  },
  {
    "path": "app/src/main/res/xml/filepaths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n\t<!-- https://issuetracker.google.com/issues/37125252 -->\n\t<!--suppress AndroidElementNotAllowed -->\n\t<root-path\n\t\tname=\"root\"\n\t\tpath=\".\" />\n</paths>"
  },
  {
    "path": "app/src/main/res/xml/locales_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<locale-config\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<locale android:name=\"ar\" />\n\t<locale android:name=\"be\" />\n\t<locale android:name=\"bn\" />\n\t<locale android:name=\"ca\" />\n\t<locale android:name=\"cs\" />\n\t<locale android:name=\"de\" />\n\t<locale android:name=\"el\" />\n\t<locale android:name=\"en-GB\" />\n\t<locale android:name=\"es\" />\n\t<locale android:name=\"et\" />\n\t<locale android:name=\"fa\" />\n\t<locale android:name=\"fi\" />\n\t<locale android:name=\"fil\" />\n\t<locale android:name=\"fr\" />\n\t<locale android:name=\"gu\" />\n\t<locale android:name=\"hi\" />\n\t<locale android:name=\"hr\" />\n\t<locale android:name=\"hu\" />\n\t<locale android:name=\"in\" />\n\t<locale android:name=\"it\" />\n\t<locale android:name=\"ja\" />\n\t<locale android:name=\"kk\" />\n\t<locale android:name=\"km\" />\n\t<locale android:name=\"ko\" />\n\t<locale android:name=\"lt\" />\n\t<locale android:name=\"lv\" />\n\t<locale android:name=\"ml\" />\n\t<locale android:name=\"ms\" />\n\t<locale android:name=\"nb-NO\" />\n\t<locale android:name=\"ne\" />\n\t<locale android:name=\"nn\" />\n\t<locale android:name=\"or\" />\n\t<locale android:name=\"pa\" />\n\t<locale android:name=\"pa-PK\" />\n\t<locale android:name=\"pl\" />\n\t<locale android:name=\"pt\" />\n\t<locale android:name=\"pt-BR\" />\n\t<locale android:name=\"ro\" />\n\t<locale android:name=\"ru\" />\n\t<locale android:name=\"si\" />\n\t<locale android:name=\"sr\" />\n\t<locale android:name=\"sv\" />\n\t<locale android:name=\"ta\" />\n\t<locale android:name=\"th\" />\n\t<locale android:name=\"tr\" />\n\t<locale android:name=\"uk\" />\n\t<locale android:name=\"vi\" />\n\t<locale android:name=\"zh-CN\" />\n\t<locale android:name=\"zh-TW\" />\n</locale-config>\n"
  },
  {
    "path": "app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config\n\txmlns:tools=\"http://schemas.android.com/tools\">\n\t<!-- Need to allow cleartext traffic for some sources -->\n\t<base-config\n\t\tcleartextTrafficPermitted=\"true\"\n\t\ttools:ignore=\"InsecureBaseConfiguration\">\n\t\t<trust-anchors>\n\t\t\t<!-- Trust preinstalled CAs -->\n\t\t\t<certificates src=\"system\" />\n\n\t\t\t<!-- Additionally, trust user added CAs -->\n\t\t\t<certificates\n\t\t\t\tsrc=\"user\"\n\t\t\t\ttools:ignore=\"AcceptsUserCertificates\" />\n\t\t</trust-anchors>\n\t</base-config>\n</network-security-config>"
  },
  {
    "path": "app/src/main/res/xml/pref_about.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/about\">\n\n\t<Preference\n\t\tandroid:key=\"app_version\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/check_for_updates\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"updates_unstable\"\n\t\tandroid:summary=\"@string/allow_unstable_updates_summary\"\n\t\tandroid:title=\"@string/allow_unstable_updates\" />\n\n\t<Preference\n\t\tandroid:fragment=\"org.koitharu.kotatsu.settings.about.changelog.ChangelogFragment\"\n\t\tandroid:key=\"changelog\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/changelog_summary\"\n\t\tandroid:title=\"@string/changelog\" />\n\n\t<Preference\n\t\tandroid:key=\"about_help\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/url_user_manual\"\n\t\tandroid:title=\"@string/user_manual\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<Preference\n\t\tandroid:key=\"about_github\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/url_github\"\n\t\tandroid:title=\"@string/source_code\" />\n\n\t<Preference\n\t\tandroid:key=\"about_app_translation\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/url_weblate\"\n\t\tandroid:title=\"@string/about_app_translation_summary\" />\n\n\t<Preference\n\t\tandroid:key=\"about_telegram\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/url_telegram_web\"\n\t\tandroid:title=\"@string/telegram_group\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_appearance.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:title=\"@string/appearance\">\n\n    <org.koitharu.kotatsu.settings.utils.ThemeChooserPreference\n        android:key=\"color_theme\"\n        android:title=\"@string/color_theme\" />\n\n    <ListPreference\n        android:defaultValue=\"-1\"\n        android:entries=\"@array/themes\"\n        android:entryValues=\"@array/values_theme\"\n        android:key=\"theme\"\n        android:title=\"@string/theme\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"false\"\n        android:key=\"amoled_theme\"\n        android:summary=\"@string/black_dark_theme_summary\"\n        android:title=\"@string/black_dark_theme\" />\n\n    <org.koitharu.kotatsu.settings.utils.ActivityListPreference\n        android:key=\"app_locale\"\n        android:title=\"@string/language\" />\n\n    <PreferenceCategory android:title=\"@string/manga_list\">\n\n        <ListPreference\n            android:entries=\"@array/list_modes\"\n            android:key=\"list_mode_2\"\n            android:title=\"@string/list_mode\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <org.koitharu.kotatsu.settings.utils.SliderPreference\n            android:key=\"grid_size\"\n            android:stepSize=\"5\"\n            android:title=\"@string/grid_size\"\n            android:valueFrom=\"50\"\n            android:valueTo=\"150\"\n            app:defaultValue=\"100\" />\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"true\"\n            android:key=\"quick_filter\"\n            android:summary=\"@string/show_quick_filters_summary\"\n            android:title=\"@string/show_quick_filters\" />\n\n        <ListPreference\n            android:entries=\"@array/progress_indicators\"\n            android:key=\"progress_indicators\"\n            android:title=\"@string/show_reading_indicators\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <MultiSelectListPreference\n            android:defaultValue=\"@array/values_list_badges\"\n            android:entries=\"@array/list_badges\"\n            android:entryValues=\"@array/values_list_badges\"\n            android:key=\"manga_list_badges\"\n            android:title=\"@string/badges_in_lists\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory android:title=\"@string/details\">\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"true\"\n            android:key=\"description_collapse\"\n            android:title=\"@string/collapse_long_description\" />\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"true\"\n            android:key=\"pages_tab\"\n            android:summary=\"@string/show_pages_thumbs_summary\"\n            android:title=\"@string/show_pages_thumbs\" />\n\n        <ListPreference\n            android:defaultValue=\"-1\"\n            android:dependency=\"pages_tab\"\n            android:entries=\"@array/details_tabs\"\n            android:entryValues=\"@array/details_tabs_values\"\n            android:key=\"details_tab\"\n            android:title=\"@string/default_tab\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory android:title=\"@string/main_screen\">\n\n        <MultiSelectListPreference\n            android:key=\"search_suggest_types\"\n            android:title=\"@string/search_suggestions\" />\n\n        <PreferenceScreen\n            android:fragment=\"org.koitharu.kotatsu.settings.nav.NavConfigFragment\"\n            android:key=\"nav_main\"\n            android:title=\"@string/main_screen_sections\" />\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"true\"\n            android:key=\"main_fab\"\n            android:summary=\"@string/main_screen_fab_summary\"\n            android:title=\"@string/main_screen_fab\" />\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"true\"\n            android:key=\"nav_labels\"\n            android:title=\"@string/show_labels_in_navbar\" />\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"false\"\n            android:key=\"nav_pinned\"\n            android:summary=\"@string/pin_navigation_ui_summary\"\n            android:title=\"@string/pin_navigation_ui\" />\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"false\"\n            android:key=\"exit_confirm\"\n            android:summary=\"@string/exit_confirmation_summary\"\n            android:title=\"@string/exit_confirmation\" />\n\n        <SwitchPreferenceCompat\n            android:defaultValue=\"true\"\n            android:key=\"dynamic_shortcuts\"\n            android:summary=\"@string/history_shortcuts_summary\"\n            android:title=\"@string/history_shortcuts\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory android:title=\"@string/privacy\">\n\n        <SwitchPreferenceCompat\n            android:key=\"protect_app\"\n            android:persistent=\"false\"\n            android:summary=\"@string/protect_application_summary\"\n            android:title=\"@string/protect_application\" />\n\n        <ListPreference\n            android:defaultValue=\"allow\"\n            android:entries=\"@array/screenshots_policy\"\n            android:key=\"screenshots_policy\"\n            android:title=\"@string/screenshots_policy\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n    </PreferenceCategory>\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_backup_periodic.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:title=\"@string/periodic_backups\">\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"backup_periodic\"\n\t\tandroid:layout=\"@layout/preference_toggle_header\"\n\t\tandroid:title=\"@string/periodic_backups_enable\" />\n\n\t<Preference\n\t\tandroid:dependency=\"backup_periodic\"\n\t\tandroid:key=\"backup_periodic_output\"\n\t\tandroid:title=\"@string/backups_output_directory\" />\n\n\t<ListPreference\n\t\tandroid:defaultValue=\"7\"\n\t\tandroid:dependency=\"backup_periodic\"\n\t\tandroid:entries=\"@array/backup_frequency\"\n\t\tandroid:entryValues=\"@array/values_backup_frequency\"\n\t\tandroid:key=\"backup_periodic_freq\"\n\t\tandroid:title=\"@string/backup_frequency\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:dependency=\"backup_periodic\"\n\t\tandroid:key=\"backup_periodic_trim\"\n\t\tandroid:summary=\"@string/delete_old_backups_summary\"\n\t\tandroid:title=\"@string/delete_old_backups\" />\n\n\t<org.koitharu.kotatsu.settings.utils.SliderPreference\n\t\tandroid:dependency=\"backup_periodic_trim\"\n\t\tandroid:key=\"backup_periodic_count\"\n\t\tandroid:stepSize=\"1\"\n\t\tandroid:title=\"@string/max_backups_count\"\n\t\tandroid:valueFrom=\"1\"\n\t\tandroid:valueTo=\"32\"\n\t\tapp:defaultValue=\"10\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<Preference\n\t\tandroid:dependency=\"backup_periodic\"\n\t\tandroid:icon=\"@drawable/ic_info_outline\"\n\t\tandroid:key=\"backup_periodic_last\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:selectable=\"false\"\n\t\tapp:isPreferenceVisible=\"false\" />\n\n\t<PreferenceCategory\n\t\tandroid:key=\"backup_periodic_tg\"\n\t\tandroid:title=\"@string/telegram_integration\"\n\t\tapp:isPreferenceVisible=\"false\"\n\t\ttools:isPreferenceVisible=\"true\">\n\n\t\t<SwitchPreferenceCompat\n\t\t\tandroid:defaultValue=\"false\"\n\t\t\tandroid:dependency=\"backup_periodic\"\n\t\t\tandroid:key=\"backup_periodic_tg_enabled\"\n\t\t\tandroid:title=\"@string/send_backups_telegram\" />\n\n\t\t<EditTextPreference\n\t\t\tandroid:dependency=\"backup_periodic_tg_enabled\"\n\t\t\tandroid:inputType=\"text\"\n\t\t\tandroid:key=\"backup_periodic_tg_chat_id\"\n\t\t\tandroid:title=\"@string/telegram_chat_id\" />\n\n\t\t<Preference\n\t\t\tandroid:dependency=\"backup_periodic_tg_enabled\"\n\t\t\tandroid:key=\"backup_periodic_tg_open\"\n\t\t\tandroid:persistent=\"false\"\n\t\t\tandroid:summary=\"@string/open_telegram_bot_summary\"\n\t\t\tandroid:title=\"@string/open_telegram_bot\" />\n\n\t\t<Preference\n\t\t\tandroid:dependency=\"backup_periodic_tg_enabled\"\n\t\t\tandroid:key=\"backup_periodic_tg_test\"\n\t\t\tandroid:persistent=\"false\"\n\t\t\tandroid:title=\"@string/test_connection\" />\n\n\t</PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_backups.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:title=\"@string/backup_restore\">\n\n    <Preference\n        android:key=\"backup\"\n        android:persistent=\"false\"\n        android:summary=\"@string/backup_information\"\n        android:title=\"@string/create_backup\" />\n\n    <Preference\n        android:key=\"restore\"\n        android:persistent=\"false\"\n        android:summary=\"@string/restore_summary\"\n        android:title=\"@string/restore_backup\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment\"\n        android:key=\"backup_periodic\"\n        android:persistent=\"false\"\n        android:title=\"@string/periodic_backups\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_data_cleanup.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/data_removal\">\n\n\t<Preference\n\t\tandroid:key=\"search_history_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/loading_\"\n\t\tandroid:title=\"@string/clear_search_history\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<Preference\n\t\tandroid:key=\"updates_feed_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/loading_\"\n\t\tandroid:title=\"@string/clear_updates_feed\" />\n\n\t<Preference\n\t\tandroid:key=\"thumbs_cache_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/computing_\"\n\t\tandroid:title=\"@string/clear_thumbs_cache\" />\n\n\t<Preference\n\t\tandroid:key=\"pages_cache_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/computing_\"\n\t\tandroid:title=\"@string/clear_pages_cache\" />\n\n\t<Preference\n\t\tandroid:key=\"http_cache_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/loading_\"\n\t\tandroid:title=\"@string/clear_network_cache\" />\n\n\t<Preference\n\t\tandroid:key=\"manga_data_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/clear_database_summary\"\n\t\tandroid:title=\"@string/clear_database\" />\n\n\t<Preference\n\t\tandroid:key=\"cookies_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/clear_cookies_summary\"\n\t\tandroid:title=\"@string/clear_cookies\" />\n\n\t<Preference\n\t\tandroid:key=\"webview_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/clear_browser_data_summary\"\n\t\tandroid:title=\"@string/clear_browser_data\" />\n\n\t<Preference\n\t\tandroid:key=\"chapters_clear\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/delete_read_chapters_summary\"\n\t\tandroid:title=\"@string/delete_read_chapters\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"chapters_clear_auto\"\n\t\tandroid:summary=\"@string/runs_on_app_start\"\n\t\tandroid:title=\"@string/delete_read_chapters_auto\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_discord.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"discord_rpc\"\n\t\tandroid:layout=\"@layout/preference_toggle_header\"\n\t\tandroid:title=\"@string/discord_rpc\" />\n\n\t<EditTextPreference\n\t\tandroid:dependency=\"discord_rpc\"\n\t\tandroid:key=\"discord_token\"\n\t\tandroid:summary=\"@string/discord_token_summary\"\n\t\tandroid:title=\"@string/discord_token\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:dependency=\"discord_rpc\"\n\t\tandroid:key=\"discord_rpc_skip_nsfw\"\n\t\tandroid:summary=\"@string/rpc_skip_nsfw_summary\"\n\t\tandroid:title=\"@string/disable_nsfw\" />\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_downloads.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:title=\"@string/downloads\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<Preference\n\t\tandroid:key=\"local_manga_dirs\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/local_manga_directories\" />\n\n\t<Preference\n\t\tandroid:key=\"local_storage\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/manga_save_location\" />\n\n\t<ListPreference\n\t\tandroid:entries=\"@array/download_formats\"\n\t\tandroid:key=\"downloads_format\"\n\t\tandroid:title=\"@string/preferred_download_format\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<ListPreference\n\t\tandroid:entries=\"@array/metered_network_options\"\n\t\tandroid:key=\"downloads_metered_network\"\n\t\tandroid:title=\"@string/download_over_cellular\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<Preference\n\t\tandroid:icon=\"@drawable/ic_info_outline\"\n\t\tandroid:key=\"tracker_notifications_info\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:selectable=\"false\"\n\t\tandroid:summary=\"@string/downloads_settings_info\" />\n\n\t<Preference\n\t\tandroid:key=\"ignore_dose\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/disable_battery_optimization_summary_downloads\"\n\t\tandroid:title=\"@string/disable_battery_optimization\"\n\t\tapp:allowDividerAbove=\"true\"\n\t\tapp:isPreferenceVisible=\"false\" />\n\n\t<PreferenceCategory android:title=\"@string/pages_saving\">\n\n\t\t<Preference\n\t\t\tandroid:key=\"pages_dir\"\n\t\t\tandroid:persistent=\"false\"\n\t\t\tandroid:title=\"@string/default_page_save_dir\" />\n\n\t\t<SwitchPreferenceCompat\n\t\t\tandroid:defaultValue=\"true\"\n\t\t\tandroid:key=\"pages_dir_ask\"\n\t\t\tandroid:title=\"@string/ask_for_dest_dir_every_time\" />\n\n\t</PreferenceCategory>\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_network_storage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:title=\"@string/network\">\n\n    <PreferenceCategory android:title=\"@string/storage_usage\">\n\n        <org.koitharu.kotatsu.settings.userdata.storage.StorageUsagePreference android:key=\"storage_usage\" />\n\n        <Preference\n            android:fragment=\"org.koitharu.kotatsu.settings.userdata.storage.DataCleanupSettingsFragment\"\n            android:persistent=\"false\"\n            android:title=\"@string/data_removal\" />\n\n    </PreferenceCategory>\n\n    <ListPreference\n        android:defaultValue=\"0\"\n        android:entries=\"@array/network_policy\"\n        android:entryValues=\"@array/values_network_policy\"\n        android:key=\"prefetch_content\"\n        android:title=\"@string/prefetch_content\"\n        app:allowDividerAbove=\"true\"\n        app:useSimpleSummaryProvider=\"true\"\n        tools:isPreferenceVisible=\"true\" />\n\n    <ListPreference\n        android:defaultValue=\"2\"\n        android:entries=\"@array/network_policy\"\n        android:entryValues=\"@array/values_network_policy\"\n        android:key=\"pages_preload\"\n        android:title=\"@string/preload_pages\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.ProxySettingsFragment\"\n        android:key=\"proxy\"\n        android:title=\"@string/proxy\"\n        app:allowDividerAbove=\"true\" />\n\n    <ListPreference\n        android:entries=\"@array/doh_providers\"\n        android:key=\"doh\"\n        android:title=\"@string/dns_over_https\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <ListPreference\n        android:defaultValue=\"-1\"\n        android:entries=\"@array/image_proxies\"\n        android:entryValues=\"@array/values_image_proxies\"\n        android:key=\"images_proxy_2\"\n        android:title=\"@string/images_proxy_title\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <SwitchPreferenceCompat\n        android:key=\"ssl_bypass\"\n        android:summary=\"@string/ignore_ssl_errors_summary\"\n        android:title=\"@string/ignore_ssl_errors\" />\n\n    <SwitchPreferenceCompat\n        android:key=\"no_offline\"\n        android:summary=\"@string/disable_connectivity_check_summary\"\n        android:title=\"@string/disable_connectivity_check\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"false\"\n        android:key=\"adblock\"\n        android:summary=\"@string/adblock_summary\"\n        android:title=\"@string/adblock\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_notifications.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/notifications\">\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"tracker_notifications\"\n\t\tandroid:layout=\"@layout/preference_toggle_header\"\n\t\tandroid:title=\"@string/notifications_enable\" />\n\n\t<Preference\n\t\tandroid:dependency=\"tracker_notifications\"\n\t\tandroid:key=\"notifications_sound\"\n\t\tandroid:title=\"@string/notification_sound\" />\n\n\t<CheckBoxPreference\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:dependency=\"tracker_notifications\"\n\t\tandroid:key=\"notifications_vibrate\"\n\t\tandroid:title=\"@string/vibration\" />\n\n\t<CheckBoxPreference\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:dependency=\"tracker_notifications\"\n\t\tandroid:key=\"notifications_light\"\n\t\tandroid:title=\"@string/light_indicator\" />\n\n\t<org.koitharu.kotatsu.settings.utils.LinksPreference\n\t\tandroid:icon=\"@drawable/ic_info_outline\"\n\t\tandroid:key=\"tracker_notifications_info\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:selectable=\"false\"\n\t\tandroid:summary=\"@string/show_notification_new_chapters_off\"\n\t\tapp:allowDividerAbove=\"true\"\n\t\tapp:isPreferenceVisible=\"false\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_proxy.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/proxy\">\n\n\t<ListPreference\n\t\tandroid:defaultValue=\"DIRECT\"\n\t\tandroid:entries=\"@array/proxy_types\"\n\t\tandroid:entryValues=\"@array/values_proxy_types\"\n\t\tandroid:key=\"proxy_type_2\"\n\t\tandroid:title=\"@string/type\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<EditTextPreference\n\t\tandroid:key=\"proxy_address\"\n\t\tandroid:title=\"@string/address\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<EditTextPreference\n\t\tandroid:key=\"proxy_port\"\n\t\tandroid:title=\"@string/port\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<PreferenceCategory\n\t\tandroid:key=\"proxy_auth\"\n\t\tandroid:title=\"@string/authorization_optional\">\n\n\t\t<EditTextPreference\n\t\t\tandroid:key=\"proxy_login\"\n\t\t\tandroid:title=\"@string/username\"\n\t\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t\t<EditTextPreference\n\t\t\tandroid:key=\"proxy_password\"\n\t\t\tandroid:title=\"@string/password\" />\n\n\t</PreferenceCategory>\n\n\t<Preference\n\t\tandroid:key=\"proxy_test\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/test_connection\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_reader.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/reader_settings\">\n\n\t<ListPreference\n\t\tandroid:entries=\"@array/reader_modes\"\n\t\tandroid:key=\"reader_mode\"\n\t\tandroid:title=\"@string/default_mode\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"reader_mode_detect\"\n\t\tandroid:summary=\"@string/detect_reader_mode_summary\"\n\t\tandroid:title=\"@string/detect_reader_mode\" />\n\n\t<ListPreference\n\t\tandroid:entries=\"@array/zoom_modes\"\n\t\tandroid:key=\"zoom_mode\"\n\t\tandroid:title=\"@string/scale_mode\"\n\t\tapp:allowDividerAbove=\"true\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"reader_zoom_buttons\"\n\t\tandroid:summary=\"@string/reader_zoom_buttons_summary\"\n\t\tandroid:title=\"@string/reader_zoom_buttons\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"webtoon_zoom\"\n\t\tandroid:summary=\"@string/webtoon_zoom_summary\"\n\t\tandroid:title=\"@string/webtoon_zoom\" />\n\n\t<org.koitharu.kotatsu.settings.utils.SliderPreference\n\t\tandroid:dependency=\"webtoon_zoom\"\n\t\tandroid:key=\"webtoon_zoom_out\"\n\t\tandroid:stepSize=\"10\"\n\t\tandroid:title=\"@string/default_webtoon_zoom_out\"\n\t\tandroid:valueFrom=\"0\"\n\t\tandroid:valueTo=\"50\"\n\t\tapp:defaultValue=\"0\"\n\t\tapp:tickVisible=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"webtoon_gaps\"\n\t\tandroid:summary=\"@string/webtoon_gaps_summary\"\n\t\tandroid:title=\"@string/webtoon_gaps\" />\n\n\t<MultiSelectListPreference\n\t\tandroid:entries=\"@array/reader_controls\"\n\t\tandroid:key=\"reader_controls\"\n\t\tandroid:title=\"@string/reader_controls_in_bottom_bar\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<Preference\n\t\tandroid:key=\"reader_tap_actions\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/reader_actions_summary\"\n\t\tandroid:title=\"@string/reader_actions\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"reader_taps_ltr\"\n\t\tandroid:summary=\"@string/reader_control_ltr_summary\"\n\t\tandroid:title=\"@string/reader_control_ltr\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"reader_volume_buttons\"\n\t\tandroid:summary=\"@string/switch_pages_volume_buttons_summary\"\n\t\tandroid:title=\"@string/switch_pages_volume_buttons\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"reader_navigation_inverted\"\n\t\tandroid:summary=\"@string/reader_navigation_inverted_summary\"\n\t\tandroid:title=\"@string/reader_navigation_inverted\" />\n\n\t<ListPreference\n\t\tandroid:entries=\"@array/reader_animation\"\n\t\tandroid:key=\"reader_animation2\"\n\t\tandroid:title=\"@string/pages_animation\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"false\"\n        android:key=\"webtoon_pull_gesture\"\n        android:summary=\"@string/enable_pull_gesture_summary\"\n        android:title=\"@string/enable_pull_gesture_title\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"enhanced_colors\"\n\t\tandroid:summary=\"@string/enhanced_colors_summary\"\n\t\tandroid:title=\"@string/enhanced_colors\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"reader_optimize\"\n\t\tandroid:summary=\"@string/reader_optimize_summary\"\n\t\tandroid:title=\"@string/reader_optimize\" />\n\n\t<MultiSelectListPreference\n\t\tandroid:entries=\"@array/reader_crop\"\n\t\tandroid:entryValues=\"@array/values_reader_crop\"\n\t\tandroid:key=\"reader_crop\"\n\t\tandroid:title=\"@string/crop_pages\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"reader_fullscreen\"\n\t\tandroid:summary=\"@string/reader_fullscreen_summary\"\n\t\tandroid:title=\"@string/fullscreen_mode\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<ListPreference\n\t\tandroid:entries=\"@array/screen_orientations\"\n\t\tandroid:key=\"reader_orientation\"\n\t\tandroid:title=\"@string/screen_orientation\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"reader_screen_on\"\n\t\tandroid:summary=\"@string/keep_screen_on_summary\"\n\t\tandroid:title=\"@string/keep_screen_on\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"reader_multitask\"\n\t\tandroid:summary=\"@string/reader_multitask_summary\"\n\t\tandroid:title=\"@string/reader_multitask\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"reader_bar\"\n\t\tandroid:summary=\"@string/reader_info_bar_summary\"\n\t\tandroid:title=\"@string/reader_info_bar\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:dependency=\"reader_bar\"\n\t\tandroid:key=\"reader_bar_transparent\"\n\t\tandroid:title=\"@string/reader_info_bar_transparent\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"reader_chapter_toast\"\n\t\tandroid:summary=\"@string/reader_chapter_toast_summary\"\n\t\tandroid:title=\"@string/reader_chapter_toast\" />\n\n\t<ListPreference\n\t\tandroid:entries=\"@array/reader_backgrounds\"\n\t\tandroid:key=\"reader_background\"\n\t\tandroid:title=\"@string/background\"\n\t\tapp:allowDividerAbove=\"true\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"pages_numbers\"\n\t\tandroid:summary=\"@string/show_pages_numbers_summary\"\n\t\tandroid:title=\"@string/show_pages_numbers\" />\n\n\t<ListPreference\n\t\tandroid:defaultValue=\"2\"\n\t\tandroid:entries=\"@array/network_policy\"\n\t\tandroid:entryValues=\"@array/values_network_policy\"\n\t\tandroid:key=\"pages_preload\"\n\t\tandroid:title=\"@string/preload_pages\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_root.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.AppearanceSettingsFragment\"\n        android:icon=\"@drawable/ic_appearance\"\n        android:key=\"appearance\"\n        android:title=\"@string/appearance\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment\"\n        android:icon=\"@drawable/ic_manga_source\"\n        android:key=\"remote_sources\"\n        android:title=\"@string/remote_sources\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.ReaderSettingsFragment\"\n        android:icon=\"@drawable/ic_book_page\"\n        android:key=\"reader\"\n        android:title=\"@string/reader_settings\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.StorageAndNetworkSettingsFragment\"\n        android:icon=\"@drawable/ic_usage\"\n        android:key=\"network\"\n        android:title=\"@string/storage_and_network\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.DownloadsSettingsFragment\"\n        android:icon=\"@drawable/ic_download\"\n        android:key=\"downloads\"\n        android:title=\"@string/downloads\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment\"\n        android:icon=\"@drawable/ic_feed\"\n        android:key=\"tracker\"\n        android:title=\"@string/check_for_new_chapters\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.ServicesSettingsFragment\"\n        android:icon=\"@drawable/ic_services\"\n        android:key=\"services\"\n        android:title=\"@string/services\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.userdata.BackupsSettingsFragment\"\n        android:icon=\"@drawable/ic_backup_restore\"\n        android:key=\"userdata\"\n        android:title=\"@string/backup_restore\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.about.AboutSettingsFragment\"\n        android:icon=\"@drawable/ic_info_outline\"\n        android:key=\"about\"\n        android:title=\"@string/about\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_services.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/services\">\n\n\t<Preference\n\t\tandroid:enabled=\"@bool/is_sync_enabled\"\n\t\tandroid:key=\"sync\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/sync_title\"\n\t\tandroid:title=\"@string/sync\" />\n\n\t<PreferenceScreen\n\t\tandroid:enabled=\"false\"\n\t\tandroid:fragment=\"org.koitharu.kotatsu.settings.SyncSettingsFragment\"\n\t\tandroid:key=\"sync_settings\"\n\t\tandroid:title=\"@string/sync_settings\" />\n\n\t<PreferenceScreen\n\t\tandroid:fragment=\"org.koitharu.kotatsu.settings.SuggestionsSettingsFragment\"\n\t\tandroid:key=\"suggestions\"\n\t\tandroid:title=\"@string/suggestions\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"related_manga\"\n\t\tandroid:summary=\"@string/related_manga_summary\"\n\t\tandroid:title=\"@string/related_manga\" />\n\n\t<org.koitharu.kotatsu.settings.utils.SplitSwitchPreference\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"stats_on\"\n\t\tandroid:title=\"@string/reading_stats\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"reading_time\"\n\t\tandroid:summary=\"@string/reading_time_estimation_summary\"\n\t\tandroid:title=\"@string/reading_time_estimation\" />\n\n\t<PreferenceCategory android:title=\"@string/tracking\">\n\n\t\t<Preference\n\t\t\tandroid:key=\"anilist\"\n\t\t\tandroid:summary=\"@string/loading_\"\n\t\t\tandroid:title=\"@string/anilist\"\n\t\t\tapp:icon=\"@drawable/ic_anilist\" />\n\n\t\t<Preference\n\t\t\tandroid:key=\"kitsu\"\n\t\t\tandroid:summary=\"@string/loading_\"\n\t\t\tandroid:title=\"@string/kitsu\"\n\t\t\tapp:icon=\"@drawable/ic_kitsu\" />\n\n\t\t<Preference\n\t\t\tandroid:key=\"mal\"\n\t\t\tandroid:summary=\"@string/loading_\"\n\t\t\tandroid:title=\"@string/mal\"\n\t\t\tapp:icon=\"@drawable/ic_mal\" />\n\n\t\t<Preference\n\t\t\tandroid:key=\"shikimori\"\n\t\t\tandroid:summary=\"@string/loading_\"\n\t\t\tandroid:title=\"@string/shikimori\"\n\t\t\tapp:icon=\"@drawable/ic_shikimori\" />\n\n\t</PreferenceCategory>\n\n\t<Preference\n\t\tandroid:fragment=\"org.koitharu.kotatsu.settings.discord.DiscordSettingsFragment\"\n\t\tandroid:key=\"discord_rpc\"\n\t\tandroid:summary=\"@string/discord_rpc_summary\"\n\t\tandroid:title=\"@string/discord_rpc\"\n\t\tapp:allowDividerAbove=\"true\"\n\t\tapp:icon=\"@drawable/ic_discord\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\">\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"enable\"\n\t\tandroid:layout=\"@layout/preference_toggle_header\"\n\t\tandroid:order=\"0\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/enable_source\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"no_captcha\"\n\t\tandroid:order=\"104\"\n\t\tandroid:summary=\"@string/disable_captcha_notifications_summary\"\n\t\tandroid:title=\"@string/disable_captcha_notifications\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"slowdown\"\n\t\tandroid:order=\"105\"\n\t\tandroid:summary=\"@string/download_slowdown_summary\"\n\t\tandroid:title=\"@string/download_slowdown\" />\n\n\t<Preference\n\t\tandroid:icon=\"@drawable/ic_open_external\"\n\t\tandroid:key=\"open_browser\"\n\t\tandroid:order=\"500\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/open_in_browser\"\n\t\tapp:allowDividerAbove=\"true\"\n\t\tapp:isPreferenceVisible=\"false\"\n\t\ttools:isPreferenceVisible=\"true\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_source_parser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<Preference\n\t\tandroid:enabled=\"false\"\n\t\tandroid:key=\"auth\"\n\t\tandroid:order=\"100\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:title=\"@string/sign_in\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<Preference\n\t\tandroid:key=\"cookies_clear\"\n\t\tandroid:order=\"101\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:summary=\"@string/clear_source_cookies_summary\"\n\t\tandroid:title=\"@string/clear_cookies\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_sources.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:title=\"@string/remote_sources\">\n\n    <ListPreference\n        android:key=\"sources_sort_order\"\n        android:title=\"@string/sort_order\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"true\"\n        android:key=\"sources_grid\"\n        android:title=\"@string/show_in_grid_view\" />\n\n    <PreferenceScreen\n        android:fragment=\"org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment\"\n        android:key=\"remote_sources\"\n        android:persistent=\"false\"\n        android:title=\"@string/manage_sources\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"false\"\n        android:key=\"sources_enabled_all\"\n        android:summary=\"@string/enable_all_sources_summary\"\n        android:title=\"@string/enable_all_sources\"\n        app:allowDividerAbove=\"true\" />\n\n    <Preference\n        android:key=\"sources_catalog\"\n        android:persistent=\"false\"\n        android:title=\"@string/sources_catalog\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"false\"\n        android:key=\"no_nsfw\"\n        android:summary=\"@string/disable_nsfw_summary\"\n        android:title=\"@string/disable_nsfw\" />\n\n    <ListPreference\n        android:entries=\"@array/incognito_nsfw_options\"\n        android:key=\"incognito_nsfw\"\n        android:title=\"@string/incognito_for_nsfw\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"true\"\n        android:key=\"tags_warnings\"\n        android:summary=\"@string/tags_warnings_summary\"\n        android:title=\"@string/tags_warnings\" />\n\n    <SwitchPreferenceCompat\n        android:defaultValue=\"false\"\n        android:key=\"mirror_switching\"\n        android:summary=\"@string/mirror_switching_summary\"\n        android:title=\"@string/mirror_switching\"\n        app:allowDividerAbove=\"true\" />\n\n    <SwitchPreferenceCompat\n        android:key=\"handle_links\"\n        android:persistent=\"false\"\n        android:summary=\"@string/handle_links_summary\"\n        android:title=\"@string/handle_links\" />\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_suggestions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/suggestions\">\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"suggestions\"\n\t\tandroid:layout=\"@layout/preference_toggle_header\"\n\t\tandroid:title=\"@string/suggestions_enable\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:dependency=\"suggestions\"\n\t\tandroid:key=\"suggestions_wifi\"\n\t\tandroid:summary=\"@string/suggestions_wifi_only_summary\"\n\t\tandroid:title=\"@string/only_using_wifi\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:dependency=\"suggestions\"\n\t\tandroid:key=\"suggestions_disabled_sources\"\n\t\tandroid:summary=\"@string/suggestions_disabled_sources_summary\"\n\t\tandroid:title=\"@string/include_disabled_sources\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:dependency=\"suggestions\"\n\t\tandroid:key=\"suggestions_notifications\"\n\t\tandroid:summary=\"@string/suggestions_notifications_summary\"\n\t\tandroid:title=\"@string/notifications_enable\" />\n\n\t<org.koitharu.kotatsu.settings.utils.MultiAutoCompleteTextViewPreference\n\t\tandroid:dependency=\"suggestions\"\n\t\tandroid:key=\"suggestions_exclude_tags\"\n\t\tandroid:summary=\"@string/suggestions_excluded_genres_summary\"\n\t\tandroid:title=\"@string/suggestions_excluded_genres\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:dependency=\"suggestions\"\n\t\tandroid:key=\"suggestions_exclude_nsfw\"\n\t\tandroid:summary=\"@string/exclude_nsfw_from_suggestions_summary\"\n\t\tandroid:title=\"@string/exclude_nsfw_from_suggestions\" />\n\n\t<Preference\n\t\tandroid:icon=\"@drawable/ic_info_outline\"\n\t\tandroid:key=\"track_warning\"\n\t\tandroid:persistent=\"false\"\n\t\tandroid:selectable=\"false\"\n\t\tandroid:summary=\"@string/suggestions_info\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_sync.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<Preference\n\t\tandroid:key=\"host\"\n\t\tandroid:title=\"@string/server_address\" />\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_sync_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<Preference android:title=\"@string/sync_settings\">\n\n\t\t<intent\n\t\t\tandroid:action=\"android.intent.action.VIEW\"\n\t\t\tandroid:data=\"kotatsu://sync-settings\" />\n\t</Preference>\n\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_tracker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:title=\"@string/check_for_new_chapters\">\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"true\"\n\t\tandroid:key=\"tracker_enabled\"\n\t\tandroid:layout=\"@layout/preference_toggle_header\"\n\t\tandroid:title=\"@string/check_new_chapters_title\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:dependency=\"tracker_enabled\"\n\t\tandroid:key=\"tracker_wifi\"\n\t\tandroid:summary=\"@string/tracker_wifi_only_summary\"\n\t\tandroid:title=\"@string/only_using_wifi\" />\n\n\t<ListPreference\n\t\tandroid:defaultValue=\"1\"\n\t\tandroid:dependency=\"tracker_enabled\"\n\t\tandroid:entries=\"@array/tracker_frequency\"\n\t\tandroid:entryValues=\"@array/values_tracker_frequency\"\n\t\tandroid:key=\"tracker_freq\"\n\t\tandroid:title=\"@string/frequency_of_check\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<MultiSelectListPreference\n\t\tandroid:defaultValue=\"@array/values_track_sources_default\"\n\t\tandroid:dependency=\"tracker_enabled\"\n\t\tandroid:entries=\"@array/track_sources\"\n\t\tandroid:entryValues=\"@array/values_track_sources\"\n\t\tandroid:key=\"track_sources\"\n\t\tandroid:title=\"@string/track_sources\"\n\t\tapp:allowDividerAbove=\"true\" />\n\n\t<Preference\n\t\tandroid:key=\"track_categories\"\n\t\tandroid:title=\"@string/favourites_categories\" />\n\n\t<Preference\n\t\tandroid:dependency=\"tracker_enabled\"\n\t\tandroid:fragment=\"org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment\"\n\t\tandroid:key=\"notifications_settings\"\n\t\tandroid:title=\"@string/notifications_settings\" />\n\n\t<SwitchPreferenceCompat\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:dependency=\"tracker_enabled\"\n\t\tandroid:key=\"tracker_no_nsfw\"\n\t\tandroid:summary=\"@string/disable_nsfw_notifications_summary\"\n\t\tandroid:title=\"@string/disable_nsfw_notifications\" />\n\n\t<ListPreference\n\t\tandroid:dependency=\"tracker_enabled\"\n\t\tandroid:entries=\"@array/tracker_download_strategies\"\n\t\tandroid:key=\"tracker_download\"\n\t\tandroid:title=\"@string/download_new_chapters\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<PreferenceCategory android:title=\"@string/debug\">\n\n\t\t<Preference\n\t\t\tandroid:dependency=\"tracker_enabled\"\n\t\t\tandroid:key=\"tracker_debug\"\n\t\t\tandroid:persistent=\"false\"\n\t\t\tandroid:summary=\"@string/tracker_debug_info_summary\"\n\t\t\tandroid:title=\"@string/tracker_debug_info\" />\n\n\t\t<Preference\n\t\t\tandroid:dependency=\"tracker_enabled\"\n\t\t\tandroid:key=\"ignore_dose\"\n\t\t\tandroid:persistent=\"false\"\n\t\t\tandroid:summary=\"@string/disable_battery_optimization_summary\"\n\t\t\tandroid:title=\"@string/disable_battery_optimization\"\n\t\t\tapp:isPreferenceVisible=\"false\" />\n\n\t\t<org.koitharu.kotatsu.settings.utils.LinksPreference\n\t\t\tandroid:icon=\"@drawable/ic_info_outline\"\n\t\t\tandroid:key=\"track_warning\"\n\t\t\tandroid:persistent=\"false\"\n\t\t\tandroid:selectable=\"false\"\n\t\t\tandroid:summary=\"@string/tracker_warning\" />\n\n\t</PreferenceCategory>\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/remote_action.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<remote-actions version=\"1.2\">\n\t<action\n\t\tid=\"next_filter\"\n\t\tlabel=\"@string/prev_page\"\n\t\tpriority=\"1\"\n\t\ttrigger_key=\"L\">\n\t\t<preference name=\"gesture\" value=\"double_click\"/>\n\t</action>\n\n\t<action\n\t\tid=\"prev_filter\"\n\t\tlabel=\"@string/next_page\"\n\t\tpriority=\"2\"\n\t\ttrigger_key=\"R\">\n\t\t<preference name=\"gesture\" value=\"click\"/>\n\t</action>\n\n\t<action\n\t\tid=\"next_filter\"\n\t\tlabel=\"@string/prev_page\"\n\t\tpriority=\"3\"\n\t\ttrigger_key=\"L\">\n\t\t<preference name=\"gesture\" value=\"swipe_left\"/>\n\t</action>\n\n\t<action\n\t\tid=\"prev_filter\"\n\t\tlabel=\"@string/next_page\"\n\t\tpriority=\"4\"\n\t\ttrigger_key=\"R\">\n\t\t<preference name=\"gesture\" value=\"swipe_right\"/>\n\t</action>\n\n</remote-actions>\n"
  },
  {
    "path": "app/src/main/res/xml/sync_favourites.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<sync-adapter\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:accountType=\"@string/account_type_sync\"\n\tandroid:allowParallelSyncs=\"false\"\n\tandroid:contentAuthority=\"@string/sync_authority_favourites\"\n\tandroid:isAlwaysSyncable=\"true\"\n\tandroid:supportsUploading=\"true\"\n\tandroid:userVisible=\"@bool/is_sync_enabled\" />\n"
  },
  {
    "path": "app/src/main/res/xml/sync_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<sync-adapter\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:accountType=\"@string/account_type_sync\"\n\tandroid:allowParallelSyncs=\"false\"\n\tandroid:contentAuthority=\"@string/sync_authority_history\"\n\tandroid:isAlwaysSyncable=\"true\"\n\tandroid:supportsUploading=\"true\"\n\tandroid:userVisible=\"@bool/is_sync_enabled\" />\n"
  },
  {
    "path": "app/src/main/res/xml/widget_recent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:configure=\"org.koitharu.kotatsu.widget.recent.RecentWidgetConfigActivity\"\n\tandroid:description=\"@string/appwidget_recent_description\"\n\tandroid:initialLayout=\"@layout/widget_recent\"\n\tandroid:minWidth=\"120dp\"\n\tandroid:minHeight=\"40dp\"\n\tandroid:minResizeWidth=\"120dp\"\n\tandroid:minResizeHeight=\"40dp\"\n\tandroid:previewImage=\"@drawable/ic_appwidget_recent\"\n\tandroid:resizeMode=\"horizontal|vertical\"\n\tandroid:targetCellWidth=\"2\"\n\tandroid:targetCellHeight=\"2\"\n\tandroid:updatePeriodMillis=\"0\"\n\tandroid:widgetCategory=\"home_screen\"\n\ttools:ignore=\"UnusedAttribute\" />\n"
  },
  {
    "path": "app/src/main/res/xml/widget_shelf.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:configure=\"org.koitharu.kotatsu.widget.shelf.ShelfWidgetConfigActivity\"\n\tandroid:description=\"@string/appwidget_shelf_description\"\n\tandroid:initialLayout=\"@layout/widget_shelf\"\n\tandroid:minWidth=\"160dp\"\n\tandroid:minHeight=\"120dp\"\n\tandroid:minResizeWidth=\"120dp\"\n\tandroid:minResizeHeight=\"40dp\"\n\tandroid:previewImage=\"@drawable/ic_appwidget_shelf\"\n\tandroid:resizeMode=\"horizontal|vertical\"\n\tandroid:targetCellWidth=\"3\"\n\tandroid:targetCellHeight=\"2\"\n\tandroid:updatePeriodMillis=\"0\"\n\tandroid:widgetCategory=\"home_screen\"\n\tandroid:widgetFeatures=\"reconfigurable\"\n\ttools:ignore=\"UnusedAttribute\" />\n"
  },
  {
    "path": "app/src/nightly/kotlin/org/koitharu/kotatsu/KotatsuApp.kt",
    "content": "package org.koitharu.kotatsu\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport androidx.preference.PreferenceManager\nimport leakcanary.LeakCanary\nimport org.koitharu.kotatsu.core.BaseApp\n\nclass KotatsuApp : BaseApp(), SharedPreferences.OnSharedPreferenceChangeListener {\n\n\toverride fun attachBaseContext(base: Context) {\n\t\tsuper.attachBaseContext(base)\n\t\tval prefs = PreferenceManager.getDefaultSharedPreferences(this)\n\t\tconfigureLeakCanary(isEnabled = prefs.getBoolean(KEY_LEAK_CANARY, false))\n\t\tprefs.registerOnSharedPreferenceChangeListener(this)\n\t}\n\n\toverride fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {\n\t\tif (key == KEY_LEAK_CANARY) {\n\t\t\tconfigureLeakCanary(sharedPreferences.getBoolean(KEY_LEAK_CANARY, false))\n\t\t}\n\t}\n\n\tprivate fun configureLeakCanary(isEnabled: Boolean) {\n\t\tLeakCanary.config = LeakCanary.config.copy(\n\t\t\tdumpHeap = isEnabled,\n\t\t)\n\t}\n\n\tprivate companion object {\n\n\t\tconst val KEY_LEAK_CANARY = \"debug.leak_canary\"\n\t}\n}\n"
  },
  {
    "path": "app/src/nightly/kotlin/org/koitharu/kotatsu/core/network/CurlLoggingInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\n\nclass CurlLoggingInterceptor : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\treturn chain.proceed(chain.request()) // no-op implementation\n\t}\n}\n"
  },
  {
    "path": "app/src/nightly/kotlin/org/koitharu/kotatsu/core/parser/TestMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.model.TestMangaSource\nimport org.koitharu.kotatsu.parsers.MangaLoaderContext\n\n@Suppress(\"unused\")\nclass TestMangaRepository(\n\tprivate val loaderContext: MangaLoaderContext,\n\tcache: MemoryContentCache\n) : EmptyMangaRepository(TestMangaSource)\n"
  },
  {
    "path": "app/src/nightly/kotlin/org/koitharu/kotatsu/core/ui/BaseService.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.content.Context\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.LifecycleService\n\nabstract class BaseService : LifecycleService() {\n\n\toverride fun attachBaseContext(newBase: Context) {\n\t\tsuper.attachBaseContext(ContextCompat.getContextForLanguage(newBase))\n\t}\n}\n"
  },
  {
    "path": "app/src/nightly/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt",
    "content": "@file:Suppress(\"UnusedReceiverParameter\")\n\npackage org.koitharu.kotatsu.core.util.ext\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun Throwable.printStackTraceDebug() = Unit\n\nfun assertNotInMainThread() = Unit\n"
  },
  {
    "path": "app/src/nightly/kotlin/org/koitharu/kotatsu/settings/DebugSettingsFragment.kt",
    "content": "package org.koitharu.kotatsu.settings\n\nimport android.os.Bundle\nimport androidx.preference.Preference\nimport leakcanary.LeakCanary\nimport org.koitharu.kotatsu.R\nimport org.koitharu.kotatsu.core.ui.BasePreferenceFragment\nimport org.koitharu.kotatsu.settings.utils.SplitSwitchPreference\n\nclass DebugSettingsFragment : BasePreferenceFragment(R.string.debug), Preference.OnPreferenceClickListener {\n\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\taddPreferencesFromResource(R.xml.pref_debug)\n\t\tfindPreference<SplitSwitchPreference>(KEY_LEAK_CANARY)?.let { pref ->\n\t\t\tpref.onContainerClickListener = this\n\t\t}\n\t}\n\n\toverride fun onPreferenceClick(preference: Preference): Boolean = when (preference.key) {\n\t\tKEY_LEAK_CANARY -> {\n\t\t\tstartActivity(LeakCanary.newLeakDisplayActivityIntent())\n\t\t\ttrue\n\t\t}\n\n\t\telse -> super.onPreferenceTreeClick(preference)\n\t}\n\n\tprivate companion object {\n\n\t\tconst val KEY_LEAK_CANARY = \"debug.leak_canary\"\n\t}\n}\n"
  },
  {
    "path": "app/src/nightly/res/drawable/ic_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:width=\"24dp\"\n\tandroid:height=\"24dp\"\n\tandroid:tint=\"?attr/colorControlNormal\"\n\tandroid:viewportWidth=\"24\"\n\tandroid:viewportHeight=\"24\">\n\t<path\n\t\tandroid:fillColor=\"#000\"\n\t\tandroid:pathData=\"M20,8H17.19C16.74,7.2 16.12,6.5 15.37,6L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.05,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6C7.87,6.5 7.26,7.21 6.81,8H4V10H6.09C6.03,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.03,15.67 6.09,16H4V18H6.81C8.47,20.87 12.14,21.84 15,20.18C15.91,19.66 16.67,18.9 17.19,18H20V16H17.91C17.97,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.97,10.33 17.91,10H20V8M16,15A4,4 0 0,1 12,19A4,4 0 0,1 8,15V11A4,4 0 0,1 12,7A4,4 0 0,1 16,11V15M14,10V12H10V10H14M10,14H14V16H10V14Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/nightly/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n    <monochrome android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n    <monochrome android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/nightly/res/values/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\t<bool name=\"leak_canary_add_launcher_icon\" tools:node=\"replace\">false</bool>\n\t<bool name=\"leak_canary_allow_in_non_debuggable_build\">true</bool>\n</resources>\n"
  },
  {
    "path": "app/src/nightly/res/values/constants.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"account_type_sync\" translatable=\"false\">org.kotatsu.nightly.sync</string>\n\t<string name=\"sync_authority_history\" translatable=\"false\">org.koitharu.kotatsu.nightly.history</string>\n\t<string name=\"sync_authority_favourites\" translatable=\"false\">org.koitharu.kotatsu.nightly.favourites</string>\n\t<string name=\"github_updates_repo\" translatable=\"false\">KotatsuApp/Kotatsu-Nightly</string>\n</resources>\n"
  },
  {
    "path": "app/src/nightly/res/values/strings.xml",
    "content": "<resources>\n\t<string name=\"app_name\" translatable=\"false\">Kotatsu Nightly</string>\n\t<string name=\"may_affect_performance\">May affect performance</string>\n</resources>\n"
  },
  {
    "path": "app/src/nightly/res/xml/pref_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\t<org.koitharu.kotatsu.settings.utils.SplitSwitchPreference\n\t\tandroid:id=\"@+id/action_leakcanary\"\n\t\tandroid:defaultValue=\"false\"\n\t\tandroid:key=\"leak_canary\"\n\t\tandroid:title=\"LeakCanary\"\n\t\tandroid:summary=\"@string/may_affect_performance\"\n\t\tapp:key=\"debug.leak_canary\" />\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/nightly/res/xml/pref_root_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen\n\txmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\t<PreferenceScreen\n\t\tandroid:fragment=\"org.koitharu.kotatsu.settings.DebugSettingsFragment\"\n\t\tandroid:icon=\"@drawable/ic_debug\"\n\t\tandroid:key=\"debug\"\n\t\tandroid:title=\"@string/debug\" />\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/release/kotlin/org/koitharu/kotatsu/KotatsuApp.kt",
    "content": "package org.koitharu.kotatsu\n\nimport org.koitharu.kotatsu.core.BaseApp\n\nclass KotatsuApp : BaseApp()\n"
  },
  {
    "path": "app/src/release/kotlin/org/koitharu/kotatsu/core/network/CurlLoggingInterceptor.kt",
    "content": "package org.koitharu.kotatsu.core.network\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\n\nclass CurlLoggingInterceptor : Interceptor {\n\n\toverride fun intercept(chain: Interceptor.Chain): Response {\n\t\treturn chain.proceed(chain.request()) // no-op implementation\n\t}\n}\n"
  },
  {
    "path": "app/src/release/kotlin/org/koitharu/kotatsu/core/parser/TestMangaRepository.kt",
    "content": "package org.koitharu.kotatsu.core.parser\n\nimport org.koitharu.kotatsu.core.cache.MemoryContentCache\nimport org.koitharu.kotatsu.core.model.TestMangaSource\nimport org.koitharu.kotatsu.parsers.MangaLoaderContext\n\n@Suppress(\"unused\")\nclass TestMangaRepository(\n\tprivate val loaderContext: MangaLoaderContext,\n\tcache: MemoryContentCache\n) : EmptyMangaRepository(TestMangaSource)\n"
  },
  {
    "path": "app/src/release/kotlin/org/koitharu/kotatsu/core/ui/BaseService.kt",
    "content": "package org.koitharu.kotatsu.core.ui\n\nimport android.content.Context\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.LifecycleService\n\nabstract class BaseService : LifecycleService() {\n\n\toverride fun attachBaseContext(newBase: Context) {\n\t\tsuper.attachBaseContext(ContextCompat.getContextForLanguage(newBase))\n\t}\n}\n"
  },
  {
    "path": "app/src/release/kotlin/org/koitharu/kotatsu/core/util/ext/Debug.kt",
    "content": "@file:Suppress(\"UnusedReceiverParameter\")\n\npackage org.koitharu.kotatsu.core.util.ext\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun Throwable.printStackTraceDebug() = Unit\n\nfun assertNotInMainThread() = Unit\n"
  },
  {
    "path": "app/src/release/res/values/bools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<!-- Disable predictive back due to crashes -->\n\t<bool name=\"is_predictive_back_enabled\">false</bool>\n</resources>\n"
  },
  {
    "path": "app/src/release/res/xml/pref_root_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen />\n"
  },
  {
    "path": "app/src/test/kotlin/org/koitharu/kotatsu/core/github/VersionIdTest.kt",
    "content": "package org.koitharu.kotatsu.core.github\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertTrue\nimport org.junit.Test\nimport org.koitharu.kotatsu.BuildConfig\n\nclass VersionIdTest {\n\n\t@Test\n\tfun testVersionIdParse() {\n\t\tval version = VersionId(\"2.0\")\n\t\tassertEquals(version.major, 2)\n\t\tassertEquals(version.minor, 0)\n\t\tassertEquals(version.build, 0)\n\t\tassertEquals(version.variantType, \"\")\n\t\tassertEquals(version.variantNumber, 0)\n\t}\n\n\t@Test\n\tfun testVersionIdVariantParse() {\n\t\tval version = VersionId(\"2.0.1-b1\")\n\t\tassertEquals(version.major, 2)\n\t\tassertEquals(version.minor, 0)\n\t\tassertEquals(version.build, 1)\n\t\tassertEquals(version.variantType, \"b\")\n\t\tassertEquals(version.variantNumber, 1)\n\t\tval version2 = VersionId(\"2.0.1-beta1\")\n\t\tassertEquals(compareValues(version, version2), 0)\n\t}\n\n\t@Test\n\tfun testVersionIdCompare() {\n\t\tval version1 = VersionId(\"1.99.99\")\n\t\tval version2 = VersionId(\"2.0.0\")\n\t\tassertTrue(version1 < version2)\n\t}\n\t\n\t@Test\n\tfun testVersionIdVariantCompare() {\n\t\tval version1 = VersionId(\"2.0.1-a2\")\n\t\tval version2 = VersionId(\"2.0.1-b1\")\n\t\tassertTrue(version1 < version2)\n\t}\n\n\t@Test\n\tfun testCurrentVersion() {\n\t\tval version1 = VersionId(\"2.4.6\")\n\t\tval version2 = VersionId(BuildConfig.VERSION_NAME)\n\t\tassertTrue(version1 < version2)\n\t}\n}"
  },
  {
    "path": "app/src/test/kotlin/org/koitharu/kotatsu/reader/domain/ChapterPagesTest.kt",
    "content": "package org.koitharu.kotatsu.reader.domain\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertTrue\nimport org.junit.Test\nimport org.koitharu.kotatsu.core.model.TestMangaSource\nimport org.koitharu.kotatsu.reader.ui.pager.ReaderPage\nimport kotlin.random.Random\n\nclass ChapterPagesTest {\n\n\t@Test\n\tfun getChaptersSize() {\n\t\tval pages = ChapterPages()\n\t\tpages.addFirst(1L, List(12) { page(1L) })\n\t\tpages.addFirst(2L, List(17) { page(2L) })\n\t\tassertEquals(2, pages.chaptersSize)\n\t}\n\n\t@Test\n\tfun removeFirst() {\n\t\tval pages = ChapterPages()\n\t\tpages.addLast(1L, List(12) { page(1L) })\n\t\tpages.addLast(2L, List(17) { page(2L) })\n\t\tpages.addLast(4L, List(2) { page(4L) })\n\t\tpages.removeFirst()\n\t\tassertEquals(2, pages.chaptersSize)\n\t\tassertEquals(17 + 2, pages.size)\n\t}\n\n\t@Test\n\tfun removeLast() {\n\t\tval pages = ChapterPages()\n\t\tpages.addLast(1L, List(12) { page(1L) })\n\t\tpages.addLast(2L, List(17) { page(2L) })\n\t\tpages.addLast(4L, List(2) { page(4L) })\n\t\tpages.removeLast()\n\t\tassertEquals(2, pages.chaptersSize)\n\t\tassertEquals(12 + 17, pages.size)\n\t}\n\n\t@Test\n\tfun clear() {\n\t\tval pages = ChapterPages()\n\t\tpages.addLast(1L, List(12) { page(1L) })\n\t\tpages.addLast(2L, List(17) { page(2L) })\n\t\tpages.addLast(4L, List(2) { page(4L) })\n\t\tpages.clear()\n\t\tassertEquals(0, pages.chaptersSize)\n\t\tassertEquals(0, pages.size)\n\t\tassertEquals(0, pages.size(1L))\n\t\tassertEquals(0, pages.size(2L))\n\t\tassertEquals(0, pages.size(4L))\n\t}\n\n\t@Test\n\tfun subList() {\n\t\tval pages = ChapterPages()\n\t\tpages.addLast(1L, List(12) { page(1L) })\n\t\tpages.addLast(2L, List(17) { page(2L) })\n\t\tpages.addFirst(4L, List(2) { page(4L) })\n\t\tval subList = pages.subList(2L)\n\t\tassertEquals(17, subList.size)\n\t\tassertEquals(2L, subList.first().chapterId)\n\t\tassertEquals(2L, subList.last().chapterId)\n\t\tassertTrue(subList.all { it.chapterId == 2L })\n\t\tassertEquals(subList.size, pages.size(2L))\n\t}\n\n\tprivate fun page(chapterId: Long) = ReaderPage(\n\t\tid = Random.nextLong(),\n\t\turl = \"http://localhost\",\n\t\tpreview = null,\n\t\tchapterId = chapterId,\n\t\tindex = Random.nextInt(),\n\t\tsource = TestMangaSource,\n\t)\n}\n"
  },
  {
    "path": "app/src/test/kotlin/org/koitharu/kotatsu/util/MultiMutexTest.kt",
    "content": "package org.koitharu.kotatsu.util\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.cancelAndJoin\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.runTest\nimport kotlinx.coroutines.withTimeout\nimport kotlinx.coroutines.withTimeoutOrNull\nimport kotlinx.coroutines.yield\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertNull\nimport org.junit.Ignore\nimport org.junit.Test\nimport org.koitharu.kotatsu.core.util.MultiMutex\n\nclass MultiMutexTest {\n\n\t@Test\n\tfun singleLock() = runTest {\n\t\tval mutex = MultiMutex<Int>()\n\t\tmutex.lock(1)\n\t\tmutex.lock(2)\n\t\tmutex.unlock(1)\n\t\tassert(mutex.size == 1)\n\t\tmutex.unlock(2)\n\t\tassert(mutex.isEmpty())\n\t}\n\n\t@Test\n\t@Ignore(\"Cannot delay in test\")\n\tfun doubleLock() = runTest {\n\t\tval mutex = MultiMutex<Int>()\n\t\trepeat(2) {\n\t\t\tlaunch(Dispatchers.Default) {\n\t\t\t\tmutex.lock(1)\n\t\t\t}\n\t\t}\n\t\tyield()\n\t\tassertEquals(1, mutex.size)\n\t\tmutex.unlock(1)\n\t\tval tryLock = withTimeoutOrNull(1000) {\n\t\t\tmutex.lock(1)\n\t\t}\n\t\tassertNull(tryLock)\n\t}\n\n\t@Test\n\tfun cancellation() = runTest {\n\t\tval mutex = MultiMutex<Int>()\n\t\tmutex.lock(1)\n\t\tval job = launch {\n\t\t\ttry {\n\t\t\t\tmutex.lock(1)\n\t\t\t} finally {\n\t\t\t\tmutex.unlock(1)\n\t\t\t}\n\t\t}\n\t\twithTimeout(2000) {\n\t\t\tjob.cancelAndJoin()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "plugins {\n\talias(libs.plugins.android.application) apply false\n\talias(libs.plugins.kotlin) apply false\n\talias(libs.plugins.hilt) apply false\n\talias(libs.plugins.ksp) apply false\n\talias(libs.plugins.room) apply false\n\talias(libs.plugins.kotlinx.serizliation) apply false\n//\talias(libs.plugins.decoroutinator) apply false\n}\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nacra = \"5.13.1\"\nactivity = \"1.11.0\"\nadapterdelegates = \"4.3.2\"\nappcompat = \"1.7.1\"\navifDecoder = \"1.1.1.14d8e3c4\"\nbiometric = \"1.4.0-alpha04\"\ncoil = \"3.3.0\"\ncollections = \"1.5.0\"\n# https://github.com/google/conscrypt/issues/1268\n#noinspection NewerVersionAvailable,GradleDependency - 2.5.3 cause crashes\nconscrypt = \"2.5.2\"\nconstraintlayout = \"2.2.1\"\ncoreKtx = \"1.17.0\"\ncoroutines = \"1.10.2\"\ndagger = \"2.57.2\"\ndecoroutinator = \"2.5.7\"\ndesugar = \"2.1.5\"\ndiskLruCache = \"1.5\"\ndocumentfile = \"1.1.0\"\nfragment = \"1.8.9\"\ngradle = \"8.13.0\"\nguava = \"33.4.8-android\"\nhilt = \"1.3.0\"\njson = \"20250517\"\njunit = \"4.13.2\"\njunitKtx = \"1.3.0\"\nkotlin = \"2.2.10\"\nkizzyRpc = \"ad8f2e32eb\"\nksp = \"2.2.10-2.0.2\"\nleakcanary = \"3.0-alpha-8\"\nlifecycle = \"2.9.4\"\nmarkwon = \"4.6.2\"\nmaterial = \"1.14.0-alpha05\"\nmoshi = \"1.15.2\"\nokhttp = \"5.2.1\"\nokio = \"3.16.1\"\nparsers = \"4d1e521aef\"\npreference = \"1.2.1\"\nrecyclerview = \"1.4.0\"\nroom = \"2.7.2\"\nserialization = \"1.9.0\"\nssiv = \"376930523c\"\nswiperefreshlayout = \"1.1.0\"\ntestRules = \"1.7.0\"\ntestRunner = \"1.7.0\"\ntransition = \"1.6.0\"\nviewpager2 = \"1.1.0\"\nwebkit = \"1.14.0\"\nworkRuntime = \"2.10.5\"\nworkinspector = \"1.2\"\nwindow = \"1.3.0\"\n\n[libraries]\nacra-dialog = { module = \"ch.acra:acra-dialog\", version.ref = \"acra\" }\nacra-http = { module = \"ch.acra:acra-http\", version.ref = \"acra\" }\nadapterdelegates = { module = \"com.hannesdorfmann:adapterdelegates4-kotlin-dsl\", version.ref = \"adapterdelegates\" }\nadapterdelegates-viewbinding = { module = \"com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding\", version.ref = \"adapterdelegates\" }\nandroidx-activity = { module = \"androidx.activity:activity-ktx\", version.ref = \"activity\" }\nandroidx-appcompat = { module = \"androidx.appcompat:appcompat\", version.ref = \"appcompat\" }\nandroidx-biometric = { module = \"androidx.biometric:biometric\", version.ref = \"biometric\" }\nandroidx-collection = { module = \"androidx.collection:collection-ktx\", version.ref = \"collections\" }\nandroidx-constraintlayout = { module = \"androidx.constraintlayout:constraintlayout\", version.ref = \"constraintlayout\" }\nandroidx-core = { module = \"androidx.core:core-ktx\", version.ref = \"coreKtx\" }\nandroidx-documentfile = { module = \"androidx.documentfile:documentfile\", version.ref = \"documentfile\" }\nandroidx-fragment = { module = \"androidx.fragment:fragment-ktx\", version.ref = \"fragment\" }\nandroidx-hilt-compiler = { module = \"androidx.hilt:hilt-compiler\", version.ref = \"hilt\" }\nandroidx-hilt-work = { module = \"androidx.hilt:hilt-work\", version.ref = \"hilt\" }\nandroidx-junit = { module = \"androidx.test.ext:junit-ktx\", version.ref = \"junitKtx\" }\nandroidx-lifecycle-common-java8 = { module = \"androidx.lifecycle:lifecycle-common-java8\", version.ref = \"lifecycle\" }\nandroidx-preference = { module = \"androidx.preference:preference-ktx\", version.ref = \"preference\" }\nandroidx-recyclerview = { module = \"androidx.recyclerview:recyclerview\", version.ref = \"recyclerview\" }\nandroidx-room-compiler = { module = \"androidx.room:room-compiler\", version.ref = \"room\" }\nandroidx-room-ktx = { module = \"androidx.room:room-ktx\", version.ref = \"room\" }\nandroidx-room-runtime = { module = \"androidx.room:room-runtime\", version.ref = \"room\" }\nandroidx-room-testing = { module = \"androidx.room:room-testing\", version.ref = \"room\" }\nandroidx-rules = { module = \"androidx.test:rules\", version.ref = \"testRules\" }\nandroidx-runner = { module = \"androidx.test:runner\", version.ref = \"testRunner\" }\nandroidx-swiperefreshlayout = { module = \"androidx.swiperefreshlayout:swiperefreshlayout\", version.ref = \"swiperefreshlayout\" }\nandroidx-test-core = { module = \"androidx.test:core-ktx\", version.ref = \"testRules\" }\nandroidx-transition = { module = \"androidx.transition:transition-ktx\", version.ref = \"transition\" }\nandroidx-viewpager2 = { module = \"androidx.viewpager2:viewpager2\", version.ref = \"viewpager2\" }\nandroidx-webkit = { module = \"androidx.webkit:webkit\", version.ref = \"webkit\" }\nandroidx-work-runtime = { module = \"androidx.work:work-runtime\", version.ref = \"workRuntime\" }\navif-decoder = { module = \"org.aomedia.avif.android:avif\", version.ref = \"avifDecoder\" }\ncoil-core = { module = \"io.coil-kt.coil3:coil-core\", version.ref = \"coil\" }\ncoil-gif = { module = \"io.coil-kt.coil3:coil-gif\", version.ref = \"coil\" }\ncoil-network = { module = \"io.coil-kt.coil3:coil-network-okhttp\", version.ref = \"coil\" }\ncoil-svg = { module = \"io.coil-kt.coil3:coil-svg\", version.ref = \"coil\" }\nconscrypt-android = { module = \"org.conscrypt:conscrypt-android\", version.ref = \"conscrypt\" }\ndesugar_jdk_libs = { module = \"com.android.tools:desugar_jdk_libs\", version.ref = \"desugar\" }\ndisk-lru-cache = { module = \"com.github.solkin:disk-lru-cache\", version.ref = \"diskLruCache\" }\nguava = { module = \"com.google.guava:guava\", version.ref = \"guava\" }\nhilt-android = { module = \"com.google.dagger:hilt-android\", version.ref = \"dagger\" }\nhilt-android-compiler = { module = \"com.google.dagger:hilt-android-compiler\", version.ref = \"dagger\" }\nhilt-android-testing = { module = \"com.google.dagger:hilt-android-testing\", version.ref = \"dagger\" }\nhilt-compiler = { module = \"com.google.dagger:hilt-compiler\", version.ref = \"dagger\" }\njson = { module = \"org.json:json\", version.ref = \"json\" }\njunit = { module = \"junit:junit\", version.ref = \"junit\" }\nkizzyrpc = { module = \"com.github.dead8309:KizzyRPC\", version.ref = \"kizzyRpc\" }\nkotlin-stdlib = { module = \"org.jetbrains.kotlin:kotlin-stdlib\", version.ref = \"kotlin\" }\nkotlinx-coroutines-android = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-android\", version.ref = \"coroutines\" }\nkotlinx-coroutines-guava = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-guava\", version.ref = \"coroutines\" }\nkotlinx-coroutines-test = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-test\", version.ref = \"coroutines\" }\nkotlinx-serialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json-jvm\", version.ref = \"serialization\" }\nleakcanary-android = { module = \"com.squareup.leakcanary:leakcanary-android\", version.ref = \"leakcanary\" }\nlifecycle-process = { module = \"androidx.lifecycle:lifecycle-process\", version.ref = \"lifecycle\" }\nlifecycle-service = { module = \"androidx.lifecycle:lifecycle-service\", version.ref = \"lifecycle\" }\nlifecycle-viewmodel = { module = \"androidx.lifecycle:lifecycle-viewmodel-ktx\", version.ref = \"lifecycle\" }\nmarkwon = { module = \"io.noties.markwon:core\", version.ref = \"markwon\" }\nmaterial = { module = \"com.google.android.material:material\", version.ref = \"material\" }\nmoshi-kotlin = { module = \"com.squareup.moshi:moshi-kotlin\", version.ref = \"moshi\" }\nokhttp = { module = \"com.squareup.okhttp3:okhttp\", version.ref = \"okhttp\" }\nokhttp-dnsoverhttps = { module = \"com.squareup.okhttp3:okhttp-dnsoverhttps\", version.ref = \"okhttp\" }\nokhttp-tls = { module = \"com.squareup.okhttp3:okhttp-tls\", version.ref = \"okhttp\" }\nokio = { module = \"com.squareup.okio:okio\", version.ref = \"okio\" }\nssiv = { module = \"com.github.KotatsuApp:subsampling-scale-image-view\", version.ref = \"ssiv\" }\nworkinspector = { module = \"com.github.Koitharu:WorkInspector\", version.ref = \"workinspector\" }\nandroidx-window = { module = \"androidx.window:window\", version.ref = \"window\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"gradle\" }\nhilt = { id = \"com.google.dagger.hilt.android\", version.ref = \"dagger\" }\nkotlin = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\nkotlinx-serizliation = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"kotlin\" }\nksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\nroom = { id = \"androidx.room\", version.ref = \"room\" }\ndecoroutinator = { id = \"dev.reformator.stacktracedecoroutinator\", version.ref = \"decoroutinator\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.0.0-bin.zip\ndistributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "## For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n#\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx1024m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n#\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n#Sat Sep 19 17:19:33 EEST 2020\nandroid.enableJetifier=false\nandroid.nonTransitiveRClass=true\nandroid.useAndroidX=true\nkotlin.code.style=official\norg.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\\=\"-Xmx1536M\"\nandroid.enableR8.fullMode=true\nandroid.nonFinalResIds=false\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "metadata/en-US/full_description.txt",
    "content": "Kotatsu is a free and open source manga reader for Android.\n\n\n**Main Features:**\n\n\n- Online manga catalogs (with 1100+ manga sources)\n- Search manga by name, genres, and more filters\n- Favorites organized by user-defined categories\n- Reading history, bookmarks, and incognito mode support\n- Download manga and read it offline. Third-party CBZ archives are also supported\n- Clean and convenient Material You UI, optimized for phones, tablets, and desktop\n- Standard and Webtoon-optimized customizable reader, gesture support on reading interface\n- Notifications about new chapters with updates feed, manga recommendations (with filters)\n- Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu\n- Password / fingerprint-protected access to the app\n- Automatically sync app data with other devices on the same account\n- Support for older devices running Android 5+\n\n"
  },
  {
    "path": "metadata/en-US/short_description.txt",
    "content": "Manga reader with online catalogues\n"
  },
  {
    "path": "metadata/ru/full_description.txt",
    "content": "Kotatsu - приложения для чтения манги с открытым исходным кодом.\n\n\n**Основные возможности:**\n\n\n- Онлайн-каталоги манги (более 1100 источников)\n- Поиск манги по названию, жанрам и другим фильтрам\n- Избранное с настраиваемыми категориям\n- История чтения, закладки и поддержка режима Инкогнито\n- Загрузка манги с последующим чтением в автономном режиме. Также поддерживаются сторонние архивы CBZ\n- Простой и удобный Material You интерфейс, оптимизированный как для телефонов, так и для планшетов и ПК\n- Читалка с обычным и Webtoon режимами, поддержка жестов при чтении\n- Уведомления о новых главах и лента обновлений, рекомендации манги (с фильтрами)\n- Интеграция со службами отслеживания манги: Shikimori, AniList, MyAnimeList, Kitsu\n- Защита доступа к приложению паролем или отпечатком пальца\n- Автоматическая синхронизация данных приложения с другими устройствами в рамках учетной записи\n- Поддержка старых устройств на базе Android 5+\n\n"
  },
  {
    "path": "metadata/ru/short_description.txt",
    "content": "Приложение для чтения манги с онлайн каталогами\n"
  },
  {
    "path": "metadata/vi/full_description.txt",
    "content": "Kotatsu là một ứng dụng đọc truyện mã nguồn mở miễn phí dành cho các thiết bị Android\n\n\nCác tính năng chính:\n\n\n- Hỗ trợ 100% tiếng Việt, được phát triển và đóng góp bởi cộng đồng người Việt\n- Có nhiều nguồn đọc Online đa dạng (với hơn 1100 nguồn khác nhau), được cập nhật liên tục\n- Tìm kiếm manga với nhiều bộ lọc khác nhau (tên, thể loại,...)\n- Lưu lại manga vào lịch sử khi đọc / đánh dấu trang\n- Mục yêu thích được sắp xếp theo thứ tự do người dùng quản lí\n- Tải và đọc manga ngoại tuyến (không cần mạng). Các tệp lưu trữ manga định dạng CBZ cũng được hỗ trợ!\n- Giao diện rõ ràng, đơn giản, được tối ưu hóa cho cả điện thoại & máy tính bảng\n- Hỗ trợ nhiều chế độ đọc khác nhau: Webtoon; Trái qua phải; Phải qua trái;...\n- Nhận các thông báo khi manga xuất hiện chương mới tại mục Feed của ứng dụng, nhận gợi ý manga (với bộ lọc tùy chọn)\n- Tích hợp các trang giúp theo dõi manga: Shikimori, AniList, MyAnimeList, Kitsu\n- Bảo vệ ứng dụng bằng mật khẩu cá nhân / Sử dụng sinh trắc học (vân tay)\n- Tạo tài khoản giúp đồng bộ lịch sử và mục yêu thích của bạn trên nhiều thiết bị khác nhau\n- Hỗ trợ cho các thiết bị cũ có cấu hình yếu (từ Android 5 trở lên)\n\n"
  },
  {
    "path": "metadata/vi/short_description.txt",
    "content": "Ứng dụng đọc truyện miễn phí với nhiều nguồn Online đa dạng"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n\trepositories {\n\t\tgoogle {\n\t\t\tcontent {\n\t\t\t\tincludeGroupByRegex(\"com\\\\.android.*\")\n\t\t\t\tincludeGroupByRegex(\"com\\\\.google.*\")\n\t\t\t\tincludeGroupByRegex(\"androidx.*\")\n\t\t\t}\n\t\t}\n\t\tmavenCentral()\n\t\tgradlePluginPortal()\n\t\tmaven {\n\t\t\turl 'https://jitpack.io'\n\t\t\tcontent {\n\t\t\t\tincludeGroupByRegex(\"com\\\\.github.*\")\n\t\t\t}\n\t\t}\n\t}\n}\ndependencyResolutionManagement {\n\trepositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n\trepositories {\n\t\tgoogle()\n\t\tmavenCentral()\n\t\tmaven {\n\t\t\turl 'https://jitpack.io'\n\t\t}\n\t}\n}\n\ninclude ':app'\nrootProject.name = \"Kotatsu\"\n"
  }
]